《Linux和windows进程同步与线程同步那些事儿(一)》
一、线程同步
1.1 windows下线程同步
在Windows中,线程同步可以通过多种机制来实现,其中最常见的包括互斥量(mutex
)、事件(event
)、临界区(critical section
)、信号量(semaphore
)和条件变量(condition variable
)等。
1.1.1. 互斥量(Mutex
):
互斥量是最常用的线程同步机制,它可以确保在同一时间只有一个线程可以访问共享资源。
在Windows中,可以使用CreateMutex
函数来创建互斥量。
代码示例1:
#include <windows.h>
#include <iostream>int main() {HANDLE hMutex;// 创建互斥量hMutex = CreateMutex(NULL, // 默认安全属性FALSE, // 初始拥有者为FALSE,表示创建后不立即拥有互斥量L"MyMutex"); // 互斥量名称if (hMutex == NULL) {std::cerr << "CreateMutex error: " << GetLastError() << std::endl;return 1;}// 尝试获取互斥量的所有权DWORD dwWaitResult = WaitForSingleObject(hMutex, // 互斥量的句柄INFINITE); // 无限等待switch (dwWaitResult) {// 线程获得了互斥量的所有权case WAIT_OBJECT_0:try {// 执行线程的任务,访问共享资源}finally {// 释放互斥量if (!ReleaseMutex(hMutex)) {std::cerr << "ReleaseMutex error: " << GetLastError() << std::endl;}}break;// 无法获取互斥量的所有权case WAIT_ABANDONED:return 1;}// 关闭互斥量句柄CloseHandle(hMutex);return 0;
}
代码讲解
- CreateMutex函数用于创建或打开一个互斥量。它的参数包括:
- 安全属性:NULL表示互斥量使用默认的安全描述符。
- 初始拥有者:FALSE表示创建互斥量后当前线程不立即拥有它。
- 互斥量名称:可以是任意字符串,用于标识互斥量。如果为NULL,则创建一个无名互斥量。
-
WaitForSingleObject函数用于请求互斥量的所有权。如果互斥量已被其他线程拥有,调用线程将等待直到它可以获得互斥量的所有权。参数INFINITE表示无限等待。
-
WAIT_OBJECT_0表示成功获取了互斥量的所有权,此时线程可以安全地访问共享资源。
-
ReleaseMutex函数用于释放互斥量的所有权,这样其他等待互斥量的线程可以继续执行。
-
CloseHandle函数用于关闭互斥量的句柄。当不再需要互斥量时,应该关闭它的句柄。
-
WAIT_ABANDONED表示试图获取的互斥量是由其他线程在持有时终止的,这通常意味着共享资源可能处于未知状态。
使用互斥量时,务必确保在访问完共享资源后释放互斥量,避免死锁。如果程序在持有互斥量时异常退出,可能会导致互斥量永远不会被释放,从而阻塞等待该互斥量的其他线程。
代码示例2:
#include <windows.h>
#include <iostream>// 全局互斥量句柄
HANDLE hMutex;// 模拟共享资源
int sharedResource = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 请求互斥量的所有权WaitForSingleObject(hMutex, INFINITE);// 临界区开始// 修改共享资源sharedResource++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedResource to " << sharedResource << std::endl;// 临界区结束// 释放互斥量的所有权ReleaseMutex(hMutex);return 0;
}int main() {// 创建互斥量hMutex = CreateMutex(NULL, FALSE, NULL);if (hMutex == NULL) {std::cerr << "CreateMutex error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 5;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程和互斥量句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}CloseHandle(hMutex);// 输出最终的共享资源值std::cout << "Final value of sharedResource is " << sharedResource << std::endl;return 0;
}
代码解释
- 定义了一个全局互斥量句柄hMutex和一个模拟的共享资源sharedResource。
- ThreadFunction是线程将要执行的函数。它首先尝试获取互斥量,然后进入临界区修改共享资源,并在完成后释放互斥量。
- 在main函数中,创建了一个互斥量和多个线程。每个线程都会执行ThreadFunction。
- 使用WaitForMultipleObjects等待所有线程完成执行。
- 最后,关闭所有线程和互斥量的句柄,并输出共享资源的最终值。
这个示例确保了即使多个线程尝试同时访问和修改sharedResource,互斥量也会保证每次只有一个线程可以进行修改。这样就避免了竞态条件和数据不一致的问题。
1.1.2. 事件(Event):
事件用于线程间的通信和同步,允许线程等待某个特定事件的发生。
在Windows中,可以使用CreateEvent函数来创建事件对象。
#include <windows.h>
#include <iostream>// 全局事件对象句柄
HANDLE hEvent;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 等待事件触发WaitForSingleObject(hEvent, INFINITE);// 临界区开始// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 临界区结束return 0;
}int main() {// 创建手动复位事件对象hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);if (hEvent == NULL) {std::cerr << "CreateEvent error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 模拟触发事件std::cout << "Event is being signaled." << std::endl;SetEvent(hEvent);// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程和事件对象句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}CloseHandle(hEvent);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}
代码解释
- 定义了一个全局事件对象句柄hEvent和一个全局共享资源sharedValue。
- ThreadFunction是线程将要执行的函数。它首先等待事件对象被触发,然后进入临界区修改共享资源,并在完成后释放事件对象。
- 在main函数中,创建了一个手动复位事件对象和多个线程。每个线程都会执行ThreadFunction。
- 使用SetEvent模拟触发事件,使得所有等待的线程可以执行。
- 使用WaitForMultipleObjects等待所有线程完成执行。
- 最后,关闭所有线程和事件对象的句柄,并输出共享资源的最终值。
在这个示例中,事件对象hEvent充当了一个信号,当事件被触发时,所有等待的线程可以修改共享资源sharedValue。通过控制事件的触发时机,可以实现对共享资源的安全访问和修改。
1.1.3. 临界区(Critical Section):
临界区用于保护共享资源,确保在同一时间只有一个线程可以访问。
在Windows中,可以使用InitializeCriticalSection函数来初始化临界区。
#include <windows.h>
#include <iostream>// 全局临界区对象
CRITICAL_SECTION criticalSection;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 进入临界区EnterCriticalSection(&criticalSection);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 离开临界区LeaveCriticalSection(&criticalSection);return 0;
}int main() {// 初始化临界区InitializeCriticalSection(&criticalSection);// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 删除临界区DeleteCriticalSection(&criticalSection);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}
代码解释
- 定义了一个全局临界区对象criticalSection和一个全局共享资源sharedValue。
- ThreadFunction是线程将要执行的函数。它首先进入临界区,然后修改共享资源,并在完成后离开临界区。
- 在main函数中,初始化了临界区,并创建了多个线程。每个线程都会执行ThreadFunction。
- 使用WaitForMultipleObjects等待所有线程完成执行。
- 最后,关闭所有线程的句柄,删除临界区,并输出共享资源的最终值。
在这个示例中,临界区criticalSection充当了一个保护共享资源的锁,确保每次只有一个线程可以进入临界区修改共享资源sharedValue。这样就避免了多个线程同时修改共享资源导致的数据不一致问题。
1.1.4. 信号量(Semaphore):
信号量是一种经典的线程同步机制,它可以用于控制对共享资源的访问。
在Windows中,可以使用CreateSemaphore函数来创建信号量。
#include <windows.h>
#include <iostream>// 全局信号量对象
HANDLE hSemaphore;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 等待信号量WaitForSingleObject(hSemaphore, INFINITE);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 释放信号量ReleaseSemaphore(hSemaphore, 1, NULL);return 0;
}int main() {// 创建信号量hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);if (hSemaphore == NULL) {std::cerr << "CreateSemaphore error: " << GetLastError() << std::endl;return 1;}// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 关闭信号量句柄CloseHandle(hSemaphore);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}
代码解释
- 定义了一个全局信号量对象hSemaphore和一个全局共享资源sharedValue。
- ThreadFunction是线程将要执行的函数。它首先等待信号量,然后修改共享资源,并在完成后释放信号量。
- 在main函数中,创建了一个初始计数为1的信号量,并多个线程。每个线程都会执行ThreadFunction。
- 使用WaitForMultipleObjects等待所有线程完成执行。
- 最后,关闭所有线程和信号量的句柄,并输出共享资源的最终值。
在这个示例中,信号量hSemaphore充当了一个控制访问共享资源的信号,确保每次只有一个线程可以修改共享资源sharedValue。通过控制信号量的释放和等待,可以实现对共享资源的安全访问和修改。
1.1.5. 条件变量(Condition Variable):
条件变量用于线程间的通信和同步,允许线程等待某个特定条件的发生。
在Windows中,可以使用条件变量的概念结合事件对象或互斥量来实现条件变量的功能。
#include <windows.h>
#include <iostream>// 全局事件对象句柄
HANDLE hEvent;// 全局临界区对象
CRITICAL_SECTION criticalSection;// 全局共享资源
int sharedValue = 0;// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam) {// 进入临界区EnterCriticalSection(&criticalSection);// 等待事件触发LeaveCriticalSection(&criticalSection);WaitForSingleObject(hEvent, INFINITE);EnterCriticalSection(&criticalSection);// 修改共享资源sharedValue++;std::cout << "Thread " << GetCurrentThreadId() << " incremented sharedValue to " << sharedValue << std::endl;// 重置事件,以便其他线程等待ResetEvent(hEvent);// 离开临界区LeaveCriticalSection(&criticalSection);return 0;
}int main() {// 初始化事件对象和临界区hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);InitializeCriticalSection(&criticalSection);// 创建线程const int numThreads = 3;HANDLE hThreads[numThreads];for (int i = 0; i < numThreads; ++i) {hThreads[i] = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);if (hThreads[i] == NULL) {std::cerr << "CreateThread error: " << GetLastError() << std::endl;return 1;}}// 触发事件,使得所有等待的线程可以执行SetEvent(hEvent);// 等待所有线程完成WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE);// 关闭线程句柄for (int i = 0; i < numThreads; ++i) {CloseHandle(hThreads[i]);}// 关闭事件对象和删除临界区CloseHandle(hEvent);DeleteCriticalSection(&criticalSection);// 输出最终的共享资源值std::cout << "Final value of sharedValue is " << sharedValue << std::endl;return 0;
}
在这个示例中,我们使用了一个事件对象hEvent和一个临界区criticalSection来模拟条件变量的行为。当事件被触发时,所有等待的线程可以进入临界区修改共享资源sharedValue。通过控制事件的触发时机和临界区的进入和离开,可以实现对共享资源的安全访问和修改。
这些线程同步机制都可以通过Windows提供的API函数来使用。在实际编程中,选择合适的线程同步机制取决于具体的应用场景和需求,以确保线程间的安全访问和协调。