一、线程同步概念:
线程同步是指在多线程编程中,为了保证多个线程之间的数据访问和操作的有序性以及正确性,需要采取一些机制来协调它们的执行。在多线程环境下,由于线程之间是并发执行的,可能会出现竞争条件(Race Condition)等问题,从而导致程序的不稳定性和错误。
案例与猜想:
示例1:
#include <iostream>
#include <windows.h>
int g;
DWORD WINAPI MyThreadProc (LPVOID lp){
for(int i = 0; i < 100000000; i++){
g++;
}
return 0;
}
int main(){
HANDLE h = CreateThread(NULL, 0, MyThreadProc, NULL, 0, 0);
for(int i = 0; i < 100000000; i++){
g++;
}
std::cout << g << std::endl;
CloseHandle(h);
return 0;
}
结果:
这里测试了两个次,当多个线程同时对公共资源进行操作时,会发生错误,该示例结果始终处在
100000000~200000000之间
猜测一:主线程可能先走完,然后输出g,并退出。
示例2:
因此在这里继续添加一个等待线程函数(WaitForSingleObject)等待线程结束试试
发现依旧无法达到预期。
猜测二:线程与线程之间存在同步,使得g在某一个或某一些值时,g在主线程与线程中,只增加一次。
二、解决方法
常见的线程同步机制包括:
1. 互斥锁(Mutex):互斥锁是一种保护共享资源的机制,它确保在任意时刻只有一个线程能够访问被保护的资源。当一个线程获得了互斥锁,其他线程就需要等待锁的释放才能访问资源。
2. 信号量(Semaphore):信号量是一种用于控制同时访问某一资源的线程数目的方法。它可以允许多个线程同时访问资源,但是可以通过信号量的计数来控制同时访问的线程数量。
3. 条件变量(Condition Variable):条件变量用于在某些特定条件下使线程等待或唤醒。它通常与互斥锁一起使用,以实现在满足特定条件时线程的阻塞和唤醒。
4. 读写锁(Read-Write Lock):读写锁允许多个线程同时读取共享资源,但是在写操作时需要互斥锁来保护资源,以避免多个线程同时写入导致数据不一致性。
5. 原子操作(Atomic Operations):原子操作是一种不可分割的操作,能够保证在多线程环境下的执行不会被中断,从而避免竞争条件。
线程同步的目的是确保线程之间的协调和有序执行,以避免数据竞争和其他并发问题。选择合适的线程同步机制取决于具体的应用场景和需求。
1.互斥锁(Mutex):
PS:临界区同样存在计数机制,进入几次临界区,就要退出几次临界区
示例:
#include <iostream>
#include <windows.h>
int g;
CRITICAL_SECTION g_cs;//创建互斥锁
DWORD WINAPI MyThreadProc (LPVOID lp){
for(int i = 0; i < 100000000; i++){
EnterCriticalSection(&g_cs); //进入临界区
g++;
LeaveCriticalSection(&g_cs); //离开临界区
}
return 0;
}
int main(){
InitializeCriticalSection(&g_cs);//初始化互斥锁
HANDLE h = CreateThread(NULL, 0, MyThreadProc, NULL, 0, 0);
for(int i = 0; i < 100000000; i++){
EnterCriticalSection(&g_cs); //进入临界区
g++;
LeaveCriticalSection(&g_cs); //进入临界区
}
WaitForSingleObject(h, INFINITE);
std::cout << g << std::endl;
CloseHandle(h);
DeleteCriticalSection(&g_cs);
return 0;
}
结果:
需要注意的是,过多地使用互斥锁可能会导致性能问题,因为只有一个线程能够执行临界区代码,其他线程需要等待
未完待续……