什么是临界区
在多线程编程中,多个线程可能同时访问同一块共享资源,比如一个全局变量或者一段内存区域。当这些线程对资源进行读写操作时,如果没有适当的控制,就可能出现数据错乱。这种需要被保护、不能被多个线程同时访问的代码段或资源区域,就叫做“临界区”。
举个生活中的例子:想象办公室里只有一台打印机,三个人同时要打印合同。如果没人排队协调,文件可能会混在一起,页码错乱。临界区的作用,就是给这台打印机加个“使用中”的标识,确保一次只有一个人能用。
为什么需要线程同步
假设两个线程同时对一个计数器执行自增操作(i++),表面上看只是加1,但背后其实包含读取、修改、写回三个步骤。如果线程A读取了值,还没来得及写回,线程B也读取了同样的旧值,最后结果就会少算一次。
这种问题在高并发场景下特别常见,比如电商抢购库存扣减、银行账户转账等。如果不处理好临界区,轻则数据不准,重则系统崩溃。
常见的临界区实现方式
Windows API 提供了一种轻量级的同步机制——临界区(Critical Section)。它适用于同一进程内的线程同步,比互斥量更高效。
下面是一个使用临界区保护共享资源的 C++ 示例:
#include <windows.h>
#include <thread>
int shared_counter = 0;
CRITICAL_SECTION cs;
void worker() {
for (int i = 0; i < 10000; ++i) {
EnterCriticalSection(&cs);
++shared_counter;
LeaveCriticalSection(&cs);
}
}
int main() {
InitializeCriticalSection(&cs);
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
DeleteCriticalSection(&cs);
return 0;
}在这个例子中,EnterCriticalSection 和 LeaveCriticalSection 之间的代码就是临界区。每次只有一个线程能进入,其他线程必须等待,从而保证 shared_counter 的操作是安全的。
注意事项
临界区虽然高效,但只能用于同一进程内的线程同步。跨进程或需要超时控制的场景,应该考虑使用互斥量(Mutex)或信号量(Semaphore)。
另外,使用时要避免死锁。比如线程A持有临界区后,又去等待另一个被线程B占用的资源,而线程B也在等A释放,就会卡住。就像两个人互相等着对方先开门,结果谁也不动。
实际开发中,建议尽早初始化临界区,用完及时销毁,并且确保每次进入后都有对应的退出,否则程序可能卡死或崩溃。