POSIX线程(二)互斥量mutex、条件变量cond,联合使用代码和理解

互斥量mutex

  • 互斥量是实现多线程程序中的同步访问的一种手段,它实现线程访问资源的互斥
  • 互斥量是一个类对象,使用互斥量后,一个资源在同一时刻只能有一个线程去访问它(独占资源,具有唯一性和排他性)
  • 每一个线程在对重要资源操作之前,都应该尝试加锁
  • 多个线程同时尝试lock加锁,结果是只有一个线程能够锁定成功(至于是哪一个是无序的无法确定的),成功的标志是lock函数返回。如果没锁成功,那么线程就会阻塞在lock这,不断尝试去锁,一直到成功。
  • 互斥量可以理解为是信号量当Semaphore=1时的一种特殊情况。互斥量用于线程的互斥,信号线用于线程的同步
  • 关于互斥量和信号量的更多区别可见信号量和互斥量(锁)的区别

对互斥量的操作函数

对互斥量的操作主要有四个函数:创建,加锁,解锁,销毁
在这里插入图片描述

pthread_mutex_init对互斥量的初始化

可以理解为令mutex=1

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
	 					const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
返回值:若成功,返回0,否则,返回错误编号 

参数1:

  • pthread_mutex_t类型:互斥量类型其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数,只能为0或1值。
  • 通过pthread_mutex_t mutex命令在pthread_mutex_init函数之前定义变量mutex,取地址传为第一个参数
  • restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。
    参数2:
  • attr互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).
  • 静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 动态初始化:如果动态分配互斥量(例如通过调用malloc函数、作为局部变量)应采用动态初始化。pthread_mutex_init(&mutex, NULL)在释放内存前需要调用pthread_mutex_init

pthread_mutex_destroy销毁一个互斥锁

#include<pthread.h>
pthread_mutex_destroy(pthread_mutex_t *mutex); 
                                        返回值:若成功,返回0,否则,返回错误编号  

参数:同上&mutex

pthread_mutex_lock加锁

可以理解为令mutex–;

#include<pthread.h>
pthread_mutex_lock(pthread_mutex_t *mutex);
                                       返回值:若成功,返回0,否则,返回错误编号

如果互斥量已经上锁,调用lock的线程将阻塞直到互斥量被解锁它在加锁
如果不希望线程阻塞,可以使用pthread_mutex_trylock

pthread_mutex_trylock尝试加锁

#include<pthread.h>
pthread_mutex_trylock(pthread_mutex_t *mutex);
                                       返回值:若成功,返回0,否则,返回错误编号

调用此函数加锁互斥量,若互斥量未锁住,trylock将锁住互斥量
而若互斥量已经锁住,线程不会阻塞而直接返回EBUSY

pthread_mutex_unlock解锁

可以理解为令mutex++;

#include<pthread.h>
pthread_mutex_unlock(pthread_mutex_t *mutex);
                                          返回值:若成功,返回0,否则,返回错误编号	

注意:unlock会将阻塞在该锁上的所有线程全部唤醒,并使得其他线程自动再次尝试加锁。

至于哪个线程先被唤醒取决于优先级调度。默认:先阻塞、先唤醒。

代码示例

#include <iostream>
#include <pthread.h>//pthread_create
#include <stdio.h>//perror
#include <unistd.h>//sleep
#include <semaphore.h>
#include "PthreadPool.h"
using namespace std;

int global_number = 0;

pthread_mutex_t mutex;//互斥量变量,全局

void *thread_fun(void *data)
{

	pthread_mutex_lock(&mutex);
	for(int i=0;i<5;i++)
	{
		global_number++;
		cout << "thread runnning..." << global_number<<endl;
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);

}

int main(int argc, char *argv[])
{
	pthread_t pthread_id;

	//对互斥量进行初始化
	pthread_mutex_init(&mutex, NULL);

	if (pthread_create(&pthread_id, NULL, thread_fun, NULL) < 0)//2指定线程属性3函数指针4给线程传递的参数
	{
		perror("pthread create err:");
	}

	sleep(1);//先走副线程加锁,再主线程执行
	
	//加锁,做业务
	pthread_mutex_lock(&mutex);
	for (int i = 0; i < 5; i++)
	{
		global_number++;
		cout << "main  runnning..." << global_number<< endl;
		sleep(1);
	}
	//做完业务,解锁
	pthread_mutex_unlock(&mutex);
	
	//pthread_mutex_destroy(&mutex);	
	return 0;
}

打印结果如下
在这里插入图片描述

条件变量cond

引用《APUE》的描述:

条件变量是线程可用的另外一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

人话这么理解:我们需要完成一个任务需要多个线程配合,其中工序需要达到某一条件后,之后的工作才能继续进行。这种情况下就存在两个(多个)进程需要进行信息交互的需求,一个线程完成一项条件并在完成后告诉其他线程,其他线程则接收条件发生改变的信号。

“条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。”所谓“无竞争”“wait in a race-free way”指的是条件改变/条件被满足这一信号会发送到所有等待这个信号的线程,而不是一个线程接收到信号其他线程就接收不到了。

对条件变量的操作函数

操作函数主要有以下几个

pthread_cond_init对条件变量的初始化

#include<pthread.h>
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); 

返回值:
成功返回0失败返回错误码
参数1:

  • pthread_cond_t 类型:条件变量类型
  • 取地址传为第一个参数

参数2:

  • 静态初始化:在main函数外pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
  • 动态初始化:pthread_cond_init(&cond, NULL),除非要创建一个具有非默认属性的条件变量,否则cond_attr参数设为NULL

pthread_cond_destroy对条件变量销毁

//销毁条件变量 
int pthread_cond_destroy(pthread_cond_t *cond); 

返回值:
成功返回0失败返回错误码
在释放条件变量底层的内存空间之前,可以使用pthread_cond_destroy对条件变量进行反初始化

pthread_cond_wait自动解锁互斥锁,等待条件满足

  • 执行这一函数将解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。
  • 我们将互斥量和条件变量传给函数,然后调用该函数的线程将被挂起(放到等待条件的线程列表上),线程不占用CPU时间,直到条件变量被触发。
  • pthread_cond_wait函数对互斥量的解锁和对线程的挂起都是自动进行的,所以在调用pthread_cond_wait之前,所以线程都要对互斥量加锁,保证线程加锁互斥量和进入等待条件变量期间,条件变量不被触发
  • pthread_cond_wait返回时,互斥量将再次被锁住。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); 
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);

pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在 abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁,且 pthread_cond_timedwait 返回错误 ETIMEDOUT。abstime 参数指定一个绝对时间,时间原点与 time 和 gettimeofday 相同:abstime = 0 表示 1970 年 1 月 1 日 00:00:00 GMT
关于使用gettimeofday获取当前时间并传入绝对时间可参看《APUE》

pthread_cond_signal使在条件变量上等待的线程中的一个线程被唤醒

如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动,但不能指定哪一个。

int pthread_cond_signal(pthread_cond_t *cond); 

pthread_cond_broadcast使在条件变量上等待的所有线程被唤醒

重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。

int pthread_cond_broadcast(pthread_cond_t *cond);

互斥量和条件变量联合使用示例代码

对flag++,直到flag==4(设为满足条件)时signal唤醒线程运行
多开几个线程就可尝试broadcast并与signal进行对比

#include <iostream>
#include <pthread.h>//pthread_create
#include <stdio.h>//perror
#include <unistd.h>//sleep
#include <semaphore.h>
#include "PthreadPool.h"
using namespace std;

int global_number = 0;

pthread_mutex_t mutex;//互斥量变量,全局
pthread_cond_t cond;//条件变量类型

void *thread_fun(void *data)//data获得pthread_create的第四个参数传的数据
{
	//加锁,做业务
	pthread_mutex_lock(&mutex);
		//线程一进来就进入睡眠等待条件变量成立
	cout << "thread wait" << endl;
	pthread_cond_wait(&cond, &mutex);//等待,条件变量成立,和互斥量捆绑使用,这个时候会把锁释放出来
	
	cout << "thread wake up" << endl;
	cout << "thread do something" << endl;
	
	//做完业务,解锁
	pthread_mutex_unlock(&mutex);

}

int main(int argc, char *argv[])
{
	pthread_t pthread_id;

	//对互斥量进行初始化
	pthread_mutex_init(&mutex, NULL);
	//条件变量初始化
	pthread_cond_init(&cond, NULL);
	if (pthread_create(&pthread_id, NULL, thread_fun, NULL) < 0)
	{
		perror("pthread create err:");
	}

	sleep(1);//join或者sleep先走副再走 主

	int flag = 0;
	for (int i = 0; i < 10; i++)
	{
		cout << "flag=" << flag << endl;
		flag++;

		if (flag == 4)//模拟条件成立,就放行,唤醒副线程
		{
			pthread_mutex_lock(&mutex);
			pthread_cond_signal(&cond);//通知线程唤醒
			//pthread_cond_broadcast(&cond);//广播唤醒全部
			pthread_mutex_unlock(&mutex);
		}
		sleep(1);
	}

	pthread_cond_destroy(&cond);
	pthread_mutex_destroy(&mutex);	
	return 0;
}

打印结果:
在这里插入图片描述

一些疑问

为什么使用wait之前要加锁?

使用互斥锁保护条件变量,wait中的1、把调用线程放到条件等待队列上2、互斥量解锁操作是原子性的,如果不是原子性的,上面的两个步骤中间就可能插入其它操作。比如,如果先释放mutex,这时候生产者线程向队列中添加数据,然后signal,之后消费者线程才去『把调用线程放到等待队列上』,signal信号就这样被丢失了。

This is vital because it eliminates the race condition of a typical unprotected set-and-test. Said-race condition without the atomic unlatch-and-wait causes significant problems. Ex: you own the mutex, check the data, not what you need, so you unlock the mutex an wait for an event. It is conceivable that between the time you unlatch the mutex and actually begin the wait-operation the condition you’re waiting on happens in that slice of time. And you missed it. That won’t happen when properly waiting with a cvar-mtx combo.

参看:why pthread_cond_wait need an lock?

为什么使用signal之前要加锁,之后要解锁?

因为如果不这样将可能丢失“wakeups”
在这里插入图片描述
Calling pthread_cond_signal without locking mutex

ps:本人学习过程中发现联合使用中的疑问很多,而且有些解释互相矛盾,故本文作为初学,不作过多讨论,仅在应用上粗浅做个例子,更多疑问请参考Stack Overflow上的讨论以及参看链接。

内容参考:
《APUE》
互斥量:https://blog.csdn.net/isunbin/article/details/83415873
条件变量:https://www.cnblogs.com/harlanc/p/8596211.html#_label2_0

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值