当前位置:   article > 正文

C/C++ 实现多线程与线程同步_c++多线程执行同一个函数

c++多线程执行同一个函数

多线程中的线程同步可以使用,CreateThread,CreateMutex 互斥锁实现线程同步,通过临界区实现线程同步,Semaphore 基于信号实现线程同步,CreateEvent 事件对象的同步,以及线程函数传递单一参数与多个参数的实现方式。

CreateThread 实现多线程

先来创建一个简单的多线程实例,无参数传递版,运行实例会发现,主线程与子线程运行无规律。

  1. #include <windows.h>
  2. #include <iostream>
  3. using namespace std;
  4. DWORD WINAPI Func(LPVOID lpParamter)
  5. {
  6. for (int x = 0; x < 10; x++)
  7. {
  8. cout << "thread function" << endl;
  9. Sleep(200);
  10. }
  11. return 0;
  12. }
  13. int main(int argc,char * argv[])
  14. {
  15. HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
  16. CloseHandle(hThread);
  17. for (int x = 0; x < 10; x++)
  18. {
  19. cout << "main thread" << endl;
  20. Sleep(400);
  21. }
  22. system("pause");
  23. return 0;
  24. }

beginthreadex 实现多线程

这个方法与前面的CreateThread使用完全一致,只是在参数上面应使用void *该参数可以强转为任意类型,两者实现效果完全一致。

  1. #include <windows.h>
  2. #include <iostream>
  3. #include <process.h>
  4. using namespace std;
  5. unsigned WINAPI Func(void *arg)
  6. {
  7. for (int x = 0; x < 10; x++)
  8. {
  9. cout << "thread function" << endl;
  10. Sleep(200);
  11. }
  12. return 0;
  13. }
  14. int main(int argc, char * argv[])
  15. {
  16. HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, Func, NULL, 0, NULL);
  17. CloseHandle(hThread);
  18. for (int x = 0; x < 10; x++)
  19. {
  20. cout << "main thread" << endl;
  21. Sleep(400);
  22. }
  23. system("pause");
  24. return 0;
  25. }

CreateMutex 互斥锁实现线程同步

使用互斥锁可以实现单位时间内,只允许一个线程拥有对共享资源的独占,从而实现了互不冲突的线程同步。

  1. #include <windows.h>
  2. #include <iostream>
  3. using namespace std;
  4. HANDLE hMutex = NULL; // 创建互斥锁
  5. // 线程函数
  6. DWORD WINAPI Func(LPVOID lpParamter)
  7. {
  8. for (int x = 0; x < 10; x++)
  9. {
  10. // 请求获得一个互斥锁
  11. WaitForSingleObject(hMutex, INFINITE);
  12. cout << "thread func" << endl;
  13. // 释放互斥锁
  14. ReleaseMutex(hMutex);
  15. }
  16. return 0;
  17. }
  18. int main(int argc,char * argv[])
  19. {
  20. HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
  21. hMutex = CreateMutex(NULL, FALSE, "lyshark");
  22. CloseHandle(hThread);
  23. for (int x = 0; x < 10; x++)
  24. {
  25. // 请求获得一个互斥锁
  26. WaitForSingleObject(hMutex, INFINITE);
  27. cout << "main thread" << endl;
  28. // 释放互斥锁
  29. ReleaseMutex(hMutex);
  30. }
  31. system("pause");
  32. return 0;
  33. }

通过互斥锁,同步执行两个线程函数。

  1. #include <windows.h>
  2. #include <iostream>
  3. using namespace std;
  4. HANDLE hMutex = NULL; // 创建互斥锁
  5. #define NUM_THREAD 50
  6. // 线程函数1
  7. DWORD WINAPI FuncA(LPVOID lpParamter)
  8. {
  9. for (int x = 0; x < 10; x++)
  10. {
  11. // 请求获得一个互斥锁
  12. WaitForSingleObject(hMutex, INFINITE);
  13. cout << "this is thread func A" << endl;
  14. // 释放互斥锁
  15. ReleaseMutex(hMutex);
  16. }
  17. return 0;
  18. }
  19. // 线程函数2
  20. DWORD WINAPI FuncB(LPVOID lpParamter)
  21. {
  22. for (int x = 0; x < 10; x++)
  23. {
  24. // 请求获得一个互斥锁
  25. WaitForSingleObject(hMutex, INFINITE);
  26. cout << "this is thread func B" << endl;
  27. // 释放互斥锁
  28. ReleaseMutex(hMutex);
  29. }
  30. return 0;
  31. }
  32. int main(int argc, char * argv[])
  33. {
  34. // 用来存储线程函数的句柄
  35. HANDLE tHandle[NUM_THREAD];
  36. // /创建互斥量,此时为signaled状态
  37. hMutex = CreateMutex(NULL, FALSE, "lyshark");
  38. for (int x = 0; x < NUM_THREAD; x++)
  39. {
  40. if (x % 2)
  41. {
  42. tHandle[x] = CreateThread(NULL, 0, FuncA, NULL, 0, NULL);
  43. }
  44. else
  45. {
  46. tHandle[x] = CreateThread(NULL, 0, FuncB, NULL, 0, NULL);
  47. }
  48. }
  49. // 等待所有线程函数执行完毕
  50. WaitForMultipleObjects(NUM_THREAD, tHandle, TRUE, INFINITE);
  51. // 销毁互斥对象
  52. CloseHandle(hMutex);
  53. system("pause");
  54. return 0;
  55. }

通过临界区实现线程同步

临界区与互斥锁差不多,临界区使用时会创建CRITICAL_SECTION临界区对象,同样相当于一把钥匙,线程函数执行结束自动上交,如下是临界区函数的定义原型。

  1. //初始化函数原型
  2. VOID InitializeCriticalSection(
  3. LPCRITICAL_SECTION lpCriticalSection
  4. );
  5. //销毁函数原型
  6. VOID DeleteCriticalSection(
  7. LPCRITICAL_SECTION lpCriticalSection
  8. );
  9. //获取
  10. VOID EnterCriticalSection(
  11. LPCRITICAL_SECTION lpCriticalSection
  12. );
  13. //释放
  14. VOID LeaveCriticalSection(
  15. LPCRITICAL_SECTION lpCriticalSection
  16. );

这一次我们不适用互斥体,使用临界区实现线程同步,结果与互斥体完全一致,看个人喜好。

  1. #include <windows.h>
  2. #include <iostream>
  3. using namespace std;
  4. CRITICAL_SECTION cs; // 全局定义临界区对象
  5. #define NUM_THREAD 50
  6. // 线程函数
  7. DWORD WINAPI FuncA(LPVOID lpParamter)
  8. {
  9. for (int x = 0; x < 10; x++)
  10. {
  11. //进入临界区
  12. EnterCriticalSection(&cs);
  13. cout << "this is thread func A" << endl;
  14. //离开临界区
  15. LeaveCriticalSection(&cs);
  16. }
  17. return 0;
  18. }
  19. int main(int argc, char * argv[])
  20. {
  21. // 用来存储线程函数的句柄
  22. HANDLE tHandle[NUM_THREAD];
  23. //初始化临界区
  24. InitializeCriticalSection(&cs);
  25. for (int x = 0; x < NUM_THREAD; x++)
  26. {
  27. tHandle[x] = CreateThread(NULL, 0, FuncA, NULL, 0, NULL);
  28. }
  29. // 等待所有线程函数执行完毕
  30. WaitForMultipleObjects(NUM_THREAD, tHandle, TRUE, INFINITE);
  31. //释放临界区
  32. DeleteCriticalSection(&cs);
  33. system("pause");
  34. return 0;
  35. }

Semaphore 基于信号实现线程同步

通过定义一个信号,初始化信号为0,利用信号量值为0时进入non-signaled状态,大于0时进入signaled状态的特性即可实现线程同步。

  1. #include <windows.h>
  2. #include <iostream>
  3. using namespace std;
  4. static HANDLE SemaphoreOne;
  5. static HANDLE SemaphoreTwo;
  6. // 线程函数1
  7. DWORD WINAPI FuncA(LPVOID lpParamter)
  8. {
  9. for (int x = 0; x < 10; x++)
  10. {
  11. // 临界区开始时设置 signaled 状态
  12. WaitForSingleObject(SemaphoreOne, INFINITE);
  13. cout << "this is thread func A" << endl;
  14. // 临界区结束则设置为 non-signaled 状态
  15. ReleaseSemaphore(SemaphoreOne, 1, NULL);
  16. }
  17. return 0;
  18. }
  19. // 线程函数2
  20. DWORD WINAPI FuncB(LPVOID lpParamter)
  21. {
  22. for (int x = 0; x < 10; x++)
  23. {
  24. // 临界区开始时设置 signaled 状态
  25. WaitForSingleObject(SemaphoreTwo, INFINITE);
  26. cout << "this is thread func B" << endl;
  27. // 临界区结束则设置为 non-signaled 状态
  28. ReleaseSemaphore(SemaphoreTwo, 1, NULL);
  29. }
  30. return 0;
  31. }
  32. int main(int argc, char * argv[])
  33. {
  34. // 用来存储线程函数的句柄
  35. HANDLE hThreadA, hThreadB;
  36. // 创建信号量对象,并且设置为0进入non-signaled状态
  37. SemaphoreOne = CreateSemaphore(NULL, 0, 1, NULL);
  38. // 创建信号量对象,并且设置为1进入signaled状态
  39. SemaphoreTwo = CreateSemaphore(NULL, 1, 1, NULL); // 先执行这一个线程函数
  40. hThreadA = CreateThread(NULL, 0, FuncA, NULL,0, NULL);
  41. hThreadB = CreateThread(NULL, 0, FuncB, NULL, 0, NULL);
  42. // 等待两个线程函数执行完毕
  43. WaitForSingleObject(hThreadA, INFINITE);
  44. WaitForSingleObject(hThreadA, INFINITE);
  45. // 销毁两个线程函数
  46. CloseHandle(SemaphoreOne);
  47. CloseHandle(SemaphoreTwo);
  48. system("pause");
  49. return 0;
  50. }

上面的一段代码,容易产生死锁现象,即,线程函数B执行完成后,A函数一直处于等待状态。

执行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. scanf("%d", &num);
  15. //临界区的结束 non-signaled状态
  16. ReleaseSemaphore(semOne, 1, NULL);
  17. }
  18. return 0;
  19. }
  20. // 线程函数B: 用户接受参数后完成计算
  21. DWORD WINAPI Check(LPVOID lpParamter)
  22. {
  23. int sum = 0, i;
  24. for (i = 0; i < 5; i++)
  25. {
  26. //临界区的开始 non-signaled状态
  27. WaitForSingleObject(semOne, INFINITE);
  28. sum += num;
  29. //临界区的结束 signaled状态
  30. ReleaseSemaphore(semTwo, 1, NULL);
  31. }
  32. printf("The Number IS: %d \n", sum);
  33. return 0;
  34. }
  35. int main(int argc, char *argv[])
  36. {
  37. HANDLE hThread1, hThread2;
  38. //创建信号量对象,设置为0进入non-signaled状态
  39. semOne = CreateSemaphore(NULL, 0, 1, NULL);
  40. //创建信号量对象,设置为1进入signaled状态
  41. semTwo = CreateSemaphore(NULL, 1, 1, NULL);
  42. hThread1 = CreateThread(NULL, 0, ReadNumber, NULL, 0, NULL);
  43. hThread2 = CreateThread(NULL, 0, Check, NULL, 0, NULL);
  44. // 关闭临界区
  45. WaitForSingleObject(hThread1, INFINITE);
  46. WaitForSingleObject(hThread2, INFINITE);
  47. CloseHandle(semOne);
  48. CloseHandle(semTwo);
  49. system("pause");
  50. return 0;
  51. }

CreateEvent 事件对象的同步

事件对象实现线程同步,与前面的临界区和互斥体有很大的不同,该方法下创建对象时,可以在自动non-signaled状态运行的auto-reset模式,当我们设置好我们需要的参数时,可以直接使用SetEvent(hEvent)设置事件状态,会自动执行线程函数。

  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. }

线程函数传递单个参数

线程函数中的定义中LPVOID允许传递一个参数,只需要在县城函数中接收并强转(int)(LPVOID)port即可。

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. // 线程函数接收一个参数
  4. DWORD WINAPI ScanThread(LPVOID port)
  5. {
  6. // 将参数强制转化为需要的类型
  7. int Port = (int)(LPVOID)port;
  8. printf("[+] 端口: %5d \n", port);
  9. return 1;
  10. }
  11. int main(int argc, char* argv[])
  12. {
  13. HANDLE handle;
  14. for (int port = 0; port < 100; port++)
  15. {
  16. handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ScanThread, (LPVOID)port, 0, 0);
  17. }
  18. WaitForSingleObject(handle, INFINITE);
  19. system("pause");
  20. return 0;
  21. }

线程函数传递多参数

如果想在线程函数中传递多个参数,则需要传递一个结构指针,通过线程函数内部强转为结构类型后,取值,这个案例花费了我一些时间,网上也没找到合适的解决方法,或找到的都是歪瓜裂枣瞎转的东西,最后还是自己研究了一下写了一个没为题的。

其主要是线程函数中调用的参数会与下一个线程函数结构相冲突,解决的办法时在每次进入线程函数时,自己拷贝一份,每个人使用自己的那一份,才可以避免此类事件的发生,同时最好配合线程同步一起使用,如下时线程扫描器的部分代码片段。

  1. #include <stdio.h>
  2. #include <windows.h>
  3. typedef struct _THREAD_PARAM
  4. {
  5. char *HostAddr; // 扫描主机
  6. DWORD dwStartPort; // 端口号
  7. }THREAD_PARAM;
  8. // 这个扫描线程函数
  9. DWORD WINAPI ScanThread(LPVOID lpParam)
  10. {
  11. // 拷贝传递来的扫描参数
  12. THREAD_PARAM ScanParam = { 0 };
  13. // 这一步很重要,如不拷贝,则会发生重复赋值现象,导致扫描端口一直都是一个。
  14. // 坑死人的玩意,一开始我始终没有发现这个问题。sb玩意!!
  15. MoveMemory(&ScanParam, lpParam, sizeof(THREAD_PARAM));
  16. printf("地址: %-16s --> 端口: %-5d 状态: [Open] \n", ScanParam.HostAddr, ScanParam.dwStartPort);
  17. return 0;
  18. }
  19. int main(int argc, char *argv[])
  20. {
  21. THREAD_PARAM ThreadParam = { 0 };
  22. ThreadParam.HostAddr = "192.168.1.10";
  23. for (DWORD port = 1; port < 100; port++)
  24. {
  25. ThreadParam.dwStartPort = port;
  26. HANDLE hThread = CreateThread(NULL, 0, ScanThread, (LPVOID)&ThreadParam, 0, NULL);
  27. WaitForSingleObject(hThread, INFINITE);
  28. }
  29. system("pause");
  30. return 0;
  31. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/972178
推荐阅读
相关标签
  

闽ICP备14008679号