陕西省建设厅网站查询,网站推广基本方法是,权威网站优化价格,电商网站的相同点我们在上一章节中讲解了关于Windows的线程基础#xff0c;相信大家已经对线程有了基本的概念。这一章节中#xff0c;我们来讲讲线程同步技术#xff0c;包括加锁技术#xff08;原子锁和互斥体#xff09;和事件#xff0c;信号量。 文章目录 一.原子锁二.互斥体三.事件… 我们在上一章节中讲解了关于Windows的线程基础相信大家已经对线程有了基本的概念。这一章节中我们来讲讲线程同步技术包括加锁技术原子锁和互斥体和事件信号量。 文章目录 一.原子锁二.互斥体三.事件四.信号量 一.原子锁
原子锁主要解决的问题是多线程在操作符方面的问题。
相关问题 多个线程对同一个数据进行原子操作时会产生结果丢失比如运算符
我们来写一段代码看看多线程在操作同一个数据的时候出现的问题
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
int g_value 0;int main() {DWORD nID 0;HANDLE hThread1 CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);HANDLE hThread2 CreateThread(NULL, 0, ThreadProc2, NULL, 0, nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf(%d\n, g_value);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {for (int i 0; i 100000000; i) {g_value;}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {for (int i 0; i 100000000; i) {g_value;}return 0;
}代码解释 我们创建两个线程同时对全局变量g_value进行自增操作两个线程分别自增100000000次那么最后结果就应该是200000000我们来看看执行结果 我们发现最后结果并不是200000000那么是为什么呢 我们来分析一下错误 当线程A执行g_value时如果线程切换正好是在线程A将结果保存到g_value之前线程B继续执行g_value那么当线程A再次被切换回来之后会继续上一步的操作继续将值保存到g_value中线程B的计算结果被覆盖 通俗点来说就是线程A计算好了g_value的结果但是还没有保存到g_value这时候线程切换到了B线程线程B完成了计算并且成功保存当返回到A线程的时候A线程会继续上一步的保存操作那么B线程的计算结果就被覆盖掉了。
那么如何来解决这样的问题呢那就要用到我们的线程同步技术—原子锁了
原子锁函数 InterlockedIncrement() InterlockedDecrement() InterlockedCompareExcahnge() InterlockedExchange() 我们在上文中提到原子锁主要针对的是运算符的问题每一种运算符都有原子锁函数 我们来看看使用效果这里以运算符为例
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value 0;int main() {DWORD nID 0;HANDLE hThread[2] { 0 };hThread[0] CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);hThread[1] CreateThread(NULL, 0, ThreadProc2, NULL, 0, nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf(%d\n, g_value);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {for (int i 0; i 100000000; i) {InterlockedIncrement(g_value);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {for (int i 0; i 100000000; i) {InterlockedIncrement(g_value);}return 0;
}我们来看看执行效果 我们可以发现当我们使用原子锁的时候两个线程操作同一个数据就不会出现结果丢失的问题了。但是我们也不难发现执行结果慢了很多这是因为执行过程中多了很多等待事件这个等待我们在互斥中会讲到。 原子锁的实现直接对数据所在的内存操作并且在任何一个瞬间只能有一个线程访问
二.互斥体 相关问题 跟原子锁一样都是解决多线程下资源的共享使用但是与原子锁不同的是互斥体解决的是代码资源的共享使用。 互斥体的使用 创建互斥体 使用CreateMutex函数 MSDN官方文档解释
HMODLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全性BOOL bInitialOwner, //初始拥有者LPCSTR lpName //为互斥命名
);参数bInitialOwner介绍 如果此值为 TRUE 并且调用方创建了互斥体则调用线程获取互斥体对象的初始所有权。 否则调用线程不会获取互斥体的所有权。 互斥体特性介绍 在任何一个时间点上只能由一个线程拥有互斥体当前任何一个线程不拥有互斥体是互斥体句柄有信号谁先等候互斥体谁先获取 等候互斥体 上一篇介绍过了使用等候句柄函数。WaitFor... 释放互斥体
BOOL ReleaseMutex(HANDLE hMutex //handle of Mutex
);关闭互斥体 使用CloseHandle函数 我们来看看使用互斥体来解决我们在多线程中遇到的问题
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value 0;
HANDLE hMutex NULL; //用于接收互斥体句柄int main() {DWORD nID 0;HANDLE hThread[2] { 0 };//hMutex CreateMutex(NULL, FALSE, NULL);hThread[0] CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);hThread[1] CreateThread(NULL, 0, ThreadProc2, NULL, 0, nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf(%d\n, g_value);CloseHandle(hMutex);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] ********;while (1) {//WaitForSingleObject(hMutex, INFINITE);for (int i 0; i strlen(a); i) {printf(%c, a[i]);Sleep(125);}printf(\n);//ReleaseMutex(hMutex);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {char b[] --------;while (1) {//WaitForSingleObject(hMutex, INFINITE);for (int i 0; i strlen(b); i) {printf(%c, b[i]);Sleep(125);}//ReleaseMutex(hMutex);printf(\n);}return 0;
}我们来看看不适用互斥体技术的时候的输出 我们再来看看使用了互斥体之后
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value 0;
HANDLE hMutex NULL; //用于接收互斥体句柄int main() {DWORD nID 0;HANDLE hThread[2] { 0 };hMutex CreateMutex(NULL, FALSE, NULL);hThread[0] CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);hThread[1] CreateThread(NULL, 0, ThreadProc2, NULL, 0, nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf(%d\n, g_value);CloseHandle(hMutex);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] ********;while (1) {WaitForSingleObject(hMutex, INFINITE);for (int i 0; i strlen(a); i) {printf(%c, a[i]);Sleep(125);}printf(\n);ReleaseMutex(hMutex);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {char b[] --------;while (1) {WaitForSingleObject(hMutex, INFINITE);for (int i 0; i strlen(b); i) {printf(%c, b[i]);Sleep(125);}ReleaseMutex(hMutex);printf(\n);}return 0;
}我们可以发现使用互斥体之后对代码段进行了枷锁。 我们来大致讲解一下互斥体的实现吧 我们在主进程中创建了互斥体并且互斥体不归之进程所有两个线程谁先等待互斥体句柄谁就拥有了互斥体那么当线程跳转到另一个线程之后发现被锁定在了另一个线程那么线程就会被阻塞直到线程再次跳转到另一个线程执行完之后互斥体被释放这时候跳转到这个线程在这个线程中再进行加锁这个线程执行完之后再锁定到另一个线程这样就实现了加锁技术。 三.事件
前两个技术都属于加锁技术即两个线程互斥的时候使用那么线程也会有协调工作的时候这时候就需要用到我们的事件和信号量了。 相关问题 多线程协调工作的时候的通知问题 事件的使用 创建事件 使用CreatEvent函数MSDN官方解释
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性BOOL bManualReset, //事件重置/复位方式BOOL bInitialState, //事件初始状态LPCSTR lpName //为事件命名
);参数解释 bManualReset为事件重置/复位方式如果该参数被设置为TRUE那么就需要我们来手动重置事件对象如果该参数被设置为FALSE那么操纵系统会帮我们完成事件的重置和复位。bInitialState该参数指定了当创建事件后该事件句柄是否处于有消息状态 等候事件 WaitFor......函数触发事件使事件句柄处于有消息状态
BOOLSetEvent(HANDLE hEvent
);复位事件将事件句柄设置为无消息状态
BOOL ResetEvent(HANDLE hEvent
);关闭事件 CloseHandle函数 我们来看看事件的使用
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value 0;
HANDLE hEvent NULL; //用于接收事件句柄int main() {DWORD nID 0;HANDLE hThread[2] { 0 };hEvent CreateEvent(NULL,FALSE,0,NULL);hThread[0] CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);hThread[1] CreateThread(NULL, 0, ThreadProc2, NULL, 0, nID);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);printf(%d\n, g_value);CloseHandle(hEvent);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] ********;while (1) {WaitForSingleObject(hEvent, INFINITE);for (int i 0; i strlen(a); i) {printf(%c, a[i]);}printf(\n);ResetEvent(hEvent);}return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter) {while (1) {Sleep(1000);SetEvent(hEvent);}return 0;
}这是一个很典型的相互协调工作的双线程我们在A线程中没有设定时间间隔但是在B线程中设定了事件间隔我们能够很明显地感受到输出是有时间间隔的
四.信号量
相关问题 类似于事件解决线程之间通知的相关问题但提供一个计数器可以设置次数。创建信号量 使用CreateSemaphore函数 MSDN官方解释
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUIES lpSemaphoreAttributes, //安全属性LONG lInitialCount, //初始化信号量数量LONG lMaximumCount, //信号量的最大值LPSTSTR lpName //为信号量命名
);创建成功返回信号量句柄等候信号量 使用WaitFor...函数 注意 等候每通过一次信号量的信号减一知道为0阻塞给信号量指定定计数值 使用ReleaseSemaphore函数 MSDN官方解释
BOOL ReleaseSemaphore(HANDLE hSeamephore, //信号量句柄LONG lReleaseSemaphore, //信号量将增加的量LPONG lpPreviousCount //指向一个变量的指针用于记录信号量的上一个计数
);关闭信号量 使用CloseHandle()函数 我们来看看信号量的使用实例
#include stdio.h
#include windows.hDWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
HANDLE hSemaphore NULL; //用于接收事件句柄int main() {DWORD nID 0;HANDLE hThread[2] { 0 };hSemaphore CreateSemaphore(NULL, 3, 10, NULL);hThread[0] CreateThread(NULL, 0, ThreadProc1, NULL, 0, nID);while (getchar()\n) {ReleaseSemaphore(hSemaphore, 5, NULL);}CloseHandle(hSemaphore);return 0;
}DWORD WINAPI ThreadProc1(LPVOID lpParameter) {char a[] ********;while (1) {WaitForSingleObject(hSemaphore, INFINITE);for (int i 0; i strlen(a); i) {printf(%c, a[i]);}printf(\n);}return 0;}我们设置了计数器为3的信号量我们发现程序最开始只会输出三行每当我们按一次回车键就将信号量计数值值为5 本篇文章的分享就到这里如果大家发现有错误之处还请大家指出来我会非常虚心地学习。希望我们共同进步