一、同步機制的核心意義
在多線程/多進程編程中,當多個執行流共享資源(如變量、內存、文件)時,可能因操作順序不確定導致數據競爭(Data Race)。同步機制的作用是:
- 保證原子性:確保關鍵代碼段(Critical Section)的獨占訪問。
- 協調執行順序:控制線程/進程的執行順序(如生產者-消費者模型)。
二、鎖(Mutex)
1. 基本概念
- 互斥鎖(Mutex):最簡單的同步工具,確保同一時間只有一個線程訪問共享資源。
- 特性:鎖被占用時,其他嘗試獲取鎖的線程會被阻塞,直到鎖被釋放。
2. 使用步驟
- 初始化鎖
- 進入臨界區前加鎖
- 退出臨界區后解鎖
- 銷毀鎖
3. 示例:多線程計數器
#include <pthread.h>
#include <stdio.h>int counter = 0;
pthread_mutex_t lock; // 定義互斥鎖void* increment(void* arg) {for (int i = 0; i < 100000; i++) {pthread_mutex_lock(&lock); // 加鎖counter++;pthread_mutex_unlock(&lock); // 解鎖}return NULL;
}int main() {pthread_t t1, t2;pthread_mutex_init(&lock, NULL); // 初始化鎖pthread_create(&t1, NULL, increment, NULL);pthread_create(&t2, NULL, increment, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("Final counter: %d\n", counter); // 正確輸出 200000pthread_mutex_destroy(&lock); // 銷毀鎖return 0;
}
關鍵點:若不加鎖,counter++
(非原子操作)會導致結果錯誤。
三、信號量(Semaphore)
1. 基本概念
- 信號量:一種更通用的同步工具,可以控制多個線程對資源的訪問。
- 類型:
- 二進制信號量:取值0或1,功能類似鎖。
- 計數信號量:允許指定數量的線程同時訪問資源。
2. 使用場景
- 限制資源并發訪問數(如數據庫連接池)。
- 生產者-消費者模型(緩沖區大小控制)。
3. 示例:生產者-消費者模型
#include <semaphore.h>
#include <pthread.h>
#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
sem_t empty, full; // 定義信號量
int in = 0, out = 0;void* producer(void* arg) {for (int i = 0; i < 10; i++) {sem_wait(&empty); // 等待空位(empty-1)buffer[in] = i;in = (in + 1) % BUFFER_SIZE;sem_post(&full); // 通知有數據(full+1)}return NULL;
}void* consumer(void* arg) {for (int i = 0; i < 10; i++) {sem_wait(&full); // 等待數據(full-1)printf("Consumed: %d\n", buffer[out]);out = (out + 1) % BUFFER_SIZE;sem_post(&empty); // 釋放空位(empty+1)}return NULL;
}int main() {sem_init(&empty, 0, BUFFER_SIZE); // 初始空位數量=5sem_init(&full, 0, 0); // 初始數據數量=0pthread_t p, c;pthread_create(&p, NULL, producer, NULL);pthread_create(&c, NULL, consumer, NULL);pthread_join(p, NULL);pthread_join(c, NULL);sem_destroy(&empty);sem_destroy(&full);return 0;
}
關鍵點:通過 empty
和 full
信號量協調生產者和消費者的執行順序。
四、條件變量(Condition Variable)
1. 基本概念
- 條件變量:允許線程在某個條件不滿足時主動阻塞,并在條件滿足時被喚醒。
- 必須與互斥鎖配合使用:確保檢查和修改條件的原子性。
2. 使用場景
- 等待特定條件(如任務隊列非空)。
- 避免忙等待(Busy Waiting),節省CPU資源。
3. 示例:任務隊列調度
#include <pthread.h>
#include <stdbool.h>pthread_mutex_t lock;
pthread_cond_t cond; // 條件變量
bool task_available = false;void* worker(void* arg) {pthread_mutex_lock(&lock);while (!task_available) {pthread_cond_wait(&cond, &lock); // 阻塞并釋放鎖,被喚醒后自動重新加鎖}printf("Processing task...\n");task_available = false;pthread_mutex_unlock(&lock);return NULL;
}void* scheduler(void* arg) {pthread_mutex_lock(&lock);task_available = true;pthread_cond_signal(&cond); // 喚醒等待的線程pthread_mutex_unlock(&lock);return NULL;
}int main() {pthread_t t_worker, t_scheduler;pthread_mutex_init(&lock, NULL);pthread_cond_init(&cond, NULL);pthread_create(&t_worker, NULL, worker, NULL);sleep(1); // 確保worker先進入等待pthread_create(&t_scheduler, NULL, scheduler, NULL);pthread_join(t_worker, NULL);pthread_join(t_scheduler, NULL);pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);return 0;
}
關鍵點:
pthread_cond_wait
會原子性地釋放鎖并阻塞線程,被喚醒后重新獲取鎖。- 必須使用
while
檢查條件(避免虛假喚醒)。
五、三者的對比與選擇
機制 | 用途 | 特點 |
---|---|---|
鎖 | 保護臨界區,確保獨占訪問 | 簡單、輕量,僅支持互斥 |
信號量 | 控制資源訪問數量或協調執行順序 | 靈活,支持計數和復雜同步邏輯 |
條件變量 | 等待特定條件成立 | 需與鎖配合,避免忙等待 |
選擇原則:
- 簡單互斥 → 鎖。
- 控制資源數量 → 信號量。
- 等待條件成立 → 條件變量。
六、深入:常見問題與陷阱
-
死鎖:
- 場景:多個鎖未按順序獲取。
- 解決:統一加鎖順序,或使用超時機制(如
pthread_mutex_trylock
)。
-
優先級反轉:
- 場景:低優先級線程持有高優先級線程需要的鎖。
- 解決:優先級繼承(如
pthread_mutexattr_setprotocol
)。
-
虛假喚醒:
- 場景:
pthread_cond_wait
可能無故返回。 - 解決:始終在
while
循環中檢查條件。
- 場景:
通過合理使用鎖、信號量和條件變量,可以構建高效且安全的并發程序。實際開發中需嚴格測試同步邏輯。