线程同步互斥之互斥锁,读写锁,条件变量

本文介绍了多线程编程中的互斥锁、读写锁以及条件变量的概念和使用方法。互斥锁确保了临界资源的独占访问,读写锁则允许并发读取但独占写入,条件变量则提供了基于特定条件的线程阻塞与唤醒机制。文中通过代码示例展示了这些同步原语的实现和应用。
摘要由CSDN通过智能技术生成

目录

1、基本概念

2、 互斥锁基本逻辑

3、互斥锁测试代码

4、读写锁基本逻辑

5、读写锁测试代码

6、条件变量基本概念

7、条件量的使用

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. 在进行条件判断前,先加锁(防止其他任务并发访问)
  2. 成功加锁后,判断条件是否允许
    • 若条件允许,则直接操作临界资源,然后释放锁
    • 若条件不允许,则进入条件量的等待队列中睡眠,并同时释放锁
  3. 在条件量中睡眠的任务,可以被其他任务唤醒,唤醒时重新判定条件是否允许程序继续执行,当然也是必须先加锁。

条件变量的函数接口说明如下:

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值