Loading

多线程编程同步:读写锁

读写锁的定义#

互斥锁锁住后,保证仅有一个线程处理数据(多线程共享的)。要是数据的读取比写入更频繁,且读取操作不涉及共享变量的修改,应允许多个线程读取操作对共享变量的读取。直接使用互斥锁效率太低,若使用读写锁,可以大大提高效率。

读写锁的分配规则:

1)只要没有线程持有某个特定的读写锁,那么任意数目的线程可以持有该读写锁用于读。

2)仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写。

📌 当多个线程竞争读写锁时,用于写的线程比用于读的线程优先级要高。

读写锁的使用#

读写锁的声明

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);

/* 不阻塞版本 */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);


例子说明:模拟多个线程写数据和多个线程读数据,验证读写锁的逻辑。
当某一个线程写操作时,其他写线程和所有读线程是不能获取读写锁的,其他写线程和所有读线程开始获取读写锁,其中其他写线程获取读写锁的优先级比所有读线程要高。
当某一个线程读操作时,其他读线程是可以获取读写锁用于读的,所有写线程不能获取读写锁。

prodcons_rwlock.c

/*
 * @Description: multi-thread sync : read-write lock
 * @Author: 
 * @version: 
 * @Date: 2023-10-16 10:01:24
 * @LastEditors: 
 * @LastEditTime: 2023-10-16 10:31:11
 */

#define _GNU_SOURCE
//==============================================================================
// Include files
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>

//==============================================================================
// Constants


#define MAX_N_WRITE 2
#define MAX_N_READ 3

#define MAX(a,b) ((a)>(b) ? (a):(b)) 
#define MIN(a,b) ((a)<(b) ? (a):(b))
//==============================================================================
// types

struct RW_DATA_ST
{
    pthread_rwlock_t rwlock;
    int number;
};

//==============================================================================
// global varibles

static struct RW_DATA_ST g_rw_data = {PTHREAD_RWLOCK_INITIALIZER, 0};
static int g_common_run = 1;

//==============================================================================
// global functions

void *thread_write(void *arg);
void *thread_read(void *arg);
void sighandler(int signum);
void get_local_datetime_string(char *datetime, int size);

//==============================================================================
// The main entry-point function.

int main(int argc, char **argv)
{
    int i = 0;
    pthread_t tid_write[MAX_N_WRITE] = {0};
    pthread_t tid_read[MAX_N_READ] = {0};
    
    signal(SIGINT, sighandler);
    /* start write threads */
    int wr_serial[MAX_N_WRITE] = {0};
    int rd_serial[MAX_N_READ] = {0};
    for (i = 0; i < MAX_N_WRITE; i++)
    {
        wr_serial[i] = i;
        pthread_create(&tid_write[i], NULL, thread_write, &wr_serial[i]);
    }
    /* start read threads */
    for (i = 0; i <  MAX_N_READ; i++)
    {
        rd_serial[i] = i;
        pthread_create(&tid_read[i], NULL, thread_read, &i);
    }
    
    /* destroy threads */
    for (i = 0; i < MAX_N_WRITE; i++)
    {
        pthread_join(tid_write[i], NULL);
    }
    for (i = 0; i < MAX_N_READ; i++)
    {
        pthread_join(tid_read[i], NULL);
    }
    printf("main thread exit\n");

    exit(0);
}

void sighandler(int signum)
{
    if (signum == SIGINT)
    {
        printf("catch SIGINT\n");
        g_common_run = 0;
    }
}

void get_local_datetime_string(char *datetime, int size)
{
    struct timeval tv;
    struct tm* timeinfo;
    char buffer[80];
    // 获取当前日期和时间
    gettimeofday(&tv, NULL);
    // 转换为本地时间
    timeinfo = localtime(&tv.tv_sec);
    // 格式化日期时间字符串,包括毫秒
    strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
    sprintf(buffer + strlen(buffer), ".%03ld", tv.tv_usec / 1000);
    // 拷贝日期时间字符串
    snprintf(datetime, size, "%s", buffer);
}

void *thread_write(void *arg)
{
    int i = *(int *)arg;
    int cur = 0;
    char datetime[128] = {0};
    while(g_common_run)
    {
        pthread_rwlock_wrlock(&g_rw_data.rwlock);
        cur = g_rw_data.number;
        cur++;
        g_rw_data.number = cur;
        get_local_datetime_string(datetime, sizeof(datetime) - 1);
        printf("write tid(%ld) | serial [%d] | data: %3d | %s\n", pthread_self(), i, cur, datetime);
        pthread_rwlock_unlock(&g_rw_data.rwlock);
        sleep(rand() % 3);
    }

    return (NULL);
}

void *thread_read(void *arg)
{
    int i = *(int *)arg;
    int cur = 0;
    char datetime[128] = {0};
    while (g_common_run)
    {
        pthread_rwlock_rdlock(&g_rw_data.rwlock);
        cur = g_rw_data.number;
        get_local_datetime_string(datetime, sizeof(datetime) - 1);
        printf("read  tid(%ld) | serial [%d] | data: %3d | %s\n", pthread_self(), i, cur, datetime);
        pthread_rwlock_unlock(&g_rw_data.rwlock);
        sleep(rand() % 3);
    }

    return (NULL);
}

观察上图,可以验证:一个线程在写操作时,其他写线程和所有读线程是无法操作变量:g_rw_data.number;一个线程在读操作时,其他读线程可以进行读操作,但写线程不能操作变量:g_rw_data.number

参考引用#

UNIX网络编程 卷2 进程间通信

作者:caojun97

出处:https://www.cnblogs.com/caojun97/p/17766774.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   eiSouthBoy  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2022-10-18 防火墙的操作(Linux系统)
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu