Linux 自旋鎖,互斥量(互斥鎖),讀寫鎖


自旋鎖(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; // 直接賦值方式初始化讀寫鎖

讀、寫模式獲得鎖,解鎖:

#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)條件版本不會阻塞線程。

讀寫鎖屬性

attr = NULL,表示使用默認的讀寫鎖屬性:PTHREAD_PROCESS_PRIVATE,表示只在單個進程內的不同線程間共享。另外,還支持屬性PTHREAD_PROCESS_SHARED,表示讀寫鎖將在不同進程間共享。

要設置非默認屬性,就要使用下面2個函數初始化、銷毀讀寫鎖屬性。

#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destory(pthread_rwlockattr_t *attr);

獲取、設置非默認屬性:

#include <pthread.h>

int pthread_rwlockattr_getshared(pthread_rwlockattr_t *attr, int *valptr);
int pthread_rwlockattr_setshared00(pthread_rwlockattr_t *attr, int value);

要設置的當前值value,其值只能是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM