Linux多線程之線程同步

  線程最大的特色就是資源的共享性,因此也就有了一個難點線程同步,實現線程同步的方法最經常使用的方法是:互斥鎖,條件變量和信號量。接下來就讓咱們來看下這幾種同步的方法。
ide

1、互斥鎖(Mutex)函數

  得到鎖的線程能夠完成「讀-修改-寫」的操做,而後釋放鎖給其它線程,沒有得到鎖的線程只能等待而不能訪問共享數據,這樣「讀-修改-寫」三步操做組成一個原子操做,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上並行作這個操做。post

一、鎖的初始化和銷燬(Mutex用pthread_mutex_t類型的變量表示)spa

wKioL1nmpo_Swv8XAABm7CySx6g673.png

  注意:若是Mutex變量是靜態分配的(全局變量 或static變量),也能夠用宏PTHREAD_MUTEX_INITIALIZER來初始化,至關於用pthread_mutex_init初始化而且attr參數爲NULL。用函數初始化則是動態分配。
線程

二、加鎖解鎖blog

wKioL1nmp7GwBHHHAABTcKT1dPo031.png

  一個線程能夠調用pthread_mutex_lock得到Mutex,若是這時另外一個線程已經調用pthread_mutex_lock得到了該Mutex,則當前線程須要掛起等待,直到另外一個線程調用
pthread_mutex_unlock釋放Mutex,當前線程被喚醒,才能得到該Mutex並繼續執行。若是一個線程既想得到鎖,又不想掛起等待,能夠調用pthread_mutex_trylock,若是Mutex已經被另外一個線程得到,這個函數會失敗返回EBUSY,而不會使線程掛起等待。
隊列

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define LOOP 5000
static int count = 0;
pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
void *read_write(void *val)
{
	int _val = 0;
	int i = 0;
	for( ; i < LOOP; i++ ){
		pthread_mutex_lock(&mutex_lock);
		_val = count;
		printf("pthread id is :%x,count : %d\n",
				(unsigned long)pthread_self(),count);
		count = _val+1;
		pthread_mutex_unlock(&mutex_lock);
	}
}
int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1,NULL,read_write,NULL);//爲了簡單沒有判斷是否建立成功
	pthread_create(&tid2,NULL,read_write,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	printf("count final value is : %d\n",count);
	return 0;
}

若是沒有加入互斥鎖,運行結果不會是10000。進程

三、互斥鎖的缺點內存

  死鎖的狀況:1)通常狀況下,若是同一個線程前後兩次調用lock,在第二次調用時,因爲鎖已經被佔用,該線程會掛起等待別的線程釋放鎖,然而鎖正是被本身佔用着的,該線程又被掛起而沒有機會釋放鎖,所以就永遠處於掛起等待狀態了。2)線程A得到了鎖1,線程B得到了鎖2,這時線程A調用lock試圖得到鎖2,結果是須要掛起等待線程B釋放鎖2,而這時線程B也調用lock試圖得到鎖1,結果是須要掛起等待線程A釋放鎖1,因而線程A和B都永遠處於掛起狀態了。資源

  解決辦法:首先要儘可能避免同時得到多個鎖。若是全部線程在須要多個鎖時都按相同的前後順序(常見的是按Mutex變量的地址順序)得到鎖,則不會出現死鎖。

2、條件變量

  互斥鎖不一樣,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊狀況發生爲止。一般條件變量和互斥鎖同時使用。條件變量分爲兩部分: 條件和變量。條件自己是由互斥量保護的。線程在改變條件狀態前先要鎖住互斥量。條件變量使咱們能夠睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動做:一個線程等待"條件變量的條件成立"而掛起;另外一個線程使"條件成立"(給出條件成立信號)。條件的檢測是在互斥鎖的保護下進行的。若是一個條件爲假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。若是另外一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,從新得到互斥鎖,從新評價條件。若是兩進程共享可讀寫的內存,條件變量能夠被用來實現這兩進程間的線程同步。  

一、條件變量的初始化和銷燬

wKiom1nms0jBDzejAABgwD25S88063.png

  條件變量初始化和互斥鎖初始化相似,宏表示靜態分配,函數表示動態分配,當函數的第二個參數爲NULL則二者等價。

二、等待條件成立和激活條件變量

wKiom1nmtcWTdwg6AAByOUDxKcs887.png

wKioL1nmskCzx6YlAAA7Ziy_vK0848.png

  可見,一個條件變量老是和一個Mutex搭配使用的。 一個線程能夠調用pthread_cond_wait在一個條件變量上阻塞等待,這個函數作如下三步操做:
1)釋放Mutex
2)阻塞等待
3)當被喚醒時,從新得到Mutex並返回
  pthread_cond_timedwait函數還有一個額外的參數能夠設定等待超時,若是到達了abstime所指定的時刻仍然沒有別的線程來喚醒當前線程,就返回ETIMEDOUT。一個線程能夠調用pthread_cond_signal喚醒在某個條件變量上等待的另外一個線程,也能夠調用pthread_cond_broadcast喚醒在這個條件變量上等待的全部線程。

  下面寫一個生產者和消費者的實現(鏈表):

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
typedef struct list{
	struct list *next;
	int val;
}product_list;
product_list *head = NULL;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;
/*init list*/
void init_list(product_list *list)
{
	if(NULL != list){
		list->next = NULL;
		list->val = 0;
	}
}
/*consumer*/
void *consumer(void *_val)
{
	product_list *p = NULL;
	while(1){
		pthread_mutex_lock(&lock);
		while(NULL == head){
			pthread_cond_wait(&need_product,&lock);
		}
		p = head;
		head = head->next;
		p->next = NULL;
		pthread_mutex_unlock(&lock);
		printf("consum success,val is : %d\n",p->val);
		free(p);
		p = NULL;
	}
}
/*product*/
void *product(void *_val)
{
	while(1){
		sleep(rand()%2);
		product_list *p = (product_list*)malloc(sizeof(product_list));
		pthread_mutex_lock(&lock);
		init_list(p);
		p->val = rand()%1000;
		p->next = NULL;
		head = p;
		pthread_mutex_unlock(&lock);
        printf("product success,val : %d\n",p->val);
		pthread_cond_signal(&need_product);
	}
}
int main()
{
	pthread_t t_product;
	pthread_t t_consumer;
	pthread_create(&t_product,NULL,product,NULL);//no check
        pthread_create(&t_consumer,NULL,consumer,NULL);//no check

	pthread_join(t_product,NULL);//wait product thread
	pthread_join(t_consumer,NULL);//wait consumer thread
	return 0;
}


wKioL1nmvBqzoRzKAABvD0kOj9U458.png

3、信號量

  Mutex變量是非0即1的,可看做一種資源的可用數量,初始化時Mutex是1,表示有一個可用資源,加鎖時得到該資源,將Mutex減到0,表示再也不有可用資源,解鎖時釋放該資源,將Mutex從新加到1,表示又有了一個可用資源。
  信號量(Semaphore)和Mutex相似,表示可用資源的數量,和Mutex不一樣的是這個數量能夠大於1。即,若是信號量描述的資源數目是1時,此時的信號量和互斥鎖相同!POSIX semaphore庫函數,這種信號量不只可用於同一進程的線程間同步,也可用於不一樣進程間的同步。

一、信號量的建立和銷燬

int sem_init(sem_t *sem , int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

二、信號量的等待和釋放

int sem_wait(sem_t *sem);//阻塞等待
int sem_trywait(sem_t *sem);//非阻塞等待

  調用sem_wait()能夠得到資源(P操做),使semaphore的值減1,若是調用sem_wait()時semaphore的值已是0,則掛起等待。若是不但願掛起等待,能夠調用sem_trywait() 。調用sem_post() 能夠釋放資源(V操做),使semaphore 的值加1,同時喚醒掛起等待的線程。

  下面寫一個生產者和消費者的實現(固定大小循環隊列):

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>

#define SEM_PRO 10
#define SEM_COM 0
/*定義信號量*/
sem_t sem_product;
sem_t sem_consume;
int bank[SEM_PRO];
/*消費者線程執行函數*/
void *consumer(void *_val)
{
	int c = 0;
	while(1){
		sem_wait(&sem_consume);
		int _consume = bank[c];
		printf("consumer done...,val : %d\n",_consume);
		sem_post(&sem_product);
		c = (c+1)%SEM_PRO;
		sleep(rand()%5);
	}
}
/*生產者線程執行函數*/
void *producter(void *_val)
{
	int p = 0;
	while(1){
		sem_wait(&sem_product);
		int _product = rand()%100;
		bank[p] = _product;
		printf("product done... val is : %d\n",_product);
		sem_post(&sem_consume);
		p = (p+1)%SEM_PRO;
		sleep(rand()%3);
	}
}
/*建立生產者消費者線程*/
void run_product_consume()
{
	pthread_t tid_consumer;
	pthread_t tid_producter;

	pthread_create(&tid_consumer,NULL,consumer,NULL);
	pthread_create(&tid_producter,NULL,producter,NULL);
    
	pthread_join(tid_consumer,NULL);
	pthread_join(tid_producter,NULL);
}
/*銷燬信號量*/
void destroy_all_sem()
{
	printf("process done...\n");
	sem_destroy(&sem_product);
	sem_destroy(&sem_consume);
	exit(0);
}
/*初始化信號量*/
void init_all_sem()
{
	int bank[SEM_PRO];
	int num = sizeof(bank)/sizeof(bank[0]);
	int i = 0;
	for(; i < num; i++ ){
		bank[i] = 0;
	}
	sem_init(&sem_product,0,SEM_PRO);
	sem_init(&sem_consume,0,SEM_COM);
}

int main()
{
	init_all_sem();
	run_product_consume();
//	destroy_all_sem();//避免銷燬信號量
	return 0;
}


wKioL1nmyyTDE2GvAABrfUyaGA8668.png

相關文章
相關標籤/搜索