自旋鎖(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)條件版本不會阻塞執行緒。