6.02 线程同步

原子锁

● 相关问题

多个线程对同一个数据进行原子操作(运算符操作),会产生结果丢失。比如执行++运算时

● 错误代码分析

当线程 A 执行 g_value ++时,如果线程切换时间正好是在线程 A 将值保存到 g_value 前,线程 B 继续执行 g_value ++,那么线程 A 再次被切换回来之后,会将之前线程 A 保存的 g_value 上,线程 B 进行的加法操作被覆盖

  1. // 打断点 右键转到反汇编
  2. // eax是寄存器
  3. mov eax,dword ptr [g_value (07FF66681D170h)] // eax = g_value
  4. inc eax // eax = eax + 1
  5. mov dword ptr [g_value (07FF66681D170h)],eax // g_value = eax
  6. // 如果在第二步切换,此时还未保存, 切到B线程后, 正常储存,再切回A,执行第三步,就会损失结果

● 使用原子锁函数

只能对操作符锁

  • InterlockedIncrement(&g_valu) => g_value++
  • InterlockedDecrement(&g_valu => g_value—
  • InterlockedCompareExchange => 三目
  • InterlockedExchanqe => 等于

原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问

互斥锁

● 相关的问题

多线程下代码或资源的共享使用

● 互斥的使用

  • 任意时间点, 只能有一个线程拥有该互斥
  • 所有线程都不拥有互斥, 有信号

1 创建互斥

  1. HANDLE CreateMutex(
  2. LPSECURITY_ATTRIBUTES IpMutexAttributes, //安全属性 置空
  3. BOOL bInitialOwner//初始的拥有者 TRUE/FALSE
  4. LPCTSTR IpName //命名
  5. );创建成功返回互斥句柄(也是可等候句柄)

2 等候互斥

WaitFor…

如: WaitForSingleObject(mutex_handle, INFINITE);

互斥的等候遵循谁先等候谁先获取,

3 释放互斥

  1. BOOL ReleaseMutex(
  2. HANDLE hMutex // handle to mutex
  3. );

4 关闭互斥句柄

CloseHandle

示例

  1. HANDLE mutex_handle;
  2. DWORD CALLBACK thread_func(LPVOID lpParam) {
  3. WaitForSingleObject(mutex_handle, INFINITE);
  4. // 访问被互斥的代码块
  5. std::cout << "Hello, world!" << std::endl;
  6. ReleaseMutex(mutex_handle);
  7. return 0;
  8. }
  9. int main() {
  10. mutex_handle = CreateMutex(NULL, FALSE, NULL);
  11. HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
  12. if (thread == NULL) {
  13. std::cerr << "Failed to create thread." << std::endl;
  14. return 1;
  15. }
  16. WaitForSingleObject(thread, INFINITE);
  17. CloseHandle(thread);
  18. CloseHandle(mutex_handle);
  19. return 0;
  20. }

事件

● 相关问题

程序(线程)之间的通知的问题。

● 事件的使用

△ 1 创建事件

也是可等候句柄,是否有信号,可以自行控制

  1. HANDLE CreateEvent(
  2. LPSECURITY_ATTRIBUTES IpEventAttributes,//安全属性
  3. BOOL bManualReset, //事件重置(复位)方式,TRUE|FALSE (手动|自动), 有信号->无信号
  4. BOOL bInitialState, //事件初始状态,TRUE 有信号
  5. LPCTSTR IpName //事件命名
  6. ):创建成功返回事件句柄

△ 2 等候事件

WaitForSingleObject/WaitForMultipleObjects

△ 3 触发事件(将事件设置成有信号状态)

  1. BOOL SetEvent(
  2. HANDLE hEvent //handle to event
  3. )

△ 4 复位事件(将事件设置成无信号状态)

  1. BOOL ResetEvent(
  2. HANDLE hEvent //handle to event
  3. )

△ 5 关闭事件

CloseHandle

● 小心事件的死锁

6.02 线程同步 - 图1

信号量

● 相关的问题

类似于事件,解决通知的相关问题.但提供一个计数器,可以设置次数.

● 信号量的使用

1 创建信号量

也是可等候句柄,计数值为 0 时, 无信号

  1. HANDLE CreateSemaphore(
  2. LPSECURITY_ATTRIBUTES IpSemaphoreAttributes,//安全属性
  3. LONG IInitialCount, // 信号量初始化数量
  4. LONG IMaximumCount, // 信号量的最大值
  5. LPCTSTR IpName // 命名
  6. );创建成功返回信号量句柄

2 等候信号量

WaitFor…

每等候通过一次,信号量的信号减 1,直到为 0 时阻塞

3 给信号量指定计数值

  1. BOOL ReleaseSemaphore(
  2. HANDLE hSemaphore, //信号量句柄LONG
  3. IReleaseCount, //释放数量,新的计数值
  4. LPLONG IpPreviousCount //释放前原来信号量的数量,可以为NULL
  5. )

4 关闭句柄

CloseHandle