多线程编程同步:读写锁
读写锁的定义#
互斥锁锁住后,保证仅有一个线程处理数据(多线程共享的)。要是数据的读取比写入更频繁,且读取操作不涉及共享变量的修改,应允许多个线程读取操作对共享变量的读取。直接使用互斥锁效率太低,若使用读写锁,可以大大提高效率。
读写锁的分配规则:
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 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
2022-10-18 防火墙的操作(Linux系统)