1. 程式人生 > >Linux C++ 執行緒同步鎖和wait()/broadcast()功能(一)

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的運用場景會更多.

下一篇我們來講解下廣播和通知的訊息。