线程安全问题.同步函数

一丶简介什么是线程安全

  通过上面几讲.我们知道了线程怎么创建.线程切换的原理(CONTEXT结构) 每个线程在切换的时候都有自己的堆栈.

但是这样会有安全问题. 为什么?  我们每个线程都使用自己的局部变量这个是没有安全问题的. 但是线程可能会使用全局变量.这样很有可能会产生安全问题.为什么是很有可能.

1.有全局变量的情况下.有可能会有安全问题.

2.对全局变量进行写操作.则一定有安全问题.

上面两个条件都具备,线程才是不安全的.

为什么是不安全的.

试想一下. 如果这个全局变量在更改.另一个线程也更改了.最后则会出现两个线程同时更改这个全局变量. 问题就会出现在这.

例如以下代码:

  1. // 临界区同步函数.cpp : 定义控制台应用程序的入口点。
  2. //
  3.  
  4. #include "stdafx.h"
  5. #include <Windows.h>
  6. DWORD g_Number = ;
  7. DWORD WINAPI MyThreadFun1(LPVOID lParame)
  8. {
  9. while (g_Number > )
  10. {
  11. printf("+++剩下Number个数 = %d\r\n", g_Number);
  12. g_Number--;
  13. printf("+++当前的Number个数 = %d\r\n", g_Number);
  14. }
  15. return ;
  16. }
  17.  
  18. DWORD WINAPI MyThreadFun2(LPVOID lParame)
  19. {
  20. while (g_Number > )
  21. {
  22. printf("***剩下Number个数 = %d\r\n", g_Number);
  23. g_Number--; //产生线程安全问题
  24. printf("***当前的Number个数 = %d\r\n", g_Number);
  25. }
  26. return ;
  27. }
  28.  
  29. int main(int argc,char *argv[])
  30. {
  31. HANDLE hThreadHand[] = { NULL };
  32. hThreadHand[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, NULL, , NULL);
  33. hThreadHand[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, NULL, , NULL); //创建两个线程
  34. WaitForMultipleObjects(, hThreadHand, TRUE, INFINITE);
  35.  
  36. printf("Number个数 = %d \r\n", g_Number);
  37. system("pause");
  38. return ;
  39. }

上面的代码很简单. 看下运行结果

为什么会产生这个问题.原因是.在线程中我们有个地方

while(全局变量 > 0) 则会执行下边代码. 但是很有可能执行完这一句. 线程发生了切换. 去执行另一个线程去了. 最终会产生这样的结果.

如果看反汇编.则会发现 全局变量--的地方.汇编代码 并不是一局. 如果发生线程切换则会出现错误.

首先获取全局变量的值.

然后sub -1

最后重新赋值.

很有可能在sun eax 1的时候就发生了切换. 这样就有安全问题了.为了解决这些问题.我们必须想办法. 所以Windows提供了一组线程同步的函数.

二丶线程同步函数之临界区

什么时候临界区. 临界区的意思就是 这一个区域我给你锁定.当前有且只能有一个线程来执行我们临界区的代码.

而临界资源是一个全局变量

临界区的使用步骤.

1.创建全局原子变量.

2.初始化全原子变量

3.进入临界区

4.释放临界区.

5.删除临界区.

具体API:

  1.全局原子变量

  1. CRITICAL_SECTION g_cs; //直接创建即可.不用关心内部实现.

  2.初始化全局原子变量.InitializeCriticalSection

  1. _Maybe_raises_SEH_exception_ VOID InitializeCriticalSection(
  2. LPCRITICAL_SECTION lpCriticalSection //传入全局原子变量的地址
  3. );

3.使用的API 进入临界区.

  1. void EnterCriticalSection(
  2. LPCRITICAL_SECTION lpCriticalSection //全局原子变量
  3. );

下面还有一个. 是尝试无阻塞模式进入临界区. 意思就是内部加了一个判断.是否死锁了.

  1. BOOL TryEnterCriticalSection( 返回吃持有的临界区对象.如果成功的情况下.
  2. LPCRITICAL_SECTION lpCriticalSection
  3. );

  4.使用API 释放临界区.

  

  1. void LeaveCriticalSection(
  2. LPCRITICAL_SECTION lpCriticalSection //全局原子对象
  3. );

  5.删除临界区对象.

  1. void DeleteCriticalSection(
  2. LPCRITICAL_SECTION lpCriticalSection
  3. );

代码例子:

  1. // 临界区同步函数.cpp : 定义控制台应用程序的入口点。
  2. //
  3.  
  4. #include "stdafx.h"
  5. #include <Windows.h>
  6. //创建临界区结构
  7. CRITICAL_SECTION g_cs;
  8.  
  9. DWORD g_Number = ;
  10. DWORD WINAPI MyThreadFun1(LPVOID lParame)
  11. {
  12. EnterCriticalSection(&g_cs); //进入临界区
  13. while (g_Number > )
  14. {
  15. printf("+++剩下Number个数 = %d\r\n", g_Number);
  16. g_Number--;
  17. printf("+++当前的Number个数 = %d\r\n", g_Number);
  18. }
  19. LeaveCriticalSection(&g_cs);
  20. return ;
  21. }
  22.  
  23. DWORD WINAPI MyThreadFun2(LPVOID lParame)
  24. {
  25. EnterCriticalSection(&g_cs); //进入临界区
  26. while (g_Number > )
  27. {
  28. printf("***剩下Number个数 = %d\r\n", g_Number);
  29. g_Number--; //while语句内就是临界区了.有且只能一个线程访问.
  30. printf("***当前的Number个数 = %d\r\n", g_Number);
  31. }
  32. LeaveCriticalSection(&g_cs);
  33. return ;
  34. }
  35.  
  36. int main(int argc,char *argv[])
  37. {
  38. //初始化临界区全局原子变量
  39. InitializeCriticalSectionAndSpinCount(&g_cs, 0x00000400);
  40. //InitializeCriticalSection(&g_cs); //初始化临界区.两个API都可以.
  41.  
  42. HANDLE hThreadHand[] = { NULL };
  43. hThreadHand[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, NULL, , NULL);
  44. hThreadHand[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, NULL, , NULL); //创建两个线程
  45. WaitForMultipleObjects(, hThreadHand, TRUE, INFINITE);
  46.  
  47. DeleteCriticalSection(&g_cs); //删除临界区.
  48.  
  49. printf("+Number个数 = %d \r\n", g_Number);
  50. system("pause");
  51. return ;
  52. }

官方MSDN例子:

链接:  https://docs.microsoft.com/zh-cn/windows/desktop/Sync/using-critical-section-objects

三丶线程同步之互斥体

1.临界区缺点.以及衍生出来的跨进程保护

上面讲了临界区. 但是我们的临界资源是一个全局变量.例如下图:

如果我们的临界资源是一个文件. 需要两个进程都要访问怎么办? 此时临界区已经不可以跨进程使用了.

2.跨进程控制.

  跨进程控制就是指 不同进程中的多线程控制安全..比如A进程访问临界资源的时候. B进程不能访问. 因为临界区的 令牌.也就是我们说的全局原子变量.只能在应用层.

但是如果放到内核中就好办了. 如下图所示

  

A进程的线程从内核中获取互斥体. 为0 还是为1. B进程一样. 如果为 0 则可以进行访问临界资源.  访问的时候.互斥体则设置为1(也就是令牌设置为1)这样B进程就获取不到了.自然不能访问

临界区资源了.

3.互斥体操作API

  既然明白了互斥体是一个内核层的原子操作.那么我们就可以使用API 进行操作了.

操作步骤.

    1.创建互斥体. 信号量设置为有信号的状态    例如全局的原子变量现在是有信号.是可以进行访问的.

    2.获取信号状态. 如果有信号则进入互斥体临界区执行代码.此时互斥体信号为无信号. 也就是说别的进程访问的时候.因为没有信号.执行不了代码.

    3.释放互斥体. 信号状态为有信号. 此时别的进程信号已经有了.所以可以进行访问了.

具体API:

1.创建互斥体

  1. HANDLE CreateMutexA(
  2. LPSECURITY_ATTRIBUTES lpMutexAttributes, SD安全属性.句柄是否可以继承.每个内核对象API都拥有.
  3. BOOL bInitialOwner, 初始的信号量状态. false为有信号. 获取令牌的时候可以获取到. True为无信号. 且如果为True互斥体对象为线程拥有者.
  4. LPCSTR lpName 全局名字. 根据名字寻找互斥体.
  5. );

2.获取令牌.

  

  1. DWORD WaitForSingleObject(
  2. HANDLE hHandle, 等待的内核对象
  3. DWORD dwMilliseconds 等待的时间
  4. );

调用此函数之后.信号为无信号.别的进程是进入不了互斥体临界区的.

3.释放互斥体

  

  1. BOOL ReleaseMutex(
  2. HANDLE hMutex
  3. );

调用完比之后.互斥体为有信号.可以使用了.

代码例子:

  两个工程代码是一样的.贴一份出来.

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. //创建临界区结构
  4.  
  5. int main(int argc,char *argv[])
  6. {
  7. //初始化临界区全局原子变量
  8. HANDLE MutexHandle = CreateMutex(NULL, FALSE, TEXT("AAA")); //创建互斥体. 信号量为0. 有信号的状态.wait可以等待
  9.  
  10. WaitForSingleObject(MutexHandle,INFINITE);
  11.  
  12. for (size_t i = ; i < ; i++)
  13. {
  14. Sleep();
  15. printf("A进程访问临街资源中临街资源ID = %d \r\n", i);
  16. }
  17.  
  18. ReleaseMutex(MutexHandle);
  19. return ;
  20. }

先运行A进程在运行B进程. 则B进程处于卡死状态.

实现了同步. 除非A进程释放互斥体句柄使信号变为有信号.此时才可以访问B

官方代码例子:

  

  1. #include <windows.h>
  2. #include <stdio.h>
  3.  
  4. #define THREADCOUNT 2
  5.  
  6. HANDLE ghMutex;
  7.  
  8. DWORD WINAPI WriteToDatabase( LPVOID );
  9.  
  10. int main( void )
  11. {
  12. HANDLE aThread[THREADCOUNT];
  13. DWORD ThreadID;
  14. int i;
  15.  
  16. // Create a mutex with no initial owner
  17.  
  18. ghMutex = CreateMutex(
  19. NULL, // default security attributes
  20. FALSE, // initially not owned 有信号
  21. NULL); // unnamed mutex 不需要跨进程使用.所以不用名字
  22.  
  23. if (ghMutex == NULL)
  24. {
  25. printf("CreateMutex error: %d\n", GetLastError());
  26. return ;
  27. }
  28.  
  29. // Create worker threads
  30.  
  31. for( i=; i < THREADCOUNT; i++ )
  32. {
  33. aThread[i] = CreateThread( //创建 THREADCOUNT个线程
  34. NULL, // default security attributes
  35. , // default stack size
  36. (LPTHREAD_START_ROUTINE) WriteToDatabase,
  37. NULL, // no thread function arguments
  38. , // default creation flags
  39. &ThreadID); // receive thread identifier
  40.  
  41. if( aThread[i] == NULL )
  42. {
  43. printf("CreateThread error: %d\n", GetLastError());
  44. return ;
  45. }
  46. }
  47.  
  48. // Wait for all threads to terminate
  49.  
  50. WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE); //等待线程执行完毕
  51.  
  52. // Close thread and mutex handles
  53.  
  54. for( i=; i < THREADCOUNT; i++ )
  55. CloseHandle(aThread[i]);
  56.  
  57. CloseHandle(ghMutex);
  58.  
  59. return ;
  60. }
  61.  
  62. DWORD WINAPI WriteToDatabase( LPVOID lpParam )
  63. {
  64. // lpParam not used in this example
  65. UNREFERENCED_PARAMETER(lpParam);
  66.  
  67. DWORD dwCount=, dwWaitResult;
  68.  
  69. // Request ownership of mutex.
  70.  
  71. while( dwCount < )
  72. {
  73. dwWaitResult = WaitForSingleObject( //线程内部等待互斥体.因为一开始为FALSE所以有信号.第一次执行线程的时候则会执行.
  74. ghMutex, // handle to mutex
  75. INFINITE); // no time-out interval
  76.  
  77. switch (dwWaitResult)
  78. {
  79. // The thread got ownership of the mutex
  80. case WAIT_OBJECT_0:
  81. __try {
  82. // TODO: Write to the database
  83. printf("Thread %d writing to database...\n",
  84. GetCurrentThreadId());
  85. dwCount++;
  86. }
  87.  
  88. __finally {
  89. // Release ownership of the mutex object
  90. if (! ReleaseMutex(ghMutex)) //执行完毕.释放互斥体.信号量变为有信号. 其余线程等待的时候可以等到则可以继续执行线程代码
  91. {
  92. // Handle error.
  93. }
  94. }
  95. break;
  96.  
  97. // The thread got ownership of an abandoned mutex
  98. // The database is in an indeterminate state
  99. case WAIT_ABANDONED:
  100. return FALSE;
  101. }
  102. }
  103. return TRUE;
  104. }

四丶事件操作API

  相应的管理线程同步操作的.还有事件.

具体API:

  1.创建事件对象

  1. HANDLE CreateEventA(
  2. LPSECURITY_ATTRIBUTES lpEventAttributes, SD安全属性
  3. BOOL bManualReset, 通知类型
  4. BOOL bInitialState, 初始值有信号还是无信号.false无信号
  5. LPCSTR lpName 全局名字
  6. );
  7. 返回事件句柄

首先这个函数有点复杂. 主要是第二个跟第三个参数.

第三个参数我们很好理解. 有信号还是无信号.  false为无信号. true为有信号.  这样Wait函数根据有无信号就可以进行线程是否执行了.

主要是第二个参数. 通知类型.这个比较复杂.

通知类型的意思就是指.  如果我们按照以前.我们使用了wait函数. 那么有信号会变为无信号.除非释放才会继续有信号执行.

而现在的通知类型如果为TRUE. 那么wait函数执行的时候.你有信号我不会自动变为无信号了.除非你手动自己更改.

如果通知类型为FALSE 那么则自动设置.有信号使用wait函数接受了.那么就变成无信号了.

2.设置信号状态

上面说了.如果为TRUE. 那么信号就不会自动设置了.那么需要我们手动设置.

具体API

  设置为有信号状态

  1. BOOL SetEvent(
  2. HANDLE hEvent
  3. );

那么相应的也有设置为无信号的状态

  1. BOOL ResetEvent(
  2. HANDLE hEvent
  3. );

3.具体代码例子

初始值为有信号状态. 通知类型为TRUE的情况下.

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. HANDLE g_hEvent;
  4. DWORD WINAPI MyThreadFun1(LPVOID lparam)
  5. {
  6. WaitForSingleObject(g_hEvent, INFINITE);// 等待事件对象
  7. for (size_t i = ; i < ; i++)
  8. {
  9. printf("A线程执行EIP = %d\r\n", i);
  10. }
  11. return ;
  12. }
  13.  
  14. DWORD WINAPI MyThreadFun2(LPVOID lparam)
  15. {
  16. WaitForSingleObject(g_hEvent, INFINITE);// 等待事件对象
  17.  
  18. for (size_t i = ; i < ; i++)
  19. {
  20. printf("B线程执行EIP = %d\r\n", i);
  21. }
  22. return ;
  23. }
  24.  
  25. int main(int argc, char *argv[])
  26. {
  27.  
  28. g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); //通知类型为TRUE,则wait函数不手动将信号设置为无信号.
  29.  
  30. HANDLE hThread[] = { NULL };
  31. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, , , NULL);
  32. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, , , NULL);
  33.  
  34. WaitForMultipleObjects(, hThread, TRUE, INFINITE);
  35.  
  36. CloseHandle(hThread[]);
  37. CloseHandle(hThread[]);
  38. CloseHandle(g_hEvent);
  39.  
  40. system("pause");
  41. return ;
  42. }

根据上面代码我们设置的通知类型为TRUE. 那么则AB线程都会执行. 因为wait函数不会将信号设置为无信号了.

结果演示.

但是如果我们设置为信号状态为无信号的情况下.

也就是将上面的创建事件的代码. 的第三个参数设置为FALSE

启动后线程都会被阻塞.

那么如果我们设置通知类型为FALSE .且信号类型是有信号的情况下.

看看其执行结果

可以看到只会执行线程A. 因为通知类型我们改为FALSE. 那么wait函数则会自动设置信号状态了. 也就是说我们创建的事件一开始是有信号的.

首先执行线程A. 线程A 里面的wait函数等待到了有信号. 那么就会执行代码了. 此时信号状态已经设置为无信号了. 所以线程B就会阻塞到wait函数哪里.

如果此时我们想执行完A之后再执行B. 那么可以使用 SetEvent设置信号为有信号.

如果我们用于编程的话.大部分通知类型会改成FALSE. 让它自动设置信号状态为无信号. 我们可以使用API SetEvent设置有信号.

这样编程比较简单. 如果设置通知类型为TRUE. 那么我们就要使用 ResetEvent 跟SetEvent配合了.

如以下图片所示代码:

g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);  不截图了.通知类型改为TRUE. 手动设置通知类型.

五丶 预留位.信号量的预留

六丶互斥本质跟同步本质

什么是互斥.什么是同步. 上面我们说了很多线程同步函数.那么是否是真的同步了.不见得. 了解了互斥跟同步的本质.才能更好的编写同步代码.

1.什么是互斥.什么是同步?

  互斥: 互斥就是指一块资源.当前访问的时候有且只有一个线程访问. 比如A访问的时候 B会阻塞.访问不了.

  同步: 同步的意思就是让线程执行顺序是有序的.因为互斥可以保证A访问的时候B访问不了.但有可能A会访问多次.线程无序.此时同步的意思就是 我就想让A执行完在执行B.

这个就是同步.

经典互斥例子.可以以这个例子讲解同步跟互斥.

如下代码:

  

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. HANDLE g_hMutex;
  4. DWORD g_Money;
  5. DWORD g_MAX = ;
  6. DWORD WINAPI MyThreadFun1(LPVOID lparam)
  7. {
  8. WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
  9. for (size_t i = ; i < g_MAX; i++)
  10. {
  11. g_Money = ; //消费者修改金钱
  12. DWORD dwTid = GetCurrentThreadId();
  13. printf("线程%d 生产者执行收钱动作.当前金钱 = %d\r\n", dwTid,g_Money);
  14. ReleaseMutex(g_hMutex);
  15. }
  16.  
  17. return ;
  18. }
  19.  
  20. DWORD WINAPI MyThreadFun2(LPVOID lparam)
  21. {
  22. WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
  23. //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
  24. for (size_t i = ; i < g_MAX; i++)
  25. {
  26. g_Money = ; //消费者修改金钱
  27. DWORD dwTid = GetCurrentThreadId();
  28. printf("线程%d 消费者执行花钱动作.当前金钱 = %d\r\n",dwTid, g_Money);
  29. ReleaseMutex(g_hMutex);
  30.  
  31. }
  32. return ;
  33. }
  34.  
  35. int main(int argc, char *argv[])
  36. {
  37.  
  38. g_hMutex = CreateMutex(NULL, FALSE,NULL); //创建互斥体.信号为有信号
  39.  
  40. HANDLE hThread[] = { NULL };
  41. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, , , NULL);
  42. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, , , NULL);
  43.  
  44. WaitForMultipleObjects(, hThread, TRUE, INFINITE);
  45.  
  46. CloseHandle(hThread[]);
  47. CloseHandle(hThread[]);
  48. CloseHandle(g_hMutex);
  49.  
  50. system("pause");
  51. return ;
  52. }

一个线程修改为1.一个线程修改为0.那么按照正常逻辑.应该是生产一个.消费一个 .

观看结果.结果是迥然不同的.

并不是生产一个.释放一个.

那如何变成 生产一个消费一个的这种模式那. 那么我们可以写一个简单的例子.判断我们的金钱来进行是否修改.

修改代码为如下代码.

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. HANDLE g_hMutex;
  4. DWORD g_Money = ;
  5. DWORD g_MAX = ;
  6. DWORD g_IsChange;
  7. DWORD WINAPI MyThreadFun1(LPVOID lparam)
  8. {
  9.  
  10. for (size_t i = ; i < g_MAX; i++)
  11. {
  12. WaitForSingleObject(g_hMutex, INFINITE);// 等待事件对象
  13. if (g_Money == )
  14. {
  15. g_Money = ; //消费者修改金钱
  16. DWORD dwTid = GetCurrentThreadId();
  17. printf("线程%d 生产者执行收钱动作.当前金钱 = %d\r\n", dwTid, g_Money);
  18.  
  19. }
  20. else
  21. {
  22. i--; //因为如果不想等.循环次数会浪费.所以-1次.不让它浪费.
  23. }
  24. ReleaseMutex(g_hMutex);
  25. }
  26.  
  27. return ;
  28. }
  29.  
  30. DWORD WINAPI MyThreadFun2(LPVOID lparam)
  31. {
  32.  
  33. //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
  34. for (size_t i = ; i < g_MAX; i++)
  35. {
  36. WaitForSingleObject(g_hMutex, INFINITE);// 等待信号
  37. if (g_Money == )
  38. {
  39. g_Money = ; //消费者修改金钱
  40. DWORD dwTid = GetCurrentThreadId();
  41. printf("线程%d 消费者执行花钱动作.当前金钱 = %d\r\n", dwTid, g_Money);
  42. }
  43. else
  44. {
  45. i--;
  46. }
  47.  
  48. ReleaseMutex(g_hMutex);
  49.  
  50. }
  51. return ;
  52. }
  53.  
  54. int main(int argc, char *argv[])
  55. {
  56.  
  57. g_hMutex = CreateMutex(NULL, FALSE,NULL);
  58.  
  59. HANDLE hThread[] = { NULL };
  60. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, , , NULL);
  61. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, , , NULL);
  62.  
  63. WaitForMultipleObjects(, hThread, TRUE, INFINITE);
  64.  
  65. CloseHandle(hThread[]);
  66. CloseHandle(hThread[]);
  67. CloseHandle(g_hMutex);
  68.  
  69. system("pause");
  70. return ;
  71. }

代码结果演示

发现已经实现了同步.但是这样写是有问题的. 浪费了大量的时间.

因为当线程执行的时候.如果判断不是1则会继续循环.而没有释放信号. 而我们要实现的则是.如果没有.则给下一个线程继续执行.且保证有序.

所以上面的代码虽然实现了但是还是不能保证同步.会浪费线程的时间.

如果要实现同步.那么只能用事件来实现了. 所以说 同步函数各有优缺点.

实现同步的方法.

1.创建两个Event对象.一个有信号.一个无信号.且通知类型都是自动设置的.也就是参数2为FALSE.

2.当A线程执行完毕之后.使用SetEvent给B线程设置信号状态为有信号.这样B就会执行. B执行完之后给A设置.这样A就执行.相当于交错设置.不浪费时间片.

如下代码演示:

  1. #include "stdafx.h"
  2. #include <Windows.h>
  3. HANDLE g_hEventOne;
  4. HANDLE g_hEventTwo;
  5.  
  6. DWORD g_Money = ;
  7. DWORD g_MAX = ;
  8. DWORD g_IsChange;
  9. DWORD WINAPI MyThreadFun1(LPVOID lparam)
  10. {
  11.  
  12. for (size_t i = ; i < g_MAX; i++)
  13. {
  14. WaitForSingleObject(g_hEventOne, INFINITE);// 等待事件对象
  15.  
  16. g_Money = ; //消费者修改金钱
  17. DWORD dwTid = GetCurrentThreadId();
  18. printf("线程%d 生产者执行收钱动作.当前金钱 = %d\r\n", dwTid, g_Money);
  19.  
  20. SetEvent(g_hEventTwo);
  21.  
  22. }
  23.  
  24. return ;
  25. }
  26.  
  27. DWORD WINAPI MyThreadFun2(LPVOID lparam)
  28. {
  29.  
  30. //因为通知类型为TRUE.所以我们必须手动设置信号状态为无信号
  31. for (size_t i = ; i < g_MAX; i++)
  32. {
  33. WaitForSingleObject(g_hEventTwo, INFINITE);// 等待事件对象
  34.  
  35. g_Money = ; //消费者修改金钱
  36. DWORD dwTid = GetCurrentThreadId();
  37. printf("线程%d 消费者执行消费动作.当前金钱 = %d\r\n", dwTid, g_Money);
  38.  
  39. SetEvent(g_hEventOne);
  40.  
  41. }
  42.  
  43. return ;
  44. }
  45.  
  46. int main(int argc, char *argv[])
  47. {
  48.  
  49. g_hEventOne = CreateEvent(NULL, FALSE, TRUE, NULL); //A线程设置为有信号则A线程先执行.
  50. g_hEventTwo = CreateEvent(NULL, FALSE, FALSE, NULL);//B线程设置为无信号.则B线程不会先执行.要等A线程通知才可以执行.
  51.  
  52. HANDLE hThread[] = { NULL };
  53. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun1, , , NULL);
  54. hThread[] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)MyThreadFun2, , , NULL);
  55.  
  56. WaitForMultipleObjects(, hThread, TRUE, INFINITE);
  57.  
  58. CloseHandle(hThread[]);
  59. CloseHandle(hThread[]);
  60. CloseHandle(g_hEventOne);
  61. CloseHandle(g_hEventTwo);
  62.  
  63. system("pause");
  64. return ;
  65. }

代码执行结果

实现了同步有序

Win32线程安全问题.同步函数的更多相关文章

  1. java学习笔记 --- 多线程(线程安全问题——同步代码块)

    1.导致出现安全问题的原因: A:是否是多线程环境 B:是否有共享数据 C:是否有多条语句操作共享数据 2.解决线程安全问题方法: 同步代码块: synchronized(对象){ 需要同步的代码; ...

  2. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  3. 《day15---多线程安全问题_JDK1.5的锁机制》

    //15同步问题的分析案例以及解决思路 //两个客户到一个银行去存钱,每个客户一次存100,存3次. //问题,该程序是否有安全问题,如果有,写出分析过程,并定于解决方案. /* 发现运行结果: su ...

  4. java基础知识回顾之java Thread类学习(六)--java多线程同步函数用的锁

    1.验证同步函数使用的锁----普通方法使用的锁 思路:创建两个线程,同时操作同一个资源,还是用卖票的例子来验证.创建好两个线程t1,t2,t1线程走同步代码块操作tickets,t2,线程走同步函数 ...

  5. JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

    JAVA之旅(十四)--静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是clas ...

  6. Win32 线程同步

    Win32 线程同步 ## Win32线程同步 ### 1. 原子锁 ### 2. 临界区 {全局变量} CRITICAL_SECTION CS = {0}; // 定义并初始化临界区结构体变量 {线 ...

  7. Java学习之多线程(线程安全问题及线程同步)

    一.线程安全问题产生前提:1.多线程操作共享数据2.线程任务中有多条代码 class Ticket implements Runnable { //2.共享数据 private int num = 1 ...

  8. 浅谈利用同步机制解决Java中的线程安全问题

    我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...

  9. OC 线程操作 - GCD使用 -同步函数,异步函数,串行队列,并发队列

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // GCD 开几条线程并不是我们 ...

随机推荐

  1. SQLServer · 最佳实践 · 透明数据加密TDE在SQLServer的应用

    转:https://yq.aliyun.com/articles/42270 title: SQLServer · 最佳实践 · 透明数据加密TDE在SQLServer的应用 author: 石沫 背 ...

  2. 树莓派RaspBerry账户初始化设定

    1 第一次安装系统进入后默认账户是pi/raspberry  root账户是默认锁定的 sudo passwd root 设置root账户密码 sudo passwd --unlock root 开启 ...

  3. mysql5.7安装记录

    mysql安装记录 版本5.7 windows系统 一.缺少my.ini文件 [mysql]# 设置mysql客户端默认字符集default-character-set=utf8 [mysqld]#设 ...

  4. U-Boot Makefile分析(3) rules.mk分析

    浏览各个子Makefile可以发现,他们都会在文件的后面包含rules.mk,这个文件的作用就是更新源文件的依赖,并生成各种.depend文件. _depend: $(obj).depend # Sp ...

  5. kvm-qcow2派生镜像的远程备份的方法!

    在虚拟化环境中,关于虚拟机的远程备份是一个比较重要的环节,这个是有关于整个机房挂掉之后,仍然可以恢复的最后一招. 在kvm中这种情况可以通过直接备份虚拟机的镜像文件(qcow2)到远端存储解决. 但有 ...

  6. Spring使用Autowiring自动装配 解决提示报错小技巧

    1.打开Settings   输入Inspections  找到Spring --> Spring Core --> Code --> Autowiring  for  Bean  ...

  7. echarts 调整图表 位置 的方法

    ###内部图表大小是与div容器大小位置相关的,如果想调整图表大小位置,调整div的属性就可以了### ###如果是想调整图表与div间上下左右留白,则设置grid属性就可以了### 如图所示: 具体 ...

  8. PopupWindow计算弹出位置

    1.首先自定义PopupWindow    popWindowView= LinearLayout.inflate(context, R.layout.popupWindow,null);    po ...

  9. [ 10.05 ]CF每日一题系列—— 962B贪心和思维?

    Description: 非 * 号的地方可以放A或B,不能AA或BB,一共有a个A,b个B,问你最多放几个 Solution: 1.模拟一下,找连续空位长度,如果长度为奇数,则我可以有一个位置放任意 ...

  10. linux redis 多实例安装

    前言: Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表( ...