Windows
同步
多线程共享资源同步原因
以一个双线程计数为例:
建立一个控制台应用:
线程回调:
创建两个线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
#include<Windows.h> #include <iostream> using namespace std;
int g_nCounter = 0;
DWORD WINAPI ThreadFunc(LPVOID lpParam) { for (int i = 0; i < 0x10000; i++) { ++g_nCounter; } printf("tid:%08x counter:%08x \r\n", GetCurrentThreadId(), g_nCounter);
return 0; }
int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2]; hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World!\n"; }
|
两个线程运行结果应该为00020000 但是实际上无法达到 由于多线程的同步问题
++g_nCounter:
多线程同步可能发生的情况:
当线程1执行到加1时 线程被切到线程2 线程2执行完 再切回线程1 而线程1在被切走的时候内存为10 接着执行就会变成11(环境保存和恢复) 相当于两次加1 实际上就只有一次
三环同步
原子操作
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
例如刚才的++g_nCounter 为三步操作 这三步使用原子操作时 必须全部执行 不能中途切换
windows原子操作API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<Windows.h> #include <iostream> using namespace std;
LONG g_nCounter = 0;
DWORD WINAPI ThreadFunc(LPVOID lpParam) { for (int i = 0; i < 0x10000; ++i) { InterlockedIncrement( &g_nCounter); } printf("tid:%08X counter:%08X \r\n", GetCurrentThreadId(), g_nCounter);
return 0; }
int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2]; hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World!\n"; }
|
由于两个线程是同时进行的(不是先做完线程1 再做线程2 ,二者切换)所以线程1最后的结果并不是00010000
临界区(关键段)
当遇到更加复杂的情况多个线程同时修改了 vector
或者其它需要保护的共享资源时原本上述的API就不适用了 这时候就得使用临界区(同进程)
进入临界区:
离开临界区:
初始化(一次即可):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
#include<Windows.h> #include <iostream> #include<vector> using namespace std; #pragma pack(1)
vector<int> g_vct; CRITICAL_SECTION g_csForVct; DWORD WINAPI ThreadFunc(LPVOID lpParam) { for (int i = 0; i < 0x1000; ++i) { EnterCriticalSection(&g_csForVct); g_vct.push_back(i); LeaveCriticalSection(&g_csForVct); } printf("tid:%08X counter:%08X \r\n", GetCurrentThreadId(), g_vct.size()); return 0; }
int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2]; InitializeCriticalSection(&g_csForVct); hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World!\n"; DeleteCriticalSection(&g_csForVct); }
|
运行结果:
如果在main
函数中添加EnterCriticalSection(&g_csForVct);
而没有LeaveCriticalSection(&g_csForVct);
会使得两个进程都挂起 二者都在等待LeaveCriticalSection(&g_csForVct);
的调用
调用几次EnterCriticalSection(&g_csForVct);
相应的就调用几次LeaveCriticalSection(&g_csForVct);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2]; InitializeCriticalSection(&g_csForVct); hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World!\n"; DeleteCriticalSection(&g_csForVct); }
|
内核同步对象
实现内核和三环之间的同步 由内核提供 可以做到跨进程同步
事件(Event)
1 2 3 4 5
| CreateEvent SetEvent ResetEvent WaitForSingleObject WaitForMultipleObjects
|
WaitForSingleObject
:
signaled(有信号)-开关, 开
nonsignaled(无信号) = 开关, 关
线程和进程内核对象:
活着的时候,无信号
死了的时候,有信号
创建事件:
参数2:如果此参数为 TRUE,则函数将创建手动重置事件对象,该对象需要使用 ResetEvent
函数将事件状态设置为无信号。
如果此参数为 FALSE,则函数将创建一个自动重置事件对象,在释放单个等待线程后,系统会自动将事件状态重置为无信号。
将事件设置为有信号状态
将事件设置为无信号状态
等待内核同步对象 若内核同步对象为无信号就会阻塞 直到对象变为有信号 或者等到参数2的时间到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include<Windows.h> #include <iostream> #include<vector> using namespace std; #pragma pack(1)
vector<int> g_vct; HANDLE g_hEvent; DWORD WINAPI ThreadFunc(LPVOID lpParam) { for (int i = 0; i < 0x1000; ++i) { WaitForSingleObject(g_hEvent, INFINITE); g_vct.push_back(i); SetEvent(g_hEvent); } printf("tid:%08X counter:%08X \r\n", GetCurrentThreadId(), g_vct.size());
return 0; }
int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2];
g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World" << endl; }
|
区分WaitForSingleObject
是有信号还是超时:
等待多个信号(最多为64):
互斥体
1 2 3
| CreateMutex; OpenMutex; ReleaseMutex;
|
线程拥有权:任何线程在进入临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。
线程遗弃 :当拥有临界区的互斥体退出前,没有释放互斥体,那么系统会自动的将互斥体对象的线程ID设置为0,递归计数设置为0.等待函数返回WAIT_ABANDONED
而不是 WAIT_OBJECT_0
,但是与互斥体不一样,事件的等待函数便会一直等待
参数2:当前创建互斥体的线程是否拥有互斥体的使用权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include<Windows.h> #include <iostream> #include<vector> using namespace std; #pragma pack(1)
vector<int> g_vct; HANDLE g_hMutex; DWORD WINAPI ThreadFunc(LPVOID lpParam) { for (int i = 0; i < 0x1000; ++i) { WaitForSingleObject(g_hMutex, INFINITE); g_vct.push_back(i); ReleaseMutex(g_hMutex); } printf("tid:%08X counter:%08X \r\n", GetCurrentThreadId(), g_vct.size());
return 0; }
int main() { DWORD dwThreadId, dwThrdParam = 1; HANDLE hThread[2];
g_hMutex = CreateMutex(NULL, FALSE, NULL ); WaitForSingleObject(g_hMutex, INFINITE); WaitForSingleObject(g_hMutex, INFINITE); ReleaseMutex(g_hMutex); ReleaseMutex(g_hMutex); hThread[0] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
hThread[1] = CreateThread( NULL, 0, ThreadFunc, &dwThrdParam, 0, &dwThreadId);
system("pause"); cout << "Hello World" << endl; }
|