第二部分:用户区同步
-
同步:就是按照一定的顺序执行不同的线程
-
互斥:当一个线程访问某一资源的时候,其它线程不能同时访问
多线程产生的问题
#include <stdio.h> #include <windows.h> // 全局变量,被不同的线程访问和修改 int g_Number = 0; DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) { // 为 g_Number 自增 100000 次 for (int i = 0; i < 100000; i++) g_Number++; return 0; } DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) { // 为 g_Number 自增 100000 次 for (int i = 0; i < 100000; i++) g_Number++; return 0; } int main() { // 创建两个线程 HANDLE hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL); HANDLE hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL); // 等待两个线程执行结束 WaitForSingleObject(hThread1, -1); WaitForSingleObject(hThread2, -1); // 输出修改后的全局变量 printf("%d", g_Number); return 0; }
产生问题的原因
mov eax, dword ptr[Number]
add eax, 1
mov dword ptr[Number],eax
; C语言的单条语句被翻译成了多条汇编代码,二线程的切换可能导致多条代码分开执行
mov eax, dword ptr[Number] [0]: Number(0) eax(0)
add eax, 1 [0]: Number(0) eax(1)
mov dword ptr[Number],eax [0]: Number(1) eax(1)
mov eax, dword ptr[Number] [1]: Number(1) eax(1)
add eax, 1 [1]: Number(1) eax(2)
mov dword ptr[Number],eax [1]: Number(2) eax(1)
mov eax, dword ptr[Number] [0]: Number(2) eax(2)
add eax, 1 [0]: Number(2) eax(3) -----------
mov eax, dword ptr[Number] [1]: Number(2) eax(2)
add eax, 1 [1]: Number(2) eax(3)
mov dword ptr[Number],eax [1]: Number(3) eax(3)
mov eax, dword ptr[Number] [1]: Number(3) eax(3)
add eax, 1 [1]: Number(3) eax(4)
mov dword ptr[Number],eax [1]: Number(4) eax(4)
mov dword ptr[Number],eax [0]: Number(3) eax(3) -----------
原子操作(Interlocked...)
-
特点:将一条语句转换成了具有同等功能的单条汇编指令
lock inc dword ptr [Number]
-
缺点:只能给单个整数类型(4/8)进行保护,不能给使一段代码变成原子操作
-
函数:
-
InterlockedXXX
-
for (int i = 0; i < 100000; i++) { // 使用原子操作函数,将自增操作变为不可分割的一条指令 InterlockedIncrement(&g_Number); // 以上语句会被翻译成下列单条汇编指令 // lock inc dword ptr [g_Number] }
临界区(CriticalSection)
-
特点:拥有线程拥有者的概念,同一个线程可以不断的重新进入临界区,但是进入了多少次,就要退出多少。
-
缺点:一旦拥有临界区的线程崩溃,那么所有等待临界区的线程就会产生死锁。
-
函数:
-
初始化: InitializeCriticalSection
-
保护:EnterCriticalSection
-
结束保护 :LeaveCriticalSection
-
删除:DeleteCriticalSection
-
// 1. 创建一个临界区(关键段)结构体 CRITICAL_SECTION CriticalSection = { 0 }; // 2. 在 【main】 函数中对创建的临界区进行初始化操作 // InitializeCriticalSection(&CriticalSection); DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) { // 为 g_Number 自增 100000 次 for (int i = 0; i < 100000; i++) { // 当有一个线程正在执行代码的时候 // 同一个线程每进入一次受保护的区域,RecursionCount +1 // OwningThread 当前被哪一个线程所有 // 3. 使用 EnterCriticalSection 标识需要保护的代码的起始位置 EnterCriticalSection(&CriticalSection); g_Number++; // 4. 使用 LeaveCriticalSection 标识需要保护的代码的结束位置 LeaveCriticalSection(&CriticalSection); } return 0; }