读写锁
-
互斥锁的缺陷
- 互斥锁无论读取共享资源,还是修改共享资源,都是要上锁,而且在上锁期间,其它线程不能上锁
-
概念
- 与互斥锁类似,但是读写锁允许更高的并行性。特性是,写独占,读共享
-
读写锁的状态
- 特别强调:读写锁只有一把,但具有两种状态
- 读模式下的加锁状态(读锁)
- 写模式下的加锁状态(写锁)
-
读写锁的特性
- 读写锁是“写锁”时,解锁前,所有对该锁该锁的线程都阻塞
- 读写锁是“读锁”时,如果线程以读模式对其加锁会成功;写模式加锁就会阻塞
- 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,是以独占模式锁住的
- 写独占,读共享
- 读写锁非常适合对于数据结构读的次数远大于写的情况
- 写锁优先级高
-
读写锁函数的接口
-
定义一个读写锁变量----->
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; }
-
条件变量
-
什么是条件变量
- 线程因为某一条件/情况不成立,进入一个变量中等待,这个存放线程的变量的就是条件变量。条件变量本身不是锁,但它可以造成线程堵塞。通常是与互斥锁配合使用。给多线程提供一个会和的场合
-
关于条件变量的函数接口
-
定义一个条件变量----->
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; }
终端显示:
-
条件变量生产者消费者模型
- 概念
- 线程同步典型的案例即为生产者消费模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个线程模拟生产者行为,一个去模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加商品,消费就是消费者去消耗掉商品