目录
1、基本概念
互斥与同步是最基本的逻辑概念:
- 互斥指的是控制两个进度使之互相排斥,不同时运行。
- 同步指的是控制两个进度使之有先有后,次序可控。
2、 互斥锁基本逻辑
使得多线程间互斥运行的最简单办法,就是增加一个互斥锁。任何一条线程要开始运行互斥区间的代码,都必须先获取互斥锁,而互斥锁的本质是一个二值信号量,因此当其中一条线程抢先获取了互斥锁之后,其余线程就无法再次获取了,效果相当于给相关的资源加了把锁,直到使用者主动解锁,其余线程方可有机会获取这把锁。
互斥锁使用场景: 当我们使用一些临界资源时,防止多个线程同时访问,我们可以这么做,在访问临界资源前, 让线程先上锁,然后再访问资源,访问完了之后就解锁,让别的线程去上锁。
说明:临界资源:共享资源(多线程之间需要共同操作的资源)
互斥锁的函数接口说明如下:
1)定义互斥锁变量 -> 数据类型: pthread_mutex_t --->全局变量
pthread_mutex_t m;
2)初始化互斥锁 -> pthread_mutex_init()
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
参数:mutex: 未初始化过互斥锁变量的地址
mutexattr:普通属性,NULL
返回值:成功:0
失败:非0错误码
静态初始化:
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
也就是说,以上这句话等价于:
pthread_mutex_t m;
pthread_mutex_init(&m,NULL);
3)上锁。 -> pthread_mutex_lock()
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:mutex:互斥锁变量的地址
返回值:成功:0
失败:非0错误码
4)解锁 -> pthread_mutex_unlock()
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:mutex:互斥锁变量的地址
返回值:成功:0
失败:非0错误码
5)销毁互斥锁。 -> pthread_mutex_destroy()
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:mutex:互斥锁变量的地址
返回值:成功:0
失败:非0错误码
3、互斥锁测试代码
#include<stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/sem.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
int g_val = 10;
//1、先定义一个互斥锁变量
pthread_mutex_t mutex;
//静态初始化
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void* start_routine1(void*arg)
{
//在访问共享资源的时候先上锁
pthread_mutex_lock(&mutex);
g_val = 20;
sleep(1);
printf("start_routine1 g_val:%d\n",g_val);
//共享资源 使用结束的时候要解锁
pthread_mutex_unlock(&mutex);
}
void* start_routine2(void*arg)
{
//在访问 共享资源的时候 先 上锁 --如果没有拿到锁,会阻塞 等待 有锁
pthread_mutex_lock(&mutex);
g_val = 200;
sleep(2);
printf("start_routine2 g_val:%d\n",g_val);
//共享资源 使用结束的时候要解锁
pthread_mutex_unlock(&mutex);
}
int main()
{
//2、初始化互斥锁
pthread_mutex_init(&mutex,NULL);
//1、子线程1
pthread_t thread1;
pthread_create(&thread1,NULL,start_routine1, NULL);
//2、子线程2
pthread_t thread2;
pthread_create(&thread2,NULL,start_routine2, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
4、读写锁基本逻辑
对于互斥锁而言,凡是涉及临界资源的访问一律加锁,这在并发读操作的场景下会大量浪费时间。要想提高访问效率,就必须要将对资源的读写操作加以区分:读操作可以多任务并发执行,只有写操作才进行恰当的互斥。这就是读写锁的设计来源。
1.互斥锁的缺陷
互斥锁无论是读取共享资源,还是修改共享资源,都要上锁,而是在上锁期间,不能被别的线程上锁。
2.读写锁的优势
访问资源(一起读一本书) -> 同时上读锁 -> 读锁就是一把共享锁。
修改资源 -> 不能同时上写锁 -> 写锁就是一把互斥锁。
这把既有读锁,又有写锁的锁,就称之为读写锁。
读写锁的函数接口说明如下:
1)定义一个读写锁变量 (数据类型: pthread_rwlock_t)
pthread_rwlock_t rwlock; (rwlock=read write lock)
2)初始化读写锁----》man 3 pthread_rwlock_init
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t * rwlock,const pthread_rwlockattr_t * attr);
参数:rwlock:读写锁变量的地址
attr:属性,一般为NULL
返回值:成功:0
失败:非0错误码
3)读锁上锁。--->man 3 pthread_rwlock_rdlock (rdlock = read lock)
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
参数:rwlock:读写锁变量的地址
4)写锁上锁 ---》pthread_rwlock_wrlock (wrlock = write lock)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
参数:rwlock:读写锁变量的地址
5)读写锁解锁 ---》pthread_rwlock_unlock (写锁与读锁的 解锁函数是一样的)
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数:rwlock:读写锁变量的地址
6)销毁读写锁。----》pthread_rwlock_destroy
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:rwlock:读写锁变量的地址
5、读写锁测试代码
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/sem.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
//定义一个读写锁变量
pthread_rwlock_t rwlock;
int g_val = 10;
void *start_routine1(void*arg)
{
pthread_rwlock_wrlock(&rwlock);//加写锁
g_val = 20;
sleep(3);
printf("start_routine1: %d\n",g_val);
pthread_rwlock_unlock(&rwlock); //解锁
}
void *start_routine2(void*arg)
{
pthread_rwlock_wrlock(&rwlock);//加写锁
g_val = 200;
sleep(3);
printf("start_routine2: %d\n",g_val);
pthread_rwlock_unlock(&rwlock); //解锁
}
void *start_routine3(void*arg)
{
pthread_rwlock_rdlock(&rwlock);//加读锁
//获取数据 访问(读取) 共享资源但是没有进行修改(写入)
int cnt=5;
while(cnt--){
sleep(1);
printf("start_routine3: %d\n",g_val);
}
pthread_rwlock_unlock(&rwlock); //解锁
}
void *start_routine4(void*arg)
{
pthread_rwlock_rdlock(&rwlock);//加读锁
//获取数据 访问(读取) 共享资源 但是没有进行修改(写入)
int cnt=5;
while(cnt--){
sleep(1);
printf("start_routine4: %d\n",g_val);
}
pthread_rwlock_unlock(&rwlock); //解锁
}
int main()
{
//初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
pthread_t thread1;
pthread_create(&thread1,NULL,start_routine1, NULL);
pthread_t thread2;
pthread_create(&thread2,NULL,start_routine2, NULL);
pthread_t thread3;
pthread_create(&thread3,NULL,start_routine3, NULL);
pthread_t thread4;
pthread_create(&thread4,NULL,start_routine4, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
pthread_join(thread4, NULL);
//销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
6、条件变量基本概念
在许多场合中,程序的执行通常需要满足一定的条件,条件不成熟的时候,任务应该进入睡眠阻塞等待,条件成熟时应该可以被快速唤醒。另外,在并发程序中,会其他任务同时访问该条件,因此任何时候都必须以互斥的方式对条件进行访问。条件量就是专门解决上述场景的逻辑机制。
注意,上述表述中,条件和条件量是两个不同的东西,所谓条件就是指程序要继续运行所需要的前提条件,比如文件是否读完、内存是否清空等具体的场景限定,而条件量(即pthread_cond_t)是本节课件要讨论的一种同步互斥变量,专用于解决上述逻辑场景。
说明:
- 在进行条件判断前,先加锁(防止其他任务并发访问)
- 成功加锁后,判断条件是否允许
- 若条件允许,则直接操作临界资源,然后释放锁
- 若条件不允许,则进入条件量的等待队列中睡眠,并同时释放锁
- 在条件量中睡眠的任务,可以被其他任务唤醒,唤醒时重新判定条件是否允许程序继续执行,当然也是必须先加锁。
条件变量的函数接口说明如下:
1)先定义一个条件变量。 -> 数据类型: pthread_cond_t
pthread_cond_t cond;
2)初始化条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t * attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化
参数:cond:条件变量的地址
cond_attr 普通属性,NULL。
返回值:成功:0
失败:非0错误码
3)如何进入条件变量中等待(两个功能:1.阻塞等待 2.自动解锁)
int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
参数:cond:条件变量的地址
mutex:互斥锁的地址 -> 进入条件变量中,会自动解锁。
返回值:成功:0
失败:非0错误码
4)如何唤醒条件变量中等待的线程? -> 线程离开条件变量时,会自动上锁。
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);//广播: 唤醒所有在条件变量中等待的线程
int pthread_cond_signal(pthread_cond_t *cond);//单播: 随机唤醒一个在条件变量中等待的线程
参数:cond:条件变量的地址
返回值:成功:0
失败:非0错误码
5)销毁条件变量----》man 3 pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
参数:cond:条件变量的地址
7、条件量的使用
条件量一般要跟互斥锁(或二值信号量)配套使用,互斥锁提供锁住临界资源的功能,条件量提供阻塞睡眠和唤醒的功能。
示例代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/sem.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
/*
练习:有4个小孩,每个小孩的任务就是领取生活费1000,回学校之前,父母先在银行卡中存2000块钱,
2个线程拿到1000块钱之后退出,另外2个线程进去条件变量中等待,父亲再打钱1000,唤醒所有的小孩
起来拿钱, 过一会,再打1000块钱,再唤醒最后一个小孩起来拿钱赶紧走人上学。
*/
int g_money = 2000;
//定义一个互斥锁变量
pthread_mutex_t mutex;
//定义一个条件变量并且静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *start_routine(void*arg)
{
printf("[%lu]子线程 start\n",pthread_self());
pthread_mutex_lock(&mutex);//加锁力度要小
//条件不满足的时候进入 条件变量中等待
while(g_money<1000){
printf("没钱了,进去条件变量中等待父母打钱 并且通知.....\n");
//自动解锁 ,并且阻塞等待
pthread_cond_wait(&cond,&mutex);
printf("父母打钱过来了,已经通知我了,此时余额:%d\n",g_money);
}
//走到这里,说明有钱
g_money -=1000;
printf("[%lu]子线程 拿到钱了,此时银行卡余额:%d\n",pthread_self(),g_money);
pthread_mutex_unlock(&mutex); //解锁
printf("[%lu]子线程 end\n",pthread_self());
//拿钱走人
pthread_exit(NULL);
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&mutex,NULL);
pthread_t thread1; //我
pthread_create(&thread1,NULL,start_routine, NULL);
pthread_t thread2; //哥
pthread_create(&thread2,NULL,start_routine, NULL);
pthread_t thread3; //姐
pthread_create(&thread3,NULL,start_routine, NULL);
pthread_t thread4; //弟
pthread_create(&thread4,NULL,start_routine, NULL);
int cnt=5;
while(cnt--){
sleep(1);
printf("主线程(父母) 即将准备打钱....%d\n",cnt);
}
//主线程(父母) 打钱
pthread_mutex_lock(&mutex);//加锁
g_money +=1000;
pthread_mutex_unlock(&mutex); //解锁
pthread_cond_broadcast(&cond);//广播: 唤醒所有在条件变量中等待的线程
cnt=5;
while(cnt--){
sleep(1);
printf("主线程(父母) 即将准备打钱....%d\n",cnt);
}
pthread_mutex_lock(&mutex);//加锁
g_money +=1000;
pthread_mutex_unlock(&mutex); //解锁
pthread_cond_signal(&cond);//单播: 随机唤醒一个在条件变量中等待的线程
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
pthread_join(thread4, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}