`lock()` 和 `unlock()` 线程同步函数
1) 函数的概念与用途
lock()
和 unlock()
不是特定的标准库函数,而是线程同步原语的一般概念,用于在多线程环境中保护共享资源。在不同的编程环境和库中,这些函数有不同的具体实现(如 POSIX 线程的 pthread_mutex_lock()
或 C++ 的 std::mutex::lock()
)。
可以将 lock()
和 unlock()
想象成"资源门的钥匙":当一个线程需要访问共享资源时,它必须先获取锁(拿到钥匙),使用完资源后释放锁(归还钥匙)。这样可以确保同一时间只有一个线程能访问受保护的资源,防止数据竞争和不一致。
典型应用场景包括:
- 共享数据保护:保护多线程共享的变量、数据结构
- 临界区控制:确保代码关键部分只能由一个线程执行
- 资源访问序列化:对有限资源(如文件、网络连接)的有序访问
- 生产者-消费者模式:协调生产者和消费者线程之间的数据交换
2) 函数的声明与出处
锁的实现因平台和编程语言而异,以下是几种常见实现:
POSIX 线程 (pthread)
#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
C++ 标准库
#include <mutex>std::mutex mtx;
mtx.lock(); // 获取锁
mtx.unlock(); // 释放锁
Windows API
#include <windows.h>CRITICAL_SECTION cs;
EnterCriticalSection(&cs); // 获取锁
LeaveCriticalSection(&cs); // 释放锁
3) 参数详解:互斥锁对象
对于 POSIX 线程的 pthread_mutex_lock/unlock
:
pthread_mutex_t *mutex
- 作用:指向要锁定/解锁的互斥锁对象的指针
- 要求:互斥锁必须已通过
pthread_mutex_init()
初始化 - 注意:不同类型的互斥锁有不同的特性(普通、递归、错误检查等)
4) 返回值:操作状态
对于 POSIX 线程的 pthread_mutex_lock/unlock
:
- 返回值类型:
int
- 返回值含义:
- 成功:返回 0
- 失败:返回错误码(如
EINVAL
无效参数,EDEADLK
死锁等)
5) 实战演示:多种使用场景
示例 1:POSIX 线程保护共享变量
#include <stdio.h>
#include <pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;void* thread_function(void* arg) {for (int i = 0; i < 100000; i++) {// 获取锁pthread_mutex_lock(&mutex);// 临界区开始shared_counter++;// 临界区结束// 释放锁pthread_mutex_unlock(&mutex);}return NULL;
}int main() {pthread_t thread1, thread2;pthread_create(&thread1, NULL, thread_function, NULL);pthread_create(&thread2, NULL, thread_function, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf("Final counter value: %d (expected: 200000)\n", shared_counter);pthread_mutex_destroy(&mutex);return 0;
}
示例 2:C++ 使用 std::mutex
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex mtx;
int shared_value = 0;void increment() {for (int i = 0; i < 100000; i++) {mtx.lock(); // 获取锁shared_value++; // 临界区mtx.unlock(); // 释放锁}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(increment);}for (auto& t : threads) {t.join();}std::cout << "Final value: " << shared_value << " (expected: 1000000)" << std::endl;return 0;
}
示例 3:使用 RAII 避免忘记解锁 (C++)
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex mtx;
int shared_value = 0;void increment() {for (int i = 0; i < 100000; i++) {// 使用 std::lock_guard 自动管理锁生命周期std::lock_guard<std::mutex> lock(mtx);shared_value++; // 临界区// lock 析构时自动释放锁}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; i++) {threads.emplace_back(increment);}for (auto& t : threads) {t.join();}std::cout << "Final value: " << shared_value << " (expected: 1000000)" << std::endl;return 0;
}
6) 编译方式与注意事项
编译 POSIX 线程程序:
gcc -o lock_demo lock_demo.c -lpthread
编译 C++ 程序:
g++ -o lock_demo lock_demo.cpp -std=c++11 -lpthread
关键注意事项:
- 死锁风险:不正确使用锁可能导致死锁(多个线程互相等待对方释放锁)
- 性能影响:过度使用锁会降低程序性能,应尽量减少锁的持有时间
- 锁粒度:选择适当的锁粒度(粗粒度减少开销但降低并发性,细粒度提高并发性但增加开销)
- 异常安全:在 C++ 中使用 RAII 模式(如
std::lock_guard
)确保异常时锁能被正确释放 - 锁类型选择:根据需求选择合适的锁类型(普通锁、递归锁、读写锁等)
7) 执行结果说明
示例 1 输出:
Final counter value: 200000 (expected: 200000)
展示了如何使用互斥锁保护共享计数器,确保两个线程各增加 100,000 次后结果为 200,000。
示例 2 输出:
Final value: 1000000 (expected: 1000000)
显示了 C++ 中使用 std::mutex
保护共享变量,10 个线程各增加 100,000 次后结果为 1,000,000。
示例 3 输出:
Final value: 1000000 (expected: 1000000)
演示了使用 RAII 模式(std::lock_guard
)自动管理锁的生命周期,避免忘记解锁。
8) 总结:锁的工作流程与价值
锁的工作流程可以总结如下:
锁是多线程编程中的核心同步机制,它的价值在于:
- 数据一致性:防止多个线程同时修改共享数据导致的不一致
- 执行有序性:确保临界区代码的原子性执行
- 线程协调:协调多个线程对有限资源的访问
最佳实践建议:
- 最小化临界区:尽量减少锁的持有时间,只保护真正需要同步的代码
- 避免嵌套锁:尽量避免在持有锁时获取其他锁,防止死锁
- 使用RAII模式:在C++中使用
std::lock_guard
或std::unique_lock
自动管理锁 - 锁顺序一致:如果必须使用多个锁,确保所有线程以相同顺序获取它们
- 考虑替代方案:根据场景考虑使用无锁编程、原子操作或其他同步机制
lock()
和 unlock()
是多线程编程的基础工具,正确使用它们对于编写线程安全、高效并发的程序至关重要。掌握锁的原理、使用方法和注意事项,可以帮助开发者避免常见的多线程问题,如数据竞争、死锁和性能瓶颈。