Linux应用开发-16-POSIX 互斥锁
信号量也可以实现互斥访问临界资源,区别如下:
| 特性 | sem_t (作为二元信号量) | pthread_mutex_t (专用互斥锁) |
|---|---|---|
| 核心功能 | 计数器 (0 和 1) | 锁 (Locked / Unlocked) |
| 归属权 | 无 (任何线程都能 post) | 有 (必须是上锁的线程才能解锁) |
| 递归上锁 | 导致死锁 | 支持 (使用 RECURSIVE 类型) |
| 错误检查 | 无 (直接死锁) | 支持 (使用 ERRORCHECK 类型) |
| 主要用途 | 进程/线程间同步 (发信号) | 线程间互斥 (保护临界区) |
| 效率,通用 | 相对较重,专用 | 通常更轻量、更快 |
先说明互斥锁的使用
锁的两种方式:静态初始化和动态初始化
静态初始化
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;//默认的互斥锁
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;//递归互斥锁
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;//检错互斥锁
/*
默认的互斥锁,即快速互斥锁。互斥锁被线程 1 持有时,此时互斥锁处于闭锁状态,当线程 2 尝试获取互斥锁,那么线程 2 将会阻塞直至持有互斥锁的线程 1 解锁为止。
递归互斥锁。互斥锁被线程 1 持有时,线程 2 尝试获取互斥锁,将无法获取成功,并且阻塞等待,而如果是线程 1 尝试再次获取
互斥锁时,将获取成功,并且持有互斥锁的次数加 1。检错互斥锁。这是快速互斥锁的非阻塞版本,它会立即返回一个错误代码。
*/
动态初始化
//互斥锁初始化,pthread_mutexattr_t=NULL为默认互斥锁,否则用pthread_mutexattr_t attr互斥锁属性去设置
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t*mutexattr);
//阻塞等待互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//非阻塞,直接返回错误
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
以下说明互斥锁的信号量使用的差别
互斥锁信号量的基本用法:
//互斥锁 (pthread_mutex_t)
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);// 初始化
pthread_mutex_lock(&mutex);// 上锁
//=====临界区======
pthread_mutex_unlock(&mutex);// 解锁
pthread_mutex_destroy(&mutex);// 销毁//二元信号量 (sem_t)
sem_t sem;
sem_init(&sem, 0, 1);// 初始化 (初始值为 1)
sem_wait(&sem);// P操作 (减1, 变0)
//=====临界区======
sem_post(&sem);// V操作 (加1, 变1)
sem_destroy(&sem);// 销毁
互斥锁 (Mutex) —— 严格的“归属权”(解锁失败)
演示互斥锁有归属权。[main] 线程上锁,[thief_thread] 尝试解锁,操作会失败。
互斥锁的测试 (失败)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>// 静态初始化一个标准互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* thief_thread(void* arg) {sleep(1); // 等待主线程先锁上printf("[thief_thread]: 我尝试解开 [main] 线程的锁...\n");// 本线程尝试解锁一个由“主线程”持有的锁int ret = pthread_mutex_unlock(&mutex);if (ret != 0) {// 在标准的 Linux Pthreads 实现中, 这会失败并返回 EPERMprintf("[thief_thread]: 解锁失败!错误: %s。(互斥锁有归属权)\n", strerror(ret));} else {printf("[thief_thread]: 解锁成功了。\n");}return NULL;
}int main() {// [main] 线程上锁pthread_mutex_lock(&mutex);printf("[main]: 我上锁了。\n");pthread_t tid;// [main] 线程创建了 [thief_thread] 线程pthread_create(&tid, NULL, thief_thread, NULL);// 等待 [thief_thread] 线程结束回收pthread_join(tid, NULL);printf("[main]: 我现在自己解锁。\n");pthread_mutex_unlock(&mutex); // [main] 线程自己正常解锁pthread_mutex_destroy(&mutex);printf("[main]: 程序结束。\n");return 0;
}
/*
[main]: 我上锁了。
[thief_thread]: 我尝试解开 [main] 线程的锁...
[thief_thread]: 解锁失败!错误: Operation not permitted。(互斥锁有归属权)
[main]: 我现在自己解锁。
[main]: 程序结束。
*/
信号量 (Semaphore) —— “同步”功能(解锁成功)
演示信号量没有归属权。[notifier_thread] 可以 sem_post(V操作),而 [main] 线程可以 sem_wait(P操作)。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h> sem_t sem; // 全局信号量void* notifier_thread(void* arg) {sleep(1); // 模拟一些工作printf("[notifier_thread]: 我工作完成了,给信号量 +1 (sem_post)...\n");// 信号量没有归属权,任何线程都可以 V 操作sem_post(&sem);printf("[notifier_thread]: 操作成功!我退出了。\n");return NULL;
}int main() {// 初始值为 0,“资源未就绪”sem_init(&sem, 0, 0); pthread_t tid;// [main] 线程创建了 [notifier_thread] 线程pthread_create(&tid, NULL, notifier_thread, NULL);printf("[main]: 我在等待信号量 (sem_wait),等待 [notifier_thread]...\n");// [main] 线程阻塞在这里,等待别人给它发信号sem_wait(&sem);printf("[main]: 我收到信号了![main] 线程继续运行。\n");pthread_join(tid, NULL); // 等待子线程结束sem_destroy(&sem); // 清理信号量printf("[main]: 程序结束。\n");return 0;
}
/*
[main]: 我在等待信号量 (sem_wait),等待 [notifier_thread]...
[notifier_thread]: 我工作完成了,给信号量 +1 (sem_post)...
[notifier_thread]: 操作成功!我退出了。
[main]: 我收到信号了![main] 线程继续运行。
[main]: 程序结束。
*/
信号量 (Semaphore) —— 尝试“递归”加锁(导致死锁)
sem_t(初始值为1)不能被同一个线程连续wait两次,否则会立即死锁。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>int main() {sem_t sem;// 初始化为 1 (作为互斥锁)sem_init(&sem, 0, 1);printf("[main]: 正在尝试第一次 sem_wait()...\n");sem_wait(&sem); // 第 1 次:成功,信号量值变为 0printf("[main]: 第一次 sem_wait() 成功!\n");printf("[main]: 正在尝试第二次 sem_wait()...\n");// 信号量值现在是 0,[main] 线程再次调用 sem_wait()// [main] 线程自己把自己“锁死”了sem_wait(&sem); // 这一行永远不会被执行printf("[main]: 这一行永远不会打印!\n"); sem_post(&sem);sem_destroy(&sem);return 0;
}
/*
[main]: GnuPG: 正在尝试第一次 sem_wait()...
[main]: 第一次 sem_wait() 成功!
[main]: 正在尝试第二次 sem_wait()...
(程序在此处永久挂起,光标卡住不动,直到您按 Ctrl+C 强制终止)
*/
递归互斥锁 (Recursive Mutex) —— “递归”加锁(成功)
pthread_mutex_t(互斥锁)通过设置递归属性,可以安全地被同一个线程连续加锁两次,而不会死锁。
#include <stdio.h>
#include <pthread.h>int main() {pthread_mutex_t mutex;pthread_mutexattr_t attr; // 互斥锁属性// 1. 初始化属性pthread_mutexattr_init(&attr);// 2. 将属性设置为“递归锁”pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);// 3. 使用这个属性初始化锁pthread_mutex_init(&mutex, &attr);// 4. 销毁属性对象 (锁已创建,不再需要)pthread_mutexattr_destroy(&attr);// 5. 开始测试printf("[main]: 正在尝试第一次 lock()...\n");pthread_mutex_lock(&mutex); // 第 1 次:成功,锁计数器 = 1printf("[main]: 第一次 lock() 成功!\n");printf("[main]: 正在尝试第二次 lock()...\n");// 【成功】// 因为这是“递归锁”,内核检查到是同一个线程在加锁// 它不会阻塞,只会将内部计数器增加到 2pthread_mutex_lock(&mutex); printf("[main]: 第二次 lock() 也成功了!(没有死锁)\n");// 必须解锁相同次数printf("[main]: 正在解锁第一次...\n");pthread_mutex_unlock(&mutex); // 计数器减为 1printf("[main]: 正在解锁第二次...\n");pthread_mutex_unlock(&mutex); // 计数器减为 0,锁被真正释放// 6. 销毁锁pthread_mutex_destroy(&mutex);printf("[main]: 程序正常结束。\n");return 0;
}
/*
[main]: 正在尝试第一次 lock()...
[main]: 第一次 lock() 成功!
[main]: 正在尝试第二次 lock()...
[main]: 第二次 lock() 也成功了!(没有死锁)
[main]: GnuPG: 正在解锁第一次...
[main]: 正在解锁第二次...
[main]: 程序正常结束。
*/
