臨界區
2019年5月19日
18:46
多個線程在同時調用函數時可能會產生問題,可能會產生問題的這部分代碼稱之為臨界區(Critical Section)。
根據臨界區是否會產生問題,函數可分為:
- 線程安全函數(Threa-safe function)
- 非線程安全函數(Thread-unsafe function)
線程安全函數被多個線程同時調用也沒有問題,但是非線程安全函數就可能會引發問題。
大多數標准函數都是線程安全函數,我們不需要自己區分線程安全函數與非線程安全函數。因為他們大都數時候都提供了非線程安全函數的對應線程安全版本。
在涉及線程的代碼中,我們可以同在在頭文件聲明前定義_REENTRANT宏來說明調用線程安全函數
也可以通過在編譯時添加-D_REENTRANT選項定義宏
$ gcc -D_REENTRANT mythread.c -o mythread -lpthread
線程存在的問題和臨界區
任何內存空間只要是被線程同時訪問,就有可能發生問題。
為了解決這個臨界區的問題其實很簡單,就是不讓不同線程同時訪問一個變量。而實現這個就是 線程同步。
線程同步可以解決兩方面的情況:
- 不能同時訪問同一內存空間
- 需要指定訪問同一內存空間的線程執行順序
互斥量 Mutual Exclusion
表示不允許多個線程同時訪問,互斥量主要用於結局線程同步的問題。
我們通過互斥量實現互斥鎖,在一個線程在訪問變量時就將他鎖住,而等到訪問完畢再釋放這把鎖。
創建與刪除互斥量
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
->>成功時返回0,失敗時返回其他值
mutex: 互斥量指針
attr:創建互斥量需要指定的屬性,默認值則傳遞NULL
上鎖與解鎖
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
當其他線程調用pthread_mutex_lock函數預備進入臨界區時,如果發現有其他線程已經進入臨界區。將使這個函數阻塞,一直到那個線程調用pthread_mutex_unlock解鎖
pthread_mutex_lock(&mutex);
// 臨界區開始
// ….
//臨界區結束
pthread_mutex_unlock(&mutex);
如果線程退出臨界區使,而沒有調用pthread_mutex_unlock函數,那么其他線程的pthread_mutex_lock函數將一直處於阻塞狀態。這種情況稱之為死鎖。
互斥量lock,unlock函數的頻繁調用使程序的執行效率降低。所以應該對於不同的程序適當的考慮是應該擴大還是縮小臨界區。
信號量 Semaphore
顧名思義。與互斥量的開鎖與解鎖相比。信號量就是給一個信號,看是否復合條件能通過。
信號量的創建和銷毀
#include <semaphore.h>
int sum_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
->>成功時返回0,失敗時返回其他值
sem: 保存信號量的地址
pshared: 傳遞其他值是,創建可有多個進程共享的信號量。傳遞0時,只允許一個進程內部使用。
value:信號量的初始值
相當於互斥量的開鎖與解鎖的函數
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
->>成功時返回0,失敗時返回其他值
sem:保存信號量的地址
傳遞給sem_post函數時增加1
傳遞給sem_wait函數時減少1,當信號量值必須大於等於0
也就是通過調用sem_wait函數時檢查信號量的值是否滿足要求才繼續下去,否則就阻塞。等到其他線程調用sem_post使這個信號量滿足要求。
調用sem_wait函數進入臨界區的線程再調用sem_post函數前不允許其他線程進入臨界區。信號量的值在0和1之間跳轉。具有這種特性的機制被稱為"二進制信號量"。