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 进程间通信

posted @ 2023-10-18 14:49  eiSouthBoy  阅读(69)  评论(0)    收藏  举报