Linux C++ 线程同步锁和wait()/broadcast()功能(一)

首先说下linux下的线程同步锁的实现:

同步锁脱离不开:pthread_mutex_t,mutex是互斥的意思,因此linux下的锁是通过互斥信号来实现的.

pthread_mutex_t:代表一个互斥锁,首先我们需要初始化这个互斥锁.

第一步我们需要初始化互斥锁:

pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&mutex_,&attr);

互斥锁一共有三个类型:

1.PTHREAD_MUTEX_NORMAL
不提供死锁检测,重新锁定互斥锁,会导致死锁,比如在同一个线程内同时锁定2次,第二次的时候必然会发生死锁.

2.PTHREAD_MUTEX_ERRORCHECK
提供死锁检测,重新锁定互斥锁,会返回错误,第一种情况下,第二次锁定会死锁,如果采用PTHREAD_MUTEX_ERRORCHECK,直接会返回错误,不会发生死锁.

3.PTHREAD_MUTEX_RECURSIVE
支持循环锁,该互斥锁维持了一个计数器,线程首次锁定互斥锁,计数器设置为1,线程计数器每锁定一次,计数增加1,释放后-1,变为0后,可以为其它线程所锁定 。

注意上面的说法都是针对同一个线程的.

接下来通过代码来进行分析,以下的代码是提取自webrtc中的.

CriticalSectionWrapper.h文件:

#ifndef _CRITICAL_SECTION_WRAPPER_
#define _CRITICAL_SECTION_WRAPPER_


class CriticalSectionWrapper {
public:
	static CriticalSectionWrapper* CreateCriticalSection();

	virtual ~CriticalSectionWrapper() {};

	virtual void Enter() = 0;
	virtual void Leave() = 0;
};

#endif

这么设计主要是为了实现跨平台的目的,不同的平台,不同的实现,我们只讲linux下的实现,windows下就不考虑了.

CriticalSectionWrapper.cpp文件:

#include "CriticalSectionWrapper.h"
#include "CriticalSectionPosix.h"

CriticalSectionWrapper*  CriticalSectionWrapper::CreateCriticalSection() {
	return new CriticalSectionPosix;
}

具体的实现类,依据各平台来单独实现.
CriticalSectionPosix .h文件:

#ifndef _CRITICAL_SECTION_POSIX_
#define _CRITICAL_SECTION_POSIX_

#include "CriticalSectionWrapper.h"
#include <pthread.h>

class CriticalSectionPosix : public CriticalSectionWrapper {

public:
	CriticalSectionPosix();
	~CriticalSectionPosix();

public:
	void Enter() override;
	void Leave() override;

private:
	pthread_mutex_t mutex_;

};

#endif

CriticalSectionPosix.cpp文件

#include "CriticalSectionPosix.h"

CriticalSectionPosix::CriticalSectionPosix() {
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&mutex_,&attr);
}


CriticalSectionPosix::~CriticalSectionPosix() {
	pthread_mutex_destroy(&mutex_);
}


void CriticalSectionPosix::Enter() {
	pthread_mutex_lock(&mutex_);
}

void CriticalSectionPosix::Leave() {
	pthread_mutex_unlock(&mutex_);
}

比较简单,封装完成.

下面看我们的测试demo:

#include <iostream>
using namespace std;

#include "CriticalSectionWrapper.h"
#include <unistd.h>

void* thread1(void* client) 
{
	cout << "thread1 enter" << endl;
	CriticalSectionWrapper* criti_Set = (CriticalSectionWrapper*)client;
	criti_Set->Enter();
	for (int i = 0 ; i  < 10 ; i++)
	{
		sleep(1);
		cout << "thread1 running....." << endl;
	}

	cout << "thread1 over" << endl;
	criti_Set->Leave();
	return NULL;
}

void* thread2(void* client)
{
	cout << "thread2 enter" << endl;

	CriticalSectionWrapper* criti_Set = (CriticalSectionWrapper*)client;
	criti_Set->Enter();

	for (int i = 0; i < 10; i++)
	{
		sleep(1);
		cout << "thread2 running....." << endl;
	}

	cout << "thread2 over" << endl;

	criti_Set->Leave();

	return NULL;
}

void* thread3(void* client)
{
	cout << "thread3" << endl;
	return NULL;
}



int main(int argc,char **argv) {
	cout << "Hello World !" << endl;

	//线程同步锁:
	CriticalSectionWrapper* criti_Set = CriticalSectionWrapper::CreateCriticalSection();

	//创建线程一:
	pthread_t th1;
	int ret = pthread_create(&th1, NULL, thread1, criti_Set);
	if (ret != 0)
	{
		cout << "Create Thread 1 error!" << endl;
	}
	
	//创建线程二:
	pthread_t th2;
	ret = pthread_create(&th2, NULL, thread2, criti_Set);
	if (ret != 0)
	{
		cout << "Create Thread 2 error!" << endl;
	}

	pthread_t th3;


	//等待线程一执行结束
	void *th1JoinRes;
	pthread_join(th1, (void**)&th1JoinRes);

	//等待线程二执行结束
	void *th2JoinRes;
	pthread_join(th2, (void**)&th2JoinRes);


	return 0;
}

输出结果如下:

thread1 enter
thread2 enter
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 running…
thread1 over
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 running…
thread2 over

看到打印结果很明显,线程二已经进入了,但是就是要等待线程一把锁释放出来

我们demo的意思很明显了,锁是针对线程的,锁只能被线程一个线程优先获取,另一个线程必须等上一个线程释放完毕后,才能获取锁.

PTHREAD_MUTEX_NORMAL/PTHREAD_MUTEX_ERRORCHECK/PTHREAD_MUTEX_RECURSIVE,都是很对在同一个线程内的操作的说明.

接下来结合demo,我们来进行一一解说 :

PTHREAD_MUTEX_NORMAL:

void* thread3(void* client)
{
	cout << "thread3" << endl;


	//互斥锁初始化
	pthread_mutex_t mutex_;

	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
	pthread_mutex_init(&mutex_, &attr);


	//第一次锁定:
	int ret = pthread_mutex_lock(&mutex_);
	cout << ret << endl;

	//尝试第二次锁定
	ret = pthread_mutex_lock(&mutex_); //这个时候回导致死锁
	cout << ret << endl;

	//解锁
	pthread_mutex_unlock(&mutex_);

	return NULL;
}

这个时候会导致死锁:
在这里插入图片描述

2.如果我们把
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
这个互斥锁的属性改为:
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);

这个时候,就不会死锁,而是返回错误码了。

在看改为PTHREAD_MUTEX_ERRORCHECK的截图:
在这里插入图片描述

这个时候就是返回错误码35了.

改为:PTHREAD_MUTEX_RECURSIVE就是正常了,只要引用计数器计算的准确就可以了.

一般我们推荐使用:PTHREAD_MUTEX_RECURSIVE,因为很多情况下:

比如某个线程A需要等待条件成立.

A先获取锁,然后判断条件不成立,A释放锁给另一个线程B去执行操作,使条件成立.

A释放锁后,暂停一下,保证B获取到锁,在B获取锁后,我们需要让A再次尝试去获取互斥锁,因为这个时候互斥锁已经被线程B获取了,所以A会等待。

但是在线程A中,其实已经2次在获取互斥锁了。

所以:PTHREAD_MUTEX_RECURSIVE的运用场景会更多.

下一篇我们来讲解下广播和通知的消息。

猜你喜欢

转载自blog.csdn.net/zhangkai19890929/article/details/85107046