天天看點

線程的互斥與同步什麼是互斥?什麼是同步?為什麼要同步與互斥?互斥和同步的聯系?線程如何實作互斥線程如何實作同步

什麼是互斥?什麼是同步?

互斥:一個資源一次隻能被一個通路者使用(保證通路資料,唯一通路性)

舉個例子:你去上廁所,門一開,人進去,門鎖上,在你上廁所期間别人不能打擾

同步:當多個通路者一起工作時并對每個通路者通路的時序有一定限制(保證通路資料,時序通路性)

舉個例子:你上完廁所,剛出來,本來應該由别人了,但是你此時又進去,你反複的進去出來,導緻别人都上不了廁所。是以同步就是保證時序性的,你出來了,應該去後面排隊。

為什麼要同步與互斥?

1.在運作多個任務,都需要通路同一種資源-----------競争

2.多個任務之間有依賴關系,某個任務的運作狀态依賴于另一個任務

同步互斥就是解決這類問題的。

互斥和同步的聯系?

同步是一種更為複雜的互斥,而互斥是一種特殊的同步。

也就是說互斥是兩個任務之間不可以同時運作,他們會互相排斥,必須等待一個執行完再執行,而同步也是不能同時運作,但是必須要按照某種次序來運作。但是互斥不限制任務的執行順序。(互斥任務是無序的,同步任務是有序的)

線程如何實作互斥

互斥量 :是一個可以處于兩态之一的變量,解鎖和加鎖。用來保護臨界資源的。當你通路臨界資源的時候,先要申請鎖,如果别人還沒釋放你就被阻塞,如果沒人用你就可以加鎖,這樣就很有效的保證臨界資源一段時間内隻能被一個人通路。

互斥量接口如下:

線程的互斥與同步什麼是互斥?什麼是同步?為什麼要同步與互斥?互斥和同步的聯系?線程如何實作互斥線程如何實作同步

互斥量使用注意事項:

 1.初始化互斥量(程式起始)

2.加鎖操作(進入臨界區之前)

3.解鎖操作(出臨界區之後)注意:你加鎖了,在有可能退出的地方都要解鎖,否則會出現死鎖問題

4.銷毀互斥量(程式結束前)

下面看一個例子,購票ticket--,不是一個原子操作,有可能在sleep的時候被切出去,造成錯誤判斷的問題,是以進入臨界區的時候要進行加鎖。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 100;
pthread_mutex_t mutex;
void * get_ticket(void *arg)
{
	int id = (int)arg;
	while (1)
	{
		pthread_mutex_lock(&mutex);//進入臨界區加鎖
		if (ticket>0)
		{

			usleep(100);
			ticket--;
			printf("thread :%d get a ticket :%d\n", id, ticket);
			pthread_mutex_unlock(&mutex);//解鎖
		}
		else
		{

			pthread_mutex_unlock(&mutex);//在有可能退出的地方,都要解鎖,否則會造成死鎖不能退出
			pthread_exit(NULL);

		}
	}
}
int main()
{
	pthread_t tid[4];
	pthread_mutex_init(&mutex, NULL);//初始化互斥鎖
	int i, ret;
	for (i = 0; i<4; i++)
	{
		ret = pthread_create(&tid[i], NULL, get_ticket, (void*)i);
		if (ret != 0)
		{
			printf("pthread_creat error!!\n");
			return -1;
		}

	}

	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	pthread_join(tid[2], NULL);
	pthread_join(tid[3], NULL);
	pthread_mutex_destroy(&mutex);//銷毀互斥鎖
	return 0;
}
           

線程如何實作同步

1.條件變量:是用來等待某一條件的發生

主要包括以下兩個動作:

等待“條件變量的條件成立”而挂起

使“條件成立”(給出條件成立信号)

為了防止競争,條件變量一般和互斥量搭配使用

怎樣了解:1.條件變量也應當受到保護是以要使用互斥鎖(線程在改變條件變量狀态之前先上鎖)2.如果隻使用互斥鎖,其他線程到這個程式上來就會判斷是否上鎖,如果上鎖了就等待,這個時候可能會有很多線程都來判斷等待,然後阻塞在這裡,當調用鎖的線程釋放鎖後,那麼被阻塞的線程又會來搶奪這個資源。而加上條件變量,條件不滿足可以讓阻塞的線程等待在條件變量上,條件滿足會喚醒等待在條件變量上的線程,進而彌補了互斥量的不足。(總結條件變量與互斥量一起使用,允許線程以無競争的方式等待條件的發生)

條件變量相關接口:

線程的互斥與同步什麼是互斥?什麼是同步?為什麼要同步與互斥?互斥和同步的聯系?線程如何實作互斥線程如何實作同步

生産者消費者模型的實作:

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

int goods = 0;
pthread_cond_t full;//生産
pthread_cond_t empty;//消費
pthread_mutex_t mutex;

void *consumer(void *arg)
{
    int id = (int)arg;
    //我是一個消費者
    while(1) {
        pthread_mutex_lock(&mutex);
        while(goods== 0) {
            pthread_cond_wait(&empty, &mutex);//pthread_con_wait幹兩件事:原子操作 1. 釋放鎖 2.等待
        }
        usleep(100000);
        goods= 0;
        printf("consumer %d get an apple!!\n", id);
        pthread_cond_signal(&full);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
void *producer(void *arg)
{
    int id = (int)arg;
    while(1) {
        pthread_mutex_lock(&mutex);
        while(goods== 1) {//思考:此處為什麼不是if判斷,而是while   原因:因為在signal喚醒wait之間,有另一個生産者可能已經生産了,是以此時應繼續判斷
            pthread_cond_wait(&full, &mutex);
        }
        goods = 1;
        printf("producer %d put an apple!!\n", id);
        //喚醒等待在cond上的線程
        pthread_cond_signal(&empty);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main()
{
    pthread_t tid;
    int ret, i;

    pthread_cond_init(&full, NULL);
    pthread_cond_init(&empty, NULL);
    pthread_mutex_init(&mutex, NULL);
    for (i = 0; i < 4; i++) {
        ret = pthread_create(&tid, NULL, producer, (void*)i);
    }
    for (i = 0; i < 4; i++) {
        ret = pthread_create(&tid, NULL, consumer, (void*)i);
    }

    pthread_join(tid, NULL);
    pthread_cond_destroy(&empty);
    pthread_cond_destroy(&full);
    pthread_mutex_destroy(&mutex);
    return 0;
}
           

2.POSIX信号量:有等待隊列的計數器(記錄臨界資源的個數)

目的:為了同步操作,達到無沖突的通路共享資源的目的POSIX

POSIX信号量接口如下:

線程的互斥與同步什麼是互斥?什麼是同步?為什麼要同步與互斥?互斥和同步的聯系?線程如何實作互斥線程如何實作同步

生産者消費者代碼如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
sem_t producer, consumer;
int noodles = 1;
void* thr_producer(void* arg)
{
	while (1)
	{
		sem_wait(&producer);
		printf("producer put a noodles!!!\n");
		noodles=1;
		sleep(1);
		sem_post(&consumer);

	}
	return NULL;
}

void* thr_consumer(void* arg)
{
	while (1)
	{
		sem_wait(&consumer);
		printf("consumer get a noodles!!!\n");
		noodles=0;
		sem_post(&producer);
	}
	return NULL;
}
	int main()
	{
		int ret;
		pthread_t tid1, tid2;
		ret = sem_init(&producer, 0, 0);
		ret = sem_init(&consumer, 0, 1);
		if (ret<0)
		{
			printf("sem init error!!\n");
			return -1;

		}
		ret == pthread_create(&tid1, NULL, thr_producer, NULL);
		if (ret != 0)
		{
			printf("pthread1 create error!!\n");
			return -1;

		}

		ret == pthread_create(&tid2, NULL, thr_consumer, NULL);
		if (ret != 0)
		{
			printf("pthread2 create error!!\n");
			return -1;

		}
		pthread_join(tid1, NULL);
		pthread_join(tid2, NULL);
		sem_destroy(&producer);
		sem_destroy(&consumer);
		return 0;
	}
           

(3)讀寫鎖

在一些多線程場景下,有些公共的資料修改的機會比較少,相比較修改,我們讀的機會反而更高,為這段代碼加鎖,會極大的降低我們程式的效率,而讀寫鎖就是專門來處理這種問題的。

讀寫鎖特點:寫獨占,讀共享

讀寫鎖其實也是一種自旋鎖,擷取不到鎖,我不挂起,頻頻回頭,一直嘗試加鎖(自旋鎖應用于你确定加鎖不會耗費很長的時間)

讀寫鎖的接口如下:

線程的互斥與同步什麼是互斥?什麼是同步?為什麼要同步與互斥?互斥和同步的聯系?線程如何實作互斥線程如何實作同步

讀寫者模型:三種關系,兩種角色,一個交易場所  (我們考慮讀者優先)

                     讀者與讀者 :  共享(也可以說沒啥關系) 

                     讀者與寫者 : 互斥與同步(讀者寫着不能同時進行,一旦有寫者,則後續讀者将會等待,喚醒時優先考慮讀者)

                     寫者與寫者 :互斥

代碼實作如下:

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
	char* ptr = "i am blockboard!!!";
	pthread_rwlock_t rwlock;
	void* thr_write(void * arg)
	{
		while (1)
		{
			pthread_rwlock_wrlock(&rwlock);
			printf("draw ....\n");
			sleep(1);
			pthread_rwlock_wrlock(&rwlock);
			usleep(1000);
		}
		return NULL;
	}
	void* thr_read(void *arg)
	{
		int id = (int)arg;
		while (1)
		{
			pthread_rwlock_rdlock(&rwlock);
			printf("i am:%d public~~,[%s] is beautiful!!\n", id, ptr);
			sleep(1);
			printf("wake up%d!!\n", id);
			pthread_rwlock_unlock(&rwlock);
			usleep(1000);
		}
		return NULL;

	}
	int main()
	{
		pthread_t tid1, tid2, tid3;
		int ret;
		pthread_rwlock_init(&rwlock, NULL);
		ret = pthread_create(&tid1, NULL, thr_write, (void*)1);
		if (ret != 0)
		{
			printf("pthread create error\n");
			return -1;
		}

		ret = pthread_create(&tid3, NULL, thr_read, (void*)3);
		if (ret != 0)
		{
			printf("pthread create error\n");
			return -1;
		}

		ret = pthread_create(&tid2, NULL, thr_read, (void*)2);
		if (ret != 0)
		{
			printf("pthread create error\n");
			return -1;
		}
		pthread_join(tid1, NULL);
		pthread_join(tid2, NULL);
		pthread_join(tid3, NULL);
		pthread_rwlock_destroy(&rwlock);
		return 0;
	}

           

繼續閱讀