Linux線程同步必知,常用方法揭秘!
一、為什么要線程同步
在Linux 多線程編程中,線程同步是一個非常重要的問題。如果線程之間沒有正確地同步,就會導致程序出現一些意外的問題,例如:
- 競態條件(Race Condition):多個線程同時修改同一個共享變量,可能會導致不可預測的結果,因為線程的執行順序是不確定的。
- 死鎖(Deadlock):當兩個或多個線程互相等待對方釋放資源時,可能會導致死鎖,這會導致程序無法繼續執行。
- 活鎖(Livelock):當多個線程相互響應對方的動作,而沒有任何進展時,可能會導致活鎖,這也會導致程序無法繼續執行。

- 兩個人在走路時需要相互讓路,兩個人都想讓對方先通過,但最終還是沒有人通過,這就是一種活鎖情況

接下來將介紹互斥鎖、條件變量、信號量、讀寫鎖這幾種線程同步方法,并使用C語言代碼示例說明其使用方法。

二、互斥鎖
互斥鎖是一種用于線程同步的鎖,用于保護共享資源。只有擁有該鎖的線程才能訪問共享資源,其他線程需要等待鎖被釋放后才能繼續執行。
在Linux環境下,我們可以使用pthread庫提供的互斥鎖函數來實現互斥鎖機制。以下是一些常用的互斥鎖函數:
| 函數名 | 描述 |
|---|---|
pthread_mutex_init |
初始化互斥鎖 |
pthread_mutex_lock |
加鎖互斥鎖 |
pthread_mutex_trylock |
嘗試加鎖互斥鎖 |
pthread_mutex_unlock |
解鎖互斥鎖 |
pthread_mutex_destroy |
銷毀互斥鎖 |
初始化互斥鎖
在使用互斥鎖之前,需要先初始化互斥鎖。pthread_mutex_init函數用于初始化一個互斥鎖。函數原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
其中,mutex參數是一個指向pthread_mutex_t結構體的指針,用于指定要初始化的互斥鎖;attr參數是一個指向pthread_mutexattr_t結構體的指針,用于指定互斥鎖的屬性,通常設置為NULL。
以下是一個初始化互斥鎖的例子:
#include <pthread.h>
pthread_mutex_t mutex;
int main()
{
// 初始化互斥鎖
pthread_mutex_init(&mutex, NULL);
// ...
// 銷毀互斥鎖
pthread_mutex_destroy(&mutex);
return 0;
}
加鎖互斥鎖
加鎖互斥鎖用于保證同一時刻只有一個線程能夠訪問共享資源。pthread_mutex_lock函數用于加鎖一個互斥鎖。函數原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
其中,mutex參數是一個指向pthread_mutex_t結構體的指針,用于指定要加鎖的互斥鎖。
以下是一個加鎖互斥鎖的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
{
// 加鎖互斥鎖
pthread_mutex_lock(&mutex);
// 訪問共享資源
// ...
// 解鎖互斥鎖
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
// 初始化互斥鎖
pthread_mutex_init(&mutex, NULL);
// 創建線程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// ...
// 等待線程結束
pthread_join(tid, NULL);
// 銷毀互斥鎖
pthread_mutex_destroy(&mutex);
return 0;
}
嘗試加鎖互斥鎖
嘗試加鎖互斥鎖與加鎖互斥鎖的主要區別在于,如果互斥鎖已經被其他線程鎖定了,嘗試加鎖互斥鎖將不會阻塞當前線程,而是會立即返回一個錯誤代碼。函數原型如下:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
其中,mutex參數是一個指向pthread_mutex_t結構體的指針,用于指定要嘗試加鎖的互斥鎖。
以下是一個嘗試加鎖互斥鎖的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
{
// 嘗試加鎖互斥鎖
int ret = pthread_mutex_trylock(&mutex);
if (ret == 0) {
// 訪問共享資源
// ...
// 解鎖互斥鎖
pthread_mutex_unlock(&mutex);
} else {
// 互斥鎖已經被其他線程鎖定了
// ...
}
return NULL;
}
int main()
{
// 初始化互斥鎖
pthread_mutex_init(&mutex, NULL);
// 創建線程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// ...
// 等待線程結束
pthread_join(tid, NULL);
// 銷毀互斥鎖
pthread_mutex_destroy(&mutex);
return 0;
}
解鎖互斥鎖
解鎖互斥鎖用于釋放已經鎖定的互斥鎖。pthread_mutex_unlock函數用于解鎖一個互斥鎖。函數原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
其中,mutex參數是一個指向pthread_mutex_t結構體的指針,用于指定要解鎖的互斥鎖。
以下是一個解鎖互斥鎖的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
{
// 加鎖互斥鎖
pthread_mutex_lock(&mutex);
// 訪問共享資源
//
// 解鎖互斥鎖
pthread_mutex_unlock(&mutex);
return NULL;
}
銷毀互斥鎖
在不再需要使用互斥鎖時,需要將互斥鎖銷毀。pthread_mutex_destroy函數用于銷毀一個互斥鎖。函數原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
其中,mutex參數是一個指向pthread_mutex_t結構體的指針,用于指定要銷毀的互斥鎖。
以下是一個銷毀互斥鎖的例子:
#include <pthread.h>
pthread_mutex_t mutex;
int main()
{
// 初始化互斥鎖
pthread_mutex_init(&mutex, NULL);
// ...
// 銷毀互斥鎖
pthread_mutex_destroy(&mutex);
return 0;
}
示例程序
下面是一個簡單的示例程序,演示了如何使用互斥鎖來同步兩個線程的訪問。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex;
int shared_data = 0;
void *thread_func(void *arg)
{
int i;
for (i = 0; i < 1000000; i++) {
pthread_mutex_lock(&mutex);
shared_data++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&thread1, NULL, thread_func, NULL);
pthread_create(&thread2, NULL, thread_func, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
printf("Shared data: %d\n", shared_data);
return 0;
}
在這個程序中,thread_func函數是兩個線程執行的函數,它會對shared_data變量進行1000000次加一操作。
為了確保多個線程不會同時訪問shared_data變量,我們使用了一個互斥鎖。當一個線程要訪問shared_data變量時,它會調用pthread_mutex_lock函數來加鎖。如果鎖已經被其他線程持有,那么這個線程就會被阻塞,直到鎖被釋放為止。當線程完成對shared_data變量的操作后,它會調用pthread_mutex_unlock函數來釋放鎖。
在這個程序執行完畢后,我們可以通過打印shared_data變量的值來檢查程序是否正確地同步了兩個線程的訪問。如果程序正確地同步了線程的訪問,那么shared_data變量的值應該是2000000。
?? 在使用互斥鎖時,需要確保每個線程都在必要的時候釋放鎖。如果一個線程忘記釋放鎖,那么其他線程就會被永久地阻塞,程序就會死鎖。另外,過度使用互斥鎖也會降低程序的性能。
??因為加鎖和釋放鎖的過程需要消耗一定的時間和系統資源,所以在設計程序時需要盡可能減少加鎖和釋放鎖的次數。
三、條件變量
條件變量是Linux線程的另一種同步機制。它用于自動阻塞線程,直到某個特定事件發生或某個條件滿足為止,通常情況下,條件變量是和互斥鎖一起搭配使用的。使用條件變量主要包括兩個動作:
-
一個線程等待某個條件滿足而被阻塞;
-
另一個線程中,條件滿足時發出“信號”
初始化條件變量
在使用條件變量之前,需要先對其進行初始化。以下是一個初始化條件變量的示例:
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
等待條件變量
線程可以通過等待條件變量來暫停執行,并在條件變量被喚醒后繼續執行。以下是一個等待條件變量的示例:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread_func(void *arg)
{
// 等待條件變量
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
return NULL;
}
在上面的示例中,線程會在pthread_cond_wait函數處等待條件變量cond。
在等待之前,線程必須先獲取互斥鎖mutex。等待函數會自動釋放互斥鎖,并在條件變量被喚醒后重新獲取互斥鎖。
喚醒等待條件變量的線程
線程可以通過發送信號來喚醒等待條件變量的線程。以下是一個發送信號的示例:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread_func(void *arg)
{
// 等待條件變量
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
// 喚醒等待條件變量的線程
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
在上面的示例中,主線程通過發送信號來喚醒等待條件變量的線程。在發送信號之前,主線程必須先獲取互斥鎖mutex。
廣播喚醒等待條件變量的線程
有時候需要喚醒多個等待條件變量的線程,此時可以使用廣播機制。以下是一個廣播喚醒等待條件變量的線程的示例:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread_func(void *arg)
{
// 等待條件變量
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
// 廣播喚醒等待條件變量的線程
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
在上面的示例中,主線程通過廣播機制來喚醒等待條件變量的線程。在廣播之前,主線程必須先獲取互斥鎖mutex。
等待特定條件的條件變量
有時候需要等待特定條件的條件變量,此時可以在等待函數中加入判斷條件。以下是一個等待特定條件的條件變量的示例:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int condition = 0;
void *thread_func(void *arg)
{
// 等待特定條件的條件變量
pthread_mutex_lock(&mutex);
while (condition == 0) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
// 設置特定條件并喚醒等待條件變量的線程
pthread_mutex_lock(&mutex);
condition = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
在上面的示例中,線程會在while循環中等待特定條件的條件變量cond。在等待之前,線程必須先獲取互斥鎖mutex。
主線程通過設置特定條件并發送信號來喚醒等待條件變量的線程。
銷毀條件變量
在不需要使用條件變量時,需要將其銷毀以釋放資源。以下是一個銷毀條件變量的示例:
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int main()
{
// 銷毀條件變量
pthread_cond_destroy(&cond);
return 0;
}
在上面的示例中,通過調用pthread_cond_destroy函數來銷毀條件變量cond。
示例程序
下面是一個簡單的示例程序,演示了如何使用條件變量來實現線程間的同步。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int shared_data = 0;
void *thread_func1(void *arg)
{
printf("Thread 1 started\n");
pthread_mutex_lock(&mutex);
while (shared_data < 10) {
pthread_cond_wait(&cond, &mutex);
}
printf("Thread 1 read shared_data: %d\n", shared_data);
pthread_mutex_unlock(&mutex);
printf("Thread 1 finished\n");
return NULL;
}
void *thread_func2(void *arg)
{
printf("Thread 2 started\n");
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
shared_data++;
printf("Thread 2 wrote shared_data: %d\n", shared_data);
if (shared_data == 10) {
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
}
printf("Thread 2 finished\n");
return NULL;
}
int main()
{
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_func1, NULL);
pthread_create(&thread2, NULL, thread_func2, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
在這個程序中,我們創建了兩個線程,分別執行thread_func1和thread_func2函數。
thread_func1函數等待shared_data變量的值達到10后再繼續執行,并輸出到控制臺上。
thread_func2函數將shared_data變量的值加1,并在shared_data變量的值等于10時,發送一個信號通知thread_func1函數可以繼續執行。
需要注意的是,我們在thread_func1函數中使用了pthread_cond_wait函數來等待條件變量,而在thread_func2函數中使用了pthread_cond_signal函數來發送條件變量。
當一個線程等待條件變量時,它會釋放掉與條件變量相關的鎖,并進入睡眠狀態。當另一個線程發送條件變量時,它會喚醒等待條件變量的線程,并重新獲取與條件變量相關的鎖。
四、信號量
信號量是一種計數器,用于同步和互斥訪問共享資源。它是一個整數變量,可以使用原子操作來訪問。
當多個線程需要同時訪問共享資源時,它們必須先獲取一個信號量,然后訪問資源,并在訪問完成后釋放信號量。
如果信號量的計數器值為零,則線程會被阻塞,直到有其他線程釋放信號量。
在Linux中,信號量的API是sem_init、sem_wait、sem_post和sem_destroy。
初始化信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_init() 函數用于初始化信號量。它接受三個參數:
sem:指向信號量的指針pshared:指示信號量是進程共享還是線程共享的標志。如果為 0,則信號量被限制在當前進程的線程中;否則,信號量可以被多個進程共享。value:信號量的初始值。如果為 0,則調用線程將等待,直到其他線程釋放信號量。
等待信號量
int sem_wait(sem_t *sem);
sem_wait() 函數用于等待信號量。
如果信號量的值大于 0,則將該值減 1 并返回。否則,調用線程將被阻塞,直到其他線程釋放信號量為止。
釋放信號量
int sem_post(sem_t *sem);
sem_post() 函數用于釋放信號量。它將信號量的值加 1,并通知等待該信號量的線程或進程。
銷毀信號量
int sem_destroy(sem_t *sem);
sem_destroy() 函數用于銷毀信號量。它將釋放信號量使用的資源,并將其重置為未初始化狀態。但是,只有在沒有線程等待信號量時才能銷毀它。
示例程序
下面是一個簡單的示例程序,演示了如何使用信號量來實現生產者消費者模式。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 10
sem_t empty, full;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int buffer[BUFFER_SIZE];
int buffer_index = 0;
void *producer(void *arg)
{
printf("Producer started\n");
for (int i = 0; i < BUFFER_SIZE * 2; i++) {
sem_wait(&empty);
pthread_mutex_lock(&mutex);
buffer[buffer_index] = i;
buffer_index++;
printf("Produced: %d\n", i);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
printf("Producer finished\n");
return NULL;
}
void *consumer(void *arg)
{
printf("Consumer started\n");
for (int i = 0; i < BUFFER_SIZE * 2; i++) {
sem_wait(&full);
pthread_mutex_lock(&mutex);
buffer_index--;
printf("Consumed: %d\n", buffer[buffer_index]);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
printf("Consumer finished\n");
return NULL;
}
int main()
{
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
pthread_t producer_thread, consumer_thread;
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
sem_destroy(&empty);
sem_destroy(&full);
pthread_mutex_destroy(&mutex);
return 0;
}
在這個程序中,我們使用了兩個信號量,一個是empty,用于表示緩沖區中的空閑空間數量,另一個是full,用于表示緩沖區中已經存儲的數據數量。
在生產者線程中,當需要向緩沖區中添加數據時,它會先等待empty信號量,以確保緩沖區中有足夠的空間來存儲數據。
一旦empty信號量的計數器值大于零,生產者線程會使用pthread_mutex_lock來保護緩沖區,然后向緩沖區中添加數據,并發送一個full信號量的信號,以通知消費者線程可以從緩沖區中獲取。
五、讀寫鎖
Linux讀寫鎖(Read-Write Lock)是一種用于多線程并發控制的同步機制,它允許多個線程同時讀取共享資源,但在寫入操作時,只允許一個線程進行,以避免數據競爭和不一致性。
初始化讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
其中rwlock為讀寫鎖指針,attr為讀寫鎖屬性指針。如果attr為NULL,則使用默認屬性。成功返回0,失敗返回錯誤碼。
銷毀讀寫鎖
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
其中rwlock為讀寫鎖指針。成功返回0,失敗返回錯誤碼。
加讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
其中rwlock為讀寫鎖指針。如果當前有寫鎖或正在等待寫鎖,則阻塞等待。成功返回0,失敗返回錯誤碼。
加寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
其中rwlock為讀寫鎖指針。如果當前有讀鎖或寫鎖或正在等待讀鎖或寫鎖,則阻塞等待。成功返回0,失敗返回錯誤碼。
解鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
其中rwlock為讀寫鎖指針。成功返回0,失敗返回錯誤碼。
示例程序
下面是一個簡單的使用讀寫鎖的例子,用于演示讀寫鎖的使用方法。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_rwlock_t rwlock;
int count = 0;
void *write_thread(void *arg)
{
while(1) {
pthread_rwlock_wrlock(&rwlock);
count++;
printf("write thread: count=%d\n", count);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
void *read_thread(void *arg)
{
while(1) {
pthread_rwlock_rdlock(&rwlock);
printf("read thread: count=%d\n", count);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2;
pthread_rwlock_init(&rwlock, NULL);
pthread_create(&tid1, NULL, write_thread, NULL);
pthread_create(&tid2, NULL, read_thread, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
該例子中,我們定義了兩個線程write_thread和read_thread。
其中write_thread對共享變量count進行寫操作,read_thread對共享變量count進行讀操作。
我們使用pthread_rwlock_init函數初始化讀寫鎖,然后使用pthread_rwlock_wrlock函數和pthread_rwlock_rdlock函數對共享變量進行加鎖,保證寫線程和讀線程互斥訪問共享變量。
在加鎖后,線程對共享變量進行操作,然后使用pthread_rwlock_unlock函數進行解鎖。最后,我們使用pthread_rwlock_destroy函數銷毀讀寫鎖。
當我們運行這個程序時,會發現write_thread線程每隔一秒鐘就會增加共享變量count的值,并打印出來。
而read_thread線程每隔一秒鐘就會讀取并打印共享變量count的值。
由于讀寫鎖的存在,這兩個線程可以安全地并發訪問共享變量,避免了數據競爭和不一致性的問題。
小結
了解這些同步機制可以幫助我們寫出高效且正確的多線程應用程序。不同的同步機制適用于不同的情況,選擇適當的同步機制也是非常重要的。

浙公網安備 33010602011771號