Linux——線程深度剖析(二)
互斥與同步
-
互斥是指散佈在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它 們之中的任一程序片段,只能等到該進程運行完這個程序片段後纔可以運行。
-
同步是散佈在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的 某種先後次序來運行,這種先後次序依賴於要完成的特定的任務。
臨界資源與臨界區
-
臨界資源:多個線程共享的資源
-
臨界區:多個線程訪問臨界資源的代碼段
線程安全
因爲進程中的線程共享了進程的虛擬地址空間,因此線程間通信將變得更加簡單,但是缺點也隨之而來:缺乏數據的訪問控制容易造成數據混亂(因爲大家都在爭搶訪問公共資源),導致程序結果出現二義性。
舉個例子:
- 在一個進程中有兩個線程,線程A,線程B,兩個線程同時對一個整形的全局變量a=10進程++操作,預期結果a=12;
- 當線程A拿到CPU之後,對全局a進行++操作,但是該操作是非原子性的,當線程A將10從內存中讀取到寄存器中後,時間片到期,此時線程A就被切換出去了,此時a的值在內存中不變。
- 搶佔式執行的前提下,假設線程B在這個時候拿到了CPU資源,完成了++操作且未被打斷,並將11寫回到了內存當中去。
- 這時候,線程A重新擁有資源,根據程序計數器(保存下一條執行的指令),上下文信息(寄存器中的值),恢復現場,此時線程A從寄存器中拿到的值爲10,並完成++操作,將11寫回內存。
結果與我們的初衷完全不一樣,這就是線程完全問題。
如何保證線程安全
- 通過互斥鎖,讓線程之間互斥
互斥鎖
- 在互斥鎖內部有一個計數器,名爲互斥量。計數器的取值只能爲0或爲1。
- 當計數器取值爲0時,表示當前線程無法獲得互斥鎖,無法訪問臨界資源
- 當計數器取值爲1時,表示當前線程可以獲取互斥鎖,可以訪問臨界資源。
互斥鎖中的計數器如何保證原子性?
獲取資源的時候:
1.寄存器中的值賦值爲0
2.將寄存器中的值與內存的值交換
3.判斷寄存器的值,得出結果
在第二步交換的過程中,系統通過xchgb交換操作,該操作是彙編代碼,一次性完成,該操作保證了原子性。
1.互斥鎖的初始化
int pthread_mutex_init(pthread_mutex_t *_mutex,_const pthread_mutex_mutexattr_t* _mutexattr)
- mutex:傳入互斥鎖變量的地址,pthread_mutex_init會初始化互斥變量
- attr:屬性,一般傳遞NULL,採用默認屬性
- pthread_mutex_init:互斥鎖變量的類型
2.加鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
- mutex:傳入互斥鎖變量的地址。
- 如果mutex當中的計數器的值爲1,則pthread_mutex_lock接口就返回了,表示說加鎖成功,同時計數器當中值會被更改爲0,
- 如果mutex當中的計數器的值爲0,則pthread_mutex_lock接口就阻塞了,pthread_mutex_lock接口沒有返回,阻塞在該函數的內部,直到加鎖成功
3.解鎖
int pthread_mutex_unlock(pthread_mutex_t*mutex);
- 不管是哪一個加鎖接口加鎖成功的,都可以使用該接口進行解鎖
- 解鎖的時候,會將互斥鎖變量當中的計數器的值,從0變成1,表示其他線程可以獲取互斥鎖
4.互斥鎖的銷燬
pthread_mutex_destroy (pthread_mutex_t*);
實例的運用:黃牛搶票
創建四個線程,每個線程在搶票時候進行加鎖,結束後解鎖。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREADCOUNT 4
int g_tickets = 100;
pthread_mutex_t lock_;
void* MyStartThread(void* arg)
{
(void)arg;
while(1)
{
pthread_mutex_lock(&lock_);
if(g_tickets > 0)
{
printf("i am thread %p, i have ticket %d\n", pthread_self(), g_tickets);
g_tickets--;
}
else
{
pthread_mutex_unlock(&lock_);
break;
}
pthread_mutex_unlock(&lock_);
}
return NULL;
}
int main()
{
pthread_mutex_init(&lock_, NULL);
pthread_t tid[THREADCOUNT];
for(int i = 0; i < THREADCOUNT; i++)
{
int ret = pthread_create(&tid[i], NULL, MyStartThread, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
}
for(int i = 0; i < THREADCOUNT; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&lock_);
return 0;
}
注意:
- 在任何開始訪問臨界資源的地方進行加鎖
- 在所有可能線程退出的地方進行解鎖,否則可能造成死鎖
條件變量(PCB等待隊列)
它可以使用在資源不滿足的情況下處於休眠狀態,讓出CPU資源,而資源狀態滿足時喚醒線程
1.初始化
int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr)
- cond : pthread_cond_t :條件變量的類型,傳參的時候還是傳入條件變量的地址
- attr:條件變量的屬性,通常傳遞NULL,採用默認屬性
2.等待:將調用該接口的線程放到PCB等待隊列當中
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
- cond:條件變量
- mutex:互斥鎖
- pthread_cond_wait會在內部進行解互斥鎖,且順序是:先放入PCB等待隊列,再進行解鎖
- 接口內部實現的邏輯:1.從PCB等待隊列中移除。 2.搶佔互斥鎖 如果沒有搶到互斥鎖,那將會阻塞在pthread_cond_wait的內部搶鎖邏輯當中
3.喚醒
int pthread_cond_siganl(pthread_cond_t* cond)
- 通知PCB等待隊列當中的線程,將其從隊列當中出來,喚醒該線程
- 喚醒至少一個PCB等待隊列當中的線程
int pthread_cond_broadcast (pthread_cond_t*cond)
- 喚醒PCB等待隊列當中全部的線程
4.釋放
int pthread_cond_destroy (pthread_cond_t* cond)
結合條件變量實戰一下消費者模型:消費者吃麪,生產者做面,當碗裏沒面時,將消費真放進pcb等待隊列,做好後有生產者通知其出隊,當碗裏有面的時,將生產者放入pcb等待隊列,當碗裏喫完的時候由消費者通知其做面。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define THREADCOUNT 2
int g_bowl = 0;
pthread_mutex_t g_mut;
pthread_cond_t g_cond;
void* EatStart(void* arg)
{
while(1)
{
//eat
pthread_mutex_lock(&g_mut);
while(g_bowl <= 0)
{
pthread_cond_wait(&g_cond, &g_mut);
}
printf("i am %p, i eat %d\n", pthread_self(), g_bowl);
g_bowl--;
pthread_mutex_unlock(&g_mut);
pthread_cond_signal(&g_cond);
}
return NULL;
}
void* MakeStart(void* arg)
{
while(1)
{
//make
pthread_mutex_lock(&g_mut);
while(g_bowl > 0)
{
pthread_cond_wait(&g_cond, &g_mut);
}
g_bowl++;
printf("i am %p, i make %d\n", pthread_self(), g_bowl);
pthread_mutex_unlock(&g_mut);
pthread_cond_signal(&g_cond);
}
return NULL;
}
int main()
{
pthread_mutex_init(&g_mut, NULL);
pthread_cond_init(&g_cond, NULL);
pthread_t consume[THREADCOUNT], product[THREADCOUNT];
for(int i = 0; i < THREADCOUNT; i++)
{
int ret = pthread_create(&consume[i], NULL, EatStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
ret = pthread_create(&product[i], NULL, MakeStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
}
for(int i = 0; i < THREADCOUNT; i++)
{
pthread_join(consume[i], NULL);
pthread_join(product[i], NULL);
}
pthread_mutex_destroy(&g_mut);
pthread_cond_destroy(&g_cond);
return 0;
}