# Linux中線程互斥鎖的示例分析
## 1. 線程安全與互斥鎖基礎概念
### 1.1 多線程環境下的共享資源問題
在現代操作系統中,多線程編程已成為提高程序性能的重要手段。然而當多個線程并發訪問共享資源時,會出現**競態條件(Race Condition)**問題:
```c
// 典型的多線程計數器問題
int counter = 0;
void* increment(void* arg) {
for (int i = 0; i < 100000; i++) {
counter++; // 非原子操作
}
return NULL;
}
上述代碼中,counter++
操作實際上包含三個步驟:
1. 從內存讀取counter值到寄存器
2. 寄存器值加1
3. 將結果寫回內存
當兩個線程同時執行這些步驟時,可能導致最終結果小于預期值(如200000次操作可能得到150000)。
互斥鎖(Mutex)是解決這類問題的同步原語,其核心特性包括: - 原子性:鎖的獲取和釋放操作是不可分割的 - 排他性:同一時刻只有一個線程能持有鎖 - 阻塞機制:未獲取鎖的線程會進入等待狀態
Linux中的互斥鎖通過內核提供的futex(快速用戶空間互斥鎖)機制實現,在無競爭情況下完全在用戶空間操作,競爭激烈時才進入內核等待隊列。
#include <pthread.h>
// 初始化互斥鎖
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 銷毀互斥鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 加鎖(阻塞)
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 嘗試加鎖(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 解鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);
通過pthread_mutexattr_t
可以定制鎖行為:
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 設置鎖類型
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 可重入鎖
// 設置進程共享屬性
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // 進程間共享
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
常見鎖類型:
- PTHREAD_MUTEX_NORMAL
:標準鎖,不檢測死鎖
- PTHREAD_MUTEX_ERRORCHECK
:提供錯誤檢查
- PTHREAD_MUTEX_RECURSIVE
:允許同一線程重復加鎖
#include <stdio.h>
#include <pthread.h>
typedef struct {
int balance;
pthread_mutex_t lock;
} BankAccount;
void init_account(BankAccount *acc, int initial_balance) {
acc->balance = initial_balance;
pthread_mutex_init(&acc->lock, NULL);
}
void deposit(BankAccount *acc, int amount) {
pthread_mutex_lock(&acc->lock);
acc->balance += amount;
pthread_mutex_unlock(&acc->lock);
}
int withdraw(BankAccount *acc, int amount) {
pthread_mutex_lock(&acc->lock);
int success = 0;
if (acc->balance >= amount) {
acc->balance -= amount;
success = 1;
}
pthread_mutex_unlock(&acc->lock);
return success;
}
#define BUFFER_SIZE 10
typedef struct {
int buffer[BUFFER_SIZE];
int count;
int in;
int out;
pthread_mutex_t mutex;
pthread_cond_t not_empty;
pthread_cond_t not_full;
} PCBuffer;
void init_buffer(PCBuffer *b) {
b->count = b->in = b->out = 0;
pthread_mutex_init(&b->mutex, NULL);
pthread_cond_init(&b->not_empty, NULL);
pthread_cond_init(&b->not_full, NULL);
}
void produce(PCBuffer *b, int item) {
pthread_mutex_lock(&b->mutex);
while (b->count == BUFFER_SIZE) {
pthread_cond_wait(&b->not_full, &b->mutex);
}
b->buffer[b->in] = item;
b->in = (b->in + 1) % BUFFER_SIZE;
b->count++;
pthread_cond_signal(&b->not_empty);
pthread_mutex_unlock(&b->mutex);
}
int consume(PCBuffer *b) {
pthread_mutex_lock(&b->mutex);
while (b->count == 0) {
pthread_cond_wait(&b->not_empty, &b->mutex);
}
int item = b->buffer[b->out];
b->out = (b->out + 1) % BUFFER_SIZE;
b->count--;
pthread_cond_signal(&b->not_full);
pthread_mutex_unlock(&b->mutex);
return item;
}
鎖的粒度選擇直接影響并發性能:
// 全局單個鎖
pthread_mutex_t global_lock;
void access_all_resources() {
pthread_mutex_lock(&global_lock);
// 操作所有共享資源
pthread_mutex_unlock(&global_lock);
}
// 為每個資源單獨加鎖
typedef struct {
int data;
pthread_mutex_t lock;
} Resource;
Resource res[N];
void access_resource(int i) {
pthread_mutex_lock(&res[i].lock);
// 操作單個資源
pthread_mutex_unlock(&res[i].lock);
}
常見死鎖場景及解決方案:
// 線程1
lock(A);
lock(B);
// 線程2
lock(B);
lock(A); // 可能導致死鎖
解決方案:固定鎖的獲取順序
void foo() {
lock(M);
bar();
unlock(M);
}
void bar() {
lock(M); // 如果不是遞歸鎖會死鎖
// ...
unlock(M);
}
解決方案:使用PTHREAD_MUTEX_RECURSIVE
屬性
線程數 | 無鎖(錯誤) | 全局鎖(ms) | 分段鎖(ms) | 自旋鎖(ms) |
---|---|---|---|---|
2 | 153,291 | 45 | 32 | 28 |
4 | 287,501 | 89 | 47 | 42 |
8 | 512,837 | 175 | 78 | 65 |
注:測試為1000萬次計數器遞增操作
錯誤1:忘記釋放鎖
void critical_section() {
pthread_mutex_lock(&mutex);
if (error_condition) {
return; // 直接返回導致鎖未釋放
}
pthread_mutex_unlock(&mutex);
}
解決方法:使用RI模式或__attribute__((cleanup))
void auto_unlock(pthread_mutex_t **mutex) {
pthread_mutex_unlock(*mutex);
}
void critical_section() {
pthread_mutex_t *mutex __attribute__((cleanup(auto_unlock))) = &the_mutex;
pthread_mutex_lock(&the_mutex);
// ...
}
錯誤2:錯誤使用遞歸鎖
void recursive_func(int n) {
pthread_mutex_lock(&mutex); // 普通鎖重復獲取
if (n > 0) {
recursive_func(n-1);
}
pthread_mutex_unlock(&mutex);
}
適用場景:讀多寫少
#include <pthread.h>
pthread_rwlock_t rwlock;
void reader() {
pthread_rwlock_rdlock(&rwlock);
// 讀取共享數據
pthread_rwlock_unlock(&rwlock);
}
void writer() {
pthread_rwlock_wrlock(&rwlock);
// 修改共享數據
pthread_rwlock_unlock(&rwlock);
}
適用場景:臨界區非常短且不希望線程休眠
pthread_spinlock_t spinlock;
void fast_path() {
pthread_spin_lock(&spinlock);
// 極短的操作
pthread_spin_unlock(&spinlock);
}
if (pthread_mutex_lock(&mutex) != 0) {
perror("pthread_mutex_lock");
// 錯誤處理
}
class LockGuard {
public:
LockGuard(pthread_mutex_t &m) : mutex(m) {
pthread_mutex_lock(&mutex);
}
~LockGuard() {
pthread_mutex_unlock(&mutex);
}
private:
pthread_mutex_t &mutex;
};
helgrind
:檢測數據競爭mutrace
:分析鎖爭用情況/* 完整的多線程安全隊列實現 */
#include <stdlib.h>
#include <pthread.h>
typedef struct {
int *array;
int capacity;
int size;
int front;
int rear;
pthread_mutex_t lock;
pthread_cond_t not_empty;
pthread_cond_t not_full;
} ThreadSafeQueue;
ThreadSafeQueue* queue_create(int capacity) {
ThreadSafeQueue *q = malloc(sizeof(ThreadSafeQueue));
q->array = malloc(sizeof(int) * capacity);
q->capacity = capacity;
q->size = 0;
q->front = 0;
q->rear = -1;
pthread_mutex_init(&q->lock, NULL);
pthread_cond_init(&q->not_empty, NULL);
pthread_cond_init(&q->not_full, NULL);
return q;
}
void queue_enqueue(ThreadSafeQueue *q, int item) {
pthread_mutex_lock(&q->lock);
while (q->size == q->capacity) {
pthread_cond_wait(&q->not_full, &q->lock);
}
q->rear = (q->rear + 1) % q->capacity;
q->array[q->rear] = item;
q->size++;
pthread_cond_signal(&q->not_empty);
pthread_mutex_unlock(&q->lock);
}
int queue_dequeue(ThreadSafeQueue *q) {
pthread_mutex_lock(&q->lock);
while (q->size == 0) {
pthread_cond_wait(&q->not_empty, &q->lock);
}
int item = q->array[q->front];
q->front = (q->front + 1) % q->capacity;
q->size--;
pthread_cond_signal(&q->not_full);
pthread_mutex_unlock(&q->lock);
return item;
}
通過本文的詳細分析和示例,讀者應能全面掌握Linux線程互斥鎖的使用方法和最佳實踐。在實際開發中,應根據具體場景選擇合適的同步策略,并始終注意線程安全和性能的平衡。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。