系统编程-读写锁

bcc0729 / 2023-09-02 / 原文

读写锁

  1. 互斥锁的缺陷

    • 互斥锁无论读取共享资源,还是修改共享资源,都是要上锁,而且在上锁期间,其它线程不能上锁
  2. 概念

    • 与互斥锁类似,但是读写锁允许更高的并行性。特性是,写独占,读共享
  3. 读写锁的状态

    • 特别强调:读写锁只有一把,但具有两种状态
    • 读模式下的加锁状态(读锁)
    • 写模式下的加锁状态(写锁)
  4. 读写锁的特性

    • 读写锁是“写锁”时,解锁前,所有对该锁该锁的线程都阻塞
    • 读写锁是“读锁”时,如果线程以读模式对其加锁会成功;写模式加锁就会阻塞
    • 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,是以独占模式锁住的
    • 写独占,读共享
    • 读写锁非常适合对于数据结构读的次数远大于写的情况
    • 写锁优先级高
  5. 读写锁函数的接口

    • 定义一个读写锁变量----->pthread_rwlock_t rwlock

    • 初始化读写锁变量----->pthread_rwlock_init()

    #include <pthread.h>
    	int pthread_rwlock_init(
    		pthread_rwlock_t *restrict rwlock,  //参数1:读写锁变量的地址
    		const pthread_rwlockattr_t *restrict attr  //参数2:属性,一般是NULL
    	);
    	// 返回值:成功0,失败非0错误码
    
    • 读锁上锁----->pthread_rwlock_rdlock()
    #include <pthread.h>
    	int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);  // 参数:读写锁变量的地址
    	// 返回值:成功0,失败非0错误码
    
    • 写锁上锁----->pthread_rwlock_wrlock()
    #include <pthread.h>
    	int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);  // 参数:读写锁变量的地址
    	// 返回值:成功0,失败非0错误码
    
    • 读写锁解锁---->pthread_rwlock_unlock
    #include <pthread.h>
    	int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  // 参数:读写锁变量的地址
    	// 返回值:成功0,失败非0错误码
    
    • 销毁读写锁---->pthread_rwlock_destroy
    #include <pthread.h>
    	int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);  // 参数:读写锁变量的地址
    	// 返回值:成功0,失败非0错误码
    
    • 案例:
    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    // 读写锁变量
    pthread_rwlock_t rwlock;
    
    // 全局变量
    int main_val = 0;
    
    void *routine1(void *arg)
    {
    	//写锁
    	pthread_rwlock_wrlock(&rwlock);
    	//写操作
    	main_val=100;
    	for(int i=0;i<5;i++)
    	{
    		sleep(1);
    		main_val+=main_val*i;
    		printf("%d routine1 100 main_val:%d\n",i,main_val);
    	}
    	//解锁
    	 pthread_rwlock_unlock(&rwlock);
    	//退出
    	pthread_exit(NULL);
    }
    void *routine2(void *arg)
    {
    	//写锁
    	pthread_rwlock_wrlock(&rwlock);
    	//写操作
    	main_val=200;
    	for(int i=0;i<5;i++)
    	{
    		sleep(1);
    		main_val+=main_val*i;
    		printf("%d routine2 200 main_val:%d\n",i,main_val);
    	}
    	//解锁
    	pthread_rwlock_unlock(&rwlock);
    	//退出
    	pthread_exit(NULL);
    }
    void *routine3(void *arg)
    {
    	sleep(1);
    	//读锁
    	pthread_rwlock_rdlock(&rwlock);
    	//读操作
    	for(int i=0;i<5;i++)
    	{
    		sleep(1);
    		printf("routine3 main_val:%d\n",main_val);
    	}
    	//解锁
    	pthread_rwlock_unlock(&rwlock);
    	//退出
    	pthread_exit(NULL);
    }
    void *routine4(void *arg)
    {
    	sleep(1);
    	//读锁
    	pthread_rwlock_rdlock(&rwlock);
    	//读操作
    	for(int i=0;i<5;i++)
    	{
    		sleep(1);
    
    		printf("routine4  main_val:%d\n",main_val);
    	}
    	//解锁
    	pthread_rwlock_unlock(&rwlock);
    	//退出
    	pthread_exit(NULL);
    }
    
    int main(int argc, char const *argv[])
    {
    	// 初始化读写锁
    	pthread_rwlock_init(&rwlock, NULL);
    
    	// 线程ID数组
    	pthread_t tid[4];
    
    	// 线程函数指针数组
    	void* (*fp[4])(void*) = {routine1, routine2, routine3, routine4};
    
    	// 创建线程
    	for (int i = 0; i < 4; ++i)
    	{
    		pthread_create(&tid[i], NULL, fp[i], NULL);
    	}
    
    	// 等待线程结束回收
    	for (int i = 0; i < 4; ++i)
    	{
    		pthread_join(tid[i], NULL);
    	}
    
    	// 销毁读写锁
    	pthread_rwlock_destroy(&rwlock);
    
    	return 0;
    }
    

条件变量

  1. 什么是条件变量

    • 线程因为某一条件/情况不成立,进入一个变量中等待,这个存放线程的变量的就是条件变量。条件变量本身不是锁,但它可以造成线程堵塞。通常是与互斥锁配合使用。给多线程提供一个会和的场合
  2. 关于条件变量的函数接口

    • 定义一个条件变量----->pthread_cond_t cond

    • 初始化条件变量----->pthread_cond_init()

    #include <pthread.h>
    	int pthread_cond_init(
    		pthread_cond_t *cond,  // 参数1:条件变量地址
    		pthread_condattr_t *cond_attr  // 参数2:普通属性,NULL
    	);
    	// 返回值:成功0,失败非0错误码
    
    • 如何进入条件变量等待
    #include <pthread.h>
    	int pthread_cond_wait(
    		pthread_cond_t *cond,
    		pthread_mutex_t *mutex
    	);
    	int pthread_cond_timedwait(
    		pthread_cond_t *cond,  // 参数1:条件变量的地址
    		pthread_mutex_t *mutex,  // 参数2:互斥锁的地址---->进入条件变量会自动解锁
    		const struct timespec *abstime  // 参数3: 绝对时间
    	);
    	// 返回值:成功0,失败非0错误码
    
    • 关于pthread_cond_timedwait
    struct timespec
    {
    	time_t tv_sec;  // 秒
    	long tv_nsec;  // 纳秒
    }
    

    获取当前时间:

    	time_t cur=time(NULL);
    	struct timespec t;
    	t.tvsec=cur+1;
    	pthread_cond_timedwait(&cond, &mutex, &t);
    
    • 如何唤醒条件变量中等待的线程?---->线程离开条件变量会自动上锁
    #include <pthread.h>
    	// 单播:随机唤醒(至少)一个在条件变量的线程
    	int pthread_cond_signal(pthread_cond_t *cond);  // 参数: 条件变量的地址
    	// 唤醒所有在条件变量中等待的线程
    	int pthread_cond_broadcast(pthread_cond_t *cond);  // 参数: 条件变量的地址
    	// 返回值:成功0,失败非0错误码
    
    • 销毁条件变量------>pthread_cond_destroy()
    #include <pthread.h>
    	int pthread_cond_destroy(pthread_cond_t *cond);  // 参数:条件变量的地址
    	// 返回值:成功0,失败非0错误码
    

    -案例:
    练习:有4个小孩,每个小孩的任务就是领取生活费1000,他们回学校之前银行卡父亲先打个两千,2个小孩可以领取到,就是两个线程退出,另外两个进入条件变量等待,父亲再打钱1000,唤醒所有的小孩来拿钱,过了一会,再打1000,再唤醒最后一个小孩起来拿钱赶紧去上学。

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    
    // 互斥锁变量
    pthread_mutex_t mutex;
    // 条件变量
    pthread_cond_t cond;
    
    int money = 2000;
    
    void* func1(void*arg)
    {
    	pthread_mutex_lock(&mutex);
    	if(money < 1000)
    	{
    		pthread_cond_wait(&cond, &mutex);
    	}
    	money -= 1000;
    	printf("boy1 拿到钱了\n");
    
    	pthread_mutex_unlock(&mutex);
    	pthread_exit(NULL);
    }
    
    void* func2(void*arg)
    {
    	pthread_mutex_lock(&mutex);
    	if(money < 1000)
    	{
    		pthread_cond_wait(&cond, &mutex);
    	}
    	money -= 1000;
    	printf("boy2 拿到钱了\n");
    
    	pthread_mutex_unlock(&mutex);
    	pthread_exit(NULL);
    }
    
    void* func3(void*arg)
    {
    	pthread_mutex_lock(&mutex);
    	if(money < 1000)
    	{
    		pthread_cond_wait(&cond, &mutex);
    	}
    	money -= 1000;
    	printf("boy3 拿到钱了\n");
    
    	pthread_mutex_unlock(&mutex);
    	pthread_exit(NULL);
    }
    
    void* func4(void*arg)
    {
    	pthread_mutex_lock(&mutex);
    	if(money < 1000)
    	{
    		pthread_cond_wait(&cond, &mutex);
    	}
    	money -= 1000;
    	printf("boy4 拿到钱了\n");
    
    	pthread_mutex_unlock(&mutex);
    	pthread_exit(NULL);
    }
    
    int main(int argc, char const *argv[])
    {
    	// 初始化互斥锁
    	pthread_mutex_init(&mutex, NULL);
    	// 初始化条件变量
    	pthread_cond_init(&cond, NULL);
    
    	pthread_t tid[4];
    
    	void* (*fp[4])(void*) = {func1, func2, func3, func4};
    	for (int i = 0; i < 4; ++i)
    	{
    		pthread_create(&tid[i], NULL, fp[i], NULL);
    	}
    
    	for (int i = 0; i < 5; ++i)
    	{
    		printf("当前延时%d秒\n", i);
    		sleep(1);
    	}
    	printf("father准备打钱了\n");
    	pthread_mutex_lock(&mutex);
    	money += 1000;
    	pthread_mutex_unlock(&mutex);
    	pthread_cond_broadcast(&cond);
    
    	for (int i = 0; i < 5; ++i)
    	{
    		printf("当前延时%d秒\n", i);
    		sleep(1);
    	}
    	printf("father准备打钱了\n");
    	pthread_mutex_lock(&mutex);
    	money += 1000;
    	pthread_mutex_unlock(&mutex);
    	pthread_cond_broadcast(&cond);
    
    	for (int i = 0; i < 4; ++i)
    	{
    		pthread_join(tid[i],NULL);
    	}
    
    	pthread_mutex_destroy(&mutex);
    	pthread_cond_destroy(&cond);
    
    	return 0;
    }
    

    终端显示:
    image

条件变量生产者消费者模型

  1. 概念
    • 线程同步典型的案例即为生产者消费模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个线程模拟生产者行为,一个去模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加商品,消费就是消费者去消耗掉商品