9.2 运用API实现线程同步

打印 上一主题 下一主题

主题 847|帖子 847|积分 2541

Windows 线程同步是指多个线程一同访问共享资源时,为了避免资源的并发访问导致数据的不一致或程序崩溃等问题,需要对线程的访问进行协同和控制,以保证程序的正确性和稳定性。Windows提供了多种线程同步机制,以适应不同的并发编程场景。主要包括以下几种:

  • 事件(Event):用于不同线程间的信号通知。包括单次通知事件和重复通知事件两种类型。
  • 互斥量(Mutex):用于控制对共享资源的访问,具有独占性,可避免线程之间对共享资源的非法访问。
  • 临界区(CriticalSection):和互斥量类似,也用于控制对共享资源的访问,但是是进程内部的,因此比较适用于同一进程中的线程同步控制。
  • 信号量(Semaphore):用于基于计数器机制,控制并发资源的访问数量。
  • 互锁变量(Interlocked Variable):用于对变量的并发修改操作控制,可提供一定程度的原子性操作保证。
以上同步机制各有优缺点和适用场景,开发者应根据具体应用场景进行选择和使用。在线程同步的实现过程中,需要注意竞争条件和死锁的处理,以确保程序中的线程能协同工作,共享资源能够正确访问和修改。线程同步是并发编程中的重要基础,对于开发高效、稳定的并发应用至关重要。
9.2.1 CreateEvent

CreateEvent 是Windows API提供的用于创建事件对象的函数之一,该函数用于创建一个事件对象,并返回一个表示该事件对象的句柄。可以通过SetEvent函数将该事件对象设置为有信号状态,通过ResetEevent函数将该事件对象设置为无信号状态。当使用WaitForSingleObject或者WaitForMultipleObjects函数等待事件对象时,会阻塞线程直到事件状态被置位。对于手动重置事件,需要调用ResetEvent函数手动将事件状态置位。
CreateEvent 函数常用于线程同步和进程间通信,在不同线程或者进程之间通知事件状态的改变。例如,某个线程完成了一项任务,需要通知其它等待该任务完成的线程;或者某个进程需要和另一个进程进行协调,需要通知其它进程某个事件的发生等等。
CreateEvent 函数的函数原型如下:
  1. HANDLE CreateEvent(
  2.   LPSECURITY_ATTRIBUTES lpEventAttributes,
  3.   BOOL                  bManualReset,
  4.   BOOL                  bInitialState,
  5.   LPCTSTR               lpName
  6. );
复制代码
参数说明:

  • lpEventAttributes:指向SECURITY_ATTRIBUTES结构体的指针,指定事件对象的安全描述符和访问权限。通常设为NULL,表示使用默认值。
  • bManualReset:指定事件对象的类型,TRUE表示创建的是手动重置事件,FALSE表示创建的是自动重置事件。
  • bInitialState:指定事件对象的初始状态,TRUE表示将事件对象设为有信号状态,FALSE表示将事件对象设为无信号状态。
  • lpName:指定事件对象的名称,可以为NULL。
CreateEvent 是实现线程同步和进程通信的重要手段之一,应用广泛且易用。在第一章中我们创建的多线程环境可能会出现线程同步的问题,此时使用Event事件机制即可很好的解决,首先在初始化时通过CreateEvent将事件设置为False状态,进入ThreadFunction线程时再次通过SetEvent释放,以此即可实现线程同步顺序执行的目的。
  1. #include <stdio.h>
  2. #include <process.h>
  3. #include <windows.h>
  4. // 全局资源
  5. long g_nNum = 0;
  6. // 子线程个数
  7. const int THREAD_NUM = 10;
  8. CRITICAL_SECTION  g_csThreadCode;
  9. HANDLE g_hThreadEvent;
  10. unsigned int __stdcall ThreadFunction(void *ptr)
  11. {
  12.   int nThreadNum = *(int *)ptr;
  13.   // 线程函数中触发事件
  14.   SetEvent(g_hThreadEvent);
  15.   // 进入线程锁
  16.   EnterCriticalSection(&g_csThreadCode);
  17.   g_nNum++;
  18.   printf("线程编号: %d --> 全局资源值: %d --> 子线程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());
  19.   // 离开线程锁
  20.   LeaveCriticalSection(&g_csThreadCode);
  21.   return 0;
  22. }
  23. int main(int argc,char * argv[])
  24. {
  25.   unsigned int ThreadCount = 0;
  26.   HANDLE  handle[THREAD_NUM];
  27.   // 初始化自动将事件设置为False
  28.   g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  29.   InitializeCriticalSection(&g_csThreadCode);
  30.   for (int each = 0; each < THREAD_NUM; each++)
  31.   {
  32.     handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
  33.     // 等待线程事件被触发
  34.     WaitForSingleObject(g_hThreadEvent, INFINITE);
  35.   }
  36.   WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  37.   // 销毁事件
  38.   CloseHandle(g_hThreadEvent);
  39.   DeleteCriticalSection(&g_csThreadCode);
  40.   system("pause");
  41.   return 0;
  42. }
复制代码
当然了事件对象同样可以实现更为复杂的同步机制,在如下我们在创建对象时,可以设置non-signaled状态运行的auto-reset模式,当我们设置好我们需要的参数时,可以直接使用SetEvent(hEvent)设置事件状态,则会自动执行线程函数。
要创建一个manual-reset模式并且初始状态为not-signaled的事件对象,需要按照以下步骤:
首先定义一个SECURITY_ATTRIBUTES结构体变量,设置其中的参数为NULL表示使用默认安全描述符,例如。
  1. SECURITY_ATTRIBUTES sa = {0};
  2. sa.nLength = sizeof(sa);
  3. sa.lpSecurityDescriptor = NULL;
  4. sa.bInheritHandle = FALSE;
复制代码
接着调用CreateEvent函数创建事件对象,将bManualReset和bInitialState参数设置为FALSE,表示创建manual-reset模式的事件对象并初始状态为not-signaled。例如:
  1. HANDLE hEvent = CreateEvent(
  2.                       &sa,           // 安全属性
  3.                       TRUE,          // Manual-reset模式
  4.                       FALSE,         // Not-signaled 初始状态
  5.                       NULL           // 事件对象名称
  6.                       );
复制代码
这样,我们就创建了一个名为hEvent的manual-reset模式的事件对象,初始状态为not-signaled。可以通过SetEvent函数将事件对象设置为signaled状态,通过ResetEvent函数将事件对象设置为non-signaled状态,也可以通过WaitForSingleObject或者WaitForMultipleObjects函数等待事件对象的状态变化。
  1. #include <windows.h>  
  2. #include <stdio.h>  
  3. #include <process.h>  
  4. #define STR_LEN 100  
  5. // 存储全局字符串
  6. static char str[STR_LEN];
  7. // 设置事件句柄
  8. static HANDLE hEvent;
  9. // 统计字符串中是否存在A
  10. unsigned WINAPI NumberOfA(void *arg)
  11. {
  12.   int cnt = 0;
  13.   // 等待线程对象事件
  14.   WaitForSingleObject(hEvent, INFINITE);
  15.   for (int i = 0; str[i] != 0; i++)
  16.   {
  17.     if (str[i] == 'A')
  18.       cnt++;
  19.   }
  20.   printf("Num of A: %d \n", cnt);
  21.   return 0;
  22. }
  23. // 统计字符串总长度
  24. unsigned WINAPI NumberOfOthers(void *arg)
  25. {
  26.   int cnt = 0;
  27.   // 等待线程对象事件
  28.   WaitForSingleObject(hEvent, INFINITE);
  29.   for (int i = 0; str[i] != 0; i++)
  30.   {
  31.     if (str[i] != 'A')
  32.       cnt++;
  33.   }
  34.   printf("Num of others: %d \n", cnt - 1);
  35.   return 0;
  36. }
  37. int main(int argc, char *argv[])
  38. {
  39.   HANDLE hThread1, hThread2;
  40.   // 以non-signaled创建manual-reset模式的事件对象
  41.   // 该对象创建后不会被立即执行,只有我们设置状态为Signaled时才会继续
  42.   hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  43.   hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
  44.   hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
  45.   fputs("Input string: ", stdout);
  46.   fgets(str, STR_LEN, stdin);
  47.   // 字符串读入完毕后,将事件句柄改为signaled状态  
  48.   SetEvent(hEvent);
  49.   WaitForSingleObject(hThread1, INFINITE);
  50.   WaitForSingleObject(hThread2, INFINITE);
  51.   // non-signaled 如果不更改,对象继续停留在signaled
  52.   ResetEvent(hEvent);
  53.   CloseHandle(hEvent);
  54.   system("pause");
  55.   return 0;
  56. }
复制代码
9.2.2 CreateSemaphore

CreateSemaphore 是Windows API提供的用于创建信号量的函数之一,用于控制多个线程之间对共享资源的访问数量。该函数常用于创建一个计数信号量对象,并返回一个表示该信号量对象的句柄。可以通过ReleaseSemaphore函数将该信号量对象的计数加1,通过WaitForSingleObject或者WaitForMultipleObjects函数等待信号量对象的计数变成正数以后再将其减1,以实现对共享资源访问数量的控制。
CreateSemaphore 函数常用于实现生产者消费者模型、线程池、任务队列等并发编程场景,用于限制访问共享资源的线程数量。信号量机制更多时候被用于限制资源的数量而不是限制线程的数量,但也可以用来实现一些线程同步场景。
该函数的函数原型如下:
  1. HANDLE CreateSemaphore(
  2.   LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  3.   LONG                  lInitialCount,
  4.   LONG                  lMaximumCount,
  5.   LPCTSTR               lpName
  6. );
复制代码
参数说明:

  • lpSemaphoreAttributes:指向SECURITY_ATTRIBUTES结构体的指针,指定信号量对象的安全描述符和访问权限。通常设为NULL,表示使用默认值。
  • lInitialCount:指定信号量对象的初始计数,表示可以同时访问共享资源的线程数量。
  • lMaximumCount:指定信号量对象的最大计数,表示信号量对象的计数上限。
  • lpName:指定信号量对象的名称,可以为NULL。
总的来说,CreateSemaphore 是实现线程同步和进程通信,控制对共享资源的访问数量的重要手段之一,如下一段演示代码片段则通过此方法解决了线程通过问题,首先调用CreateSemaphore初始化时将信号量设置一个最大值,每次进入线程函数内部时,则ReleaseSemaphore信号自动加1,如果大于指定的数值则WaitForSingleObject等待释放信号.
  1. #include <stdio.h>
  2. #include <process.h>
  3. #include <windows.h>
  4. // 全局资源
  5. long g_nNum = 0;
  6. // 子线程个数
  7. const int THREAD_NUM = 10;
  8. CRITICAL_SECTION  g_csThreadCode;
  9. HANDLE g_hThreadParameter;
  10. unsigned int __stdcall ThreadFunction(void *ptr)
  11. {
  12.   int nThreadNum = *(int *)ptr;
  13.   // 信号量++
  14.   ReleaseSemaphore(g_hThreadParameter, 1, NULL);
  15.   // 进入线程锁
  16.   EnterCriticalSection(&g_csThreadCode);
  17.   g_nNum++;
  18.   printf("线程编号: %d --> 全局资源值: %d --> 子线程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());
  19.   // 离开线程锁
  20.   LeaveCriticalSection(&g_csThreadCode);
  21.   return 0;
  22. }
  23. int main(int argc,char * argv[])
  24. {
  25.   unsigned int ThreadCount = 0;
  26.   HANDLE  handle[THREAD_NUM];
  27.   // 初始化信号量当前0个资源,最大允许1个同时访问
  28.   g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);
  29.   InitializeCriticalSection(&g_csThreadCode);
  30.   for (int each = 0; each < THREAD_NUM; each++)
  31.   {
  32.     handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
  33.     // 等待信号量>0
  34.     WaitForSingleObject(g_hThreadParameter, INFINITE);
  35.   }
  36.   // 关闭信号
  37.   CloseHandle(g_hThreadParameter);
  38.   // 等待所有进程结束
  39.   WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  40.   DeleteCriticalSection(&g_csThreadCode);
  41.   system("pause");
  42.   return 0;
  43. }
复制代码
如下所示代码片段,是一个应用了两个线程的案例,初始化信号为0,利用信号量值为0时进入non-signaled状态,大于0时进入signaled状态的特性即可实现线程同步。
执行WaitForSingleObject(semTwo, INFINITE);会让线程函数进入类似挂起的状态,当接到ReleaseSemaphore(semOne, 1, NULL);才会恢复执行。
  1. #include <windows.h>  
  2. #include <stdio.h>  
  3. static HANDLE semOne,semTwo;
  4. static int num;
  5. // 线程函数A用于接收参书
  6. DWORD WINAPI ReadNumber(LPVOID lpParamter)
  7. {
  8.   int i;
  9.   for (i = 0; i < 5; i++)
  10.   {
  11.     fputs("Input Number: ", stdout);
  12.     // 临界区的开始 signaled状态  
  13.     WaitForSingleObject(semTwo, INFINITE);
  14.    
  15.     scanf("%d", &num);
  16.     // 临界区的结束 non-signaled状态  
  17.     ReleaseSemaphore(semOne, 1, NULL);
  18.   }
  19.   return 0;
  20. }
  21. // 线程函数B: 用户接受参数后完成计算
  22. DWORD WINAPI Check(LPVOID lpParamter)
  23. {
  24.   int sum = 0, i;
  25.   for (i = 0; i < 5; i++)
  26.   {
  27.     // 临界区的开始 non-signaled状态  
  28.     WaitForSingleObject(semOne, INFINITE);
  29.     sum += num;
  30.     // 临界区的结束 signaled状态  
  31.     ReleaseSemaphore(semTwo, 1, NULL);
  32.   }
  33.   printf("The Number IS: %d \n", sum);
  34.   return 0;
  35. }
  36. int main(int argc, char *argv[])
  37. {
  38.   HANDLE hThread1, hThread2;
  39.   // 创建信号量对象,设置为0进入non-signaled状态  
  40.   semOne = CreateSemaphore(NULL, 0, 1, NULL);
  41.   // 创建信号量对象,设置为1进入signaled状态  
  42.   semTwo = CreateSemaphore(NULL, 1, 1, NULL);
  43.   hThread1 = CreateThread(NULL, 0, ReadNumber, NULL, 0, NULL);
  44.   hThread2 = CreateThread(NULL, 0, Check, NULL, 0, NULL);
  45.   // 关闭临界区
  46.   WaitForSingleObject(hThread1, INFINITE);
  47.   WaitForSingleObject(hThread2, INFINITE);
  48.   CloseHandle(semOne);
  49.   CloseHandle(semTwo);
  50.   system("pause");
  51.   return 0;
  52. }
复制代码
9.2.3 CreateMutex

CreateMutex 是Windows API提供的用于创建互斥体对象的函数之一,该函数用于创建一个互斥体对象,并返回一个表示该互斥体对象的句柄。可以通过WaitForSingleObject或者WaitForMultipleObjects函数等待互斥体对象,以确保只有一个线程能够访问共享资源,其他线程需要等待该线程释放互斥体对象后才能继续访问。当需要释放互斥体对象时,可以调用ReleaseMutex函数将其释放。
CreateMutex 函数常用于对共享资源的访问控制,避免多个线程同时访问导致数据不一致的问题。有时候,互斥体也被用于跨进程同步访问共享资源。
该函数的函数原型如下:
  1. HANDLE CreateMutex(
  2.   LPSECURITY_ATTRIBUTES lpMutexAttributes,
  3.   BOOL                  bInitialOwner,
  4.   LPCTSTR               lpName
  5. );
复制代码
参数说明:

  • lpMutexAttributes:指向SECURITY_ATTRIBUTES结构体的指针,指定互斥体对象的安全描述符和访问权限。通常设为NULL,表示使用默认值。
  • bInitialOwner:指定互斥体的初始状态,TRUE表示将互斥体设置为有所有权的状态,FALSE表示将互斥体设置为没有所有权的状态。
  • lpName:指定互斥体的名称,可以为NULL。
该函数是实现线程同步和进程通信,控制对共享资源的访问的重要手段之一,应用广泛且易用。
如下案例所示,使用互斥锁可以实现单位时间内,只允许一个线程拥有对共享资源的独占权限,从而实现了互不冲突的线程同步。
[code]#include #include using namespace std;// 创建互斥锁HANDLE hMutex = NULL;// 线程函数DWORD WINAPI Func(LPVOID lpParamter){  for (int x = 0; x < 10; x++)  {    // 请求获得一个互斥锁    WaitForSingleObject(hMutex, INFINITE);    cout
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表