自旋鎖(Spin Lock)
自旋鎖類似於互斥量,不過自旋鎖不是通過休眠阻塞程序,而是在取得鎖之前一直處於忙等待的阻塞狀態。這個忙等的阻塞狀態,也叫做自旋。
自旋鎖通常作為底層原語實現其他型別的鎖。
適用場景:
1)鎖被持有的時間短,而且執行緒不希望在重新排程上花費太多的成本;
2)在非搶佔式核心中,會阻塞中斷,這樣中斷處理程式不會讓系統陷入死鎖狀態。因為中斷處理程式無法休眠,只能使用這種鎖;
缺點:
1)執行緒自旋等待鎖變成可用時,CPU不能做其他事情,會浪費CPU資源;
虛擬碼
S = 1
執行緒P:
// 進入區
while (S <= 0) ; // 自旋
S--; // P操作
... // 臨界區
// 退出區
S++; // V操作
自旋鎖介面
自旋鎖介面與互斥量類似,容易相互替換。
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
注意:
1)如果自旋鎖當前在解鎖狀態,pthread_spin_lock不用自旋,就可以對它加鎖;
2)如果自旋鎖當前在加鎖狀態,再獲得鎖的結果是未定義的。如果呼叫pthread_spin_lock,會返回EDEADLK錯誤或其他錯誤,或者呼叫者可能會永久自旋。取決於具體實現。
3)試圖對沒有加鎖的自旋鎖解鎖,結果也是未定義的。
示例
自旋鎖使用
#include <pthread.h>
#include <stdio.h>
#define THREAD_NUM 100
pthread_spinlock_t spinlock;
void *thread_main(void *arg)
{
int id = (int)arg;
pthread_spin_lock(&spinlock); // 獲得鎖
printf("thread main %d get the lock begin\n", id);
printf("thread main %d get the lock end\n", id);
pthread_spin_unlock(&spinlock); // 釋放鎖
return NULL;
}
int main()
{
pthread_spin_init(&spinlock, 0); /* PTHREAD_PROCESS_PRIVATE == 0*/
int x = PTHREAD_PROCESS_PRIVATE;
printf("x = %d\n", x);
int i;
pthread_t tids[THREAD_NUM];
for (i = 0; i < THREAD_NUM; i++) {
pthread_create(&tids[i], NULL, thread_main, i); // 建立執行緒
}
for (i = 0; i < THREAD_NUM; i++) {
pthread_join(tids[i], NULL); // 連線執行緒
}
return 0;
}
互斥量(互斥鎖, Mutex)
互斥量(Mutex)通過休眠阻塞程序/執行緒,確保同一時間只有一個執行緒訪問資料。休眠,也就意味著會放棄CPU資源。
加鎖
對互斥量加鎖後,任何其他試圖再次對互斥量加鎖的執行緒,都會被阻塞,直到當前執行緒釋放該互斥鎖。
解鎖
如果阻塞在該互斥鎖上的執行緒有多個,當鎖可用時,所有執行緒都會變成可執行狀態,第一個變為執行的執行緒,就可以對互斥量加鎖,其他執行緒則再次等待鎖而進入休眠。
適用場景
多執行緒或多程序執行環境,需要對臨界區資源進行保護時。
缺點
1)使用不當,任意導致死鎖;
2)無法表示臨界區資源可用數量(由訊號量解決);
介面
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // 函式方式初始化,attr是執行緒屬性
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 直接賦值方式初始化
int pthread_mutex_lock(pthread_mutex_t *mutex); // 加鎖
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 嘗試加鎖,不會阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解鎖
使用示例
#define THREAD_NUM 100
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_main(void *arg)
{
int id = (int)arg;
pthread_mutex_lock(&mutex);
printf("thread main %d get the lock begin\n", id);
printf("thread main %d get the lock end\n", id);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
int i;
pthread_t tids[THREAD_NUM];
for (i = 0; i < THREAD_NUM; i++) {
pthread_create(&tids[i], NULL, thread_main, i);
}
for (i = 0; i < THREAD_NUM; i++) {
pthread_join(tids[i], NULL);
}
return 0;
}
讀寫鎖(Read-Write Lock)
讀寫鎖類似於互斥量,不過讀寫鎖允許更高的並行性。讀寫鎖,也叫共享互斥鎖(shared-exclusive lock)。
當讀寫鎖以讀模式鎖住時,可以說成是以共享模式鎖住的。當以寫模式鎖住時,可以說成是以互斥模式鎖住的。
讀寫鎖與互斥鎖的區別
讀寫鎖與互斥鎖的區別在於:
互斥鎖 要麼是加鎖狀態,要麼是不加鎖狀態,而且一次只有一個執行緒能取得鎖、對其加鎖;
讀寫鎖 可以有3種狀態:讀模式加鎖,寫模式加鎖,不加鎖。一次只有一個執行緒能佔有寫模式的讀寫鎖,不過多個執行緒可以同時佔有讀模式的讀寫鎖。
1)當讀寫鎖是寫加鎖狀態時,在被解鎖前,所有試圖對其加鎖的執行緒都會被阻塞。
2)當讀寫鎖是讀加鎖狀態時,在被解鎖前,所有以讀模式加鎖的執行緒都可以得到訪問權,以寫模式加鎖的執行緒會被阻塞。
簡而言之,讀寫鎖是讀狀態與讀狀態之間共享,與寫狀態之間互斥,寫狀態是與任何狀態互斥。
互斥鎖是隻有加鎖和解鎖狀態,加鎖狀態之間互斥。
適用場景
讀寫鎖非常適合對資料結構進行讀操作的次數 遠大於寫的情況。
使用介面
初始化銷燬:
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; // 直接賦值方式初始化讀寫鎖
attr = NULL,表示使用預設的讀寫鎖屬性。
讀、寫模式獲得鎖,解鎖:
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 讀模式取得鎖
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 讀模式取得鎖的條件版本
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 寫模式取得鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 寫模式取得鎖的條件版本
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 解鎖
注意:
1)不論是處於寫模式,還是讀模式,都可以用pthread_rwlock_unlock解鎖。
2)條件版本不會阻塞執行緒。