Windows线程控制
多线程无疑带来了很多方便,提高了很多开发效率,但是同时也带来了很多问题。
举个栗子:
DWORD WINAPI ThreadProc(LPVOID lPParameter); int m = 0;
int n = 0; int main(int argc, TCHAR* argv[], TCHAR* envp[])
{ HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL); int i = 0;
for (i=0;i<100000;i++)
{
m++;
n++;
}
Sleep(1); cout<<"M:"<<m<<endl;
cout<<"N:"<<n<<endl; return 0;
} DWORD WINAPI ThreadProc(LPVOID lPParameter)
{
int j = 0;
for (j=0;j<100000;j++)
{
m++;
n++;
}
return 0;
}
这段程序的输出结果是多少呢?
按照常理来说不难得出结论:m与n都是20000
可是真正运行结果呢?

结果令人大吃一惊,而且每次结果都不相同!为什么会出现这种情况呢?
A线程在访问全局资源的时候并不能控制B线程对全局资源的访问,A取出内存数据放到寄存器运算时,B也取出内存数据放到寄存 器运算,那么有可能造成重复运算!也就是说A正在运算的时候,还没把运算结果放回来,B就也行运算了。所以最终结果比 200000小并且m与n都不相同。
为了控制线程不要紊乱要按照我们本意的协调进行,不可避免的就要用到线程控制(互斥/同步)技术!
下面说说线程控制的基本方法:
1.EVENT(事件)
2.Critical Section(临界区)
3.Mutex (互斥体)
4.Semaphore (信号量)
0X01
EVENT(事件)
用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状 态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一 直保持 为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线 程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为 CEvent.。CEvent的构 造函数默认创 建一个自动重置的事件,而且处于未激发状态。改变事件的状态:SetEvent,ResetEvent。
我们看看如何运用事件来解决我们的实际问题:
DWORD WINAPI ThreadProc(LPVOID lPParameter); int m = 0;
int n = 0; int main(int argc, TCHAR* argv[], TCHAR* envp[])
{ HANDLE hEvent = NULL; hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); //FALSE ×Ô¶¯ TRUE ÊÖ¶¯ FALSEÊÇ×Ô¶¯ HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,hEvent,0,NULL); WaitForSingleObject(hEvent,INFINITE); int i = 0;
for (i=0;i<100000;i++)
{
m++;
n++;
}
Sleep(1); cout<<"M:"<<m<<endl;
cout<<"N:"<<n<<endl; return 0;
} DWORD WINAPI ThreadProc(LPVOID lPParameter)
{
HANDLE hEvent = NULL; hEvent = (HANDLE)lPParameter; int j = 0;
for (j=0;j<100000;j++)
{
m++;
n++;
} SetEvent(hEvent); return 0;
}
我们看看结果对不对

结果正确!
0X02
Critical Section(临界区)
CRITICAL_SECTION是最快的。其他内核锁(事件、互斥体),每进一次内核,都需要上千个CPU周期。
使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可 能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如 何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就 是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放 临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。
DWORD WINAPI ThreadProc(LPVOID lPParameter); int m = 0;
int n = 0; CRITICAL_SECTION cs;
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
InitializeCriticalSection(&cs); HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL); int i = 0;
EnterCriticalSection(&cs);
for (i=0;i<100000;i++)
{
m++;
n++;
}
LeaveCriticalSection(&cs); Sleep(1); cout<<"M:"<<m<<endl;
cout<<"N:"<<n<<endl; DeleteCriticalSection(&cs); //死锁 return 0;
} DWORD WINAPI ThreadProc(LPVOID lPParameter)
{ int j = 0;
EnterCriticalSection(&cs);
for (j=0;j<100000;j++)
{
m++;
n++;
}
LeaveCriticalSection(&cs); return 0;
}
同样这样也能获得正确结果!
关于临界区的使用也就是四步走的问题:初始化临界区-->进入临界区-->离开临界区-->销毁临界区。牢记这四部就不会出现 问题了。需要注意的一点是:临界区与递归要小心使用,在临界区递归会造成死锁!
0X03
Mutex (互斥体)
互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象 (Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定 TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此 Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个 Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。
HANDLE hMutex = NULL; int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
hMutex = CreateMutex(NULL,TRUE,NULL); HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL); int i = 0; for (i=0;i<100000;i++)
{
m++;
n++;
} ReleaseMutex(hMutex); Sleep(1); cout<<"M:"<<m<<endl;
cout<<"N:"<<n<<endl; return 0;
} DWORD WINAPI ThreadProc(LPVOID lPParameter)
{ WaitForSingleObject(hMutex,INFINITE); int j = 0;
for (j=0;j<100000;j++)
{
m++;
n++;
} return 0;
}
0X04
Semaphore (信号量)
信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。Win32函数 CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数 加1,当时不会超过初始设定的资源总数。
HANDLE hSemaphore = NULL; int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
hSemaphore = CreateSemaphore(NULL,0,1,NULL); HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL); int i = 0; for (i=0;i<100000;i++)
{
m++;
n++;
} ReleaseSemaphore(hSemaphore,1,NULL); Sleep(1); cout<<"M:"<<m<<endl;
cout<<"N:"<<n<<endl; CloseHandle(hSemaphore); return 0;
} DWORD WINAPI ThreadProc(LPVOID lPParameter)
{ WaitForSingleObject(hSemaphore,INFINITE); int j = 0;
for (j=0;j<100000;j++)
{
m++;
n++;
} return 0;
}
Windows线程控制的更多相关文章
- Java并发1——线程创建、启动、生命周期与线程控制
内容提要: 线程与进程 为什么要使用多线程/进程?线程与进程的区别?线程对比进程的优势?Java中有多进程吗? 线程的创建与启动 线程的创建有哪几种方式?它们之间有什么区别? 线程的生命周期与线程控制 ...
- 第11章 Windows线程池(2)_Win2008及以上的新线程池
11.2 Win2008以上的新线程池 (1)传统线程池的优缺点: ①传统Windows线程池调用简单,使用方便(有时只需调用一个API即可) ②这种简单也带来负面问题,如接口过于简单,无法更多去控制 ...
- windows线程同步
一.前言 之前在项目中,由于需要使用到多线程,多线程能够提高执行的效率,同时也带来线程同步的问题,故特此总结如下. 二.windows线程同步机制 windows线程同步机制常用的有几种:Event. ...
- Win32多线程编程(2) — 线程控制
Win32线程控制只有是围绕线程这一内核对象的创建.挂起.恢复.终结以及通信等操作,这些操作都依赖于Win32操作系统提供的一组API和具体编译器的C运行时库函数.本篇围绕这些操作接口介绍在Windo ...
- 零基础逆向工程34_Win32_08_线程控制_CONTEXT结构
线程控制 实验 挂起线程 ::SuspendThread(hThread); 恢复线程 ::ResumeThread(hThread); 终止线程 (这里讲了同步调用与异步调用) 方式一: 此方法结束 ...
- 孤荷凌寒自学python第三十八天初识python的线程控制
孤荷凌寒自学python第三十八天初识python的线程控制 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 一.线程 在操作系统中存在着很多的可执行的应用程序,每个应用程序启动后,就可以看 ...
- Windows线程生灭(图文并茂)
一.线程创建 Windows线程在创建时会首先创建一个线程内核对象,它是一个较小的数据结构,操作系统通过它来管理线程.新线程可以访问进程内核对象的所有句柄.进程中的所有内存及同一进程中其它线程的栈. ...
- Windows线程+进程通信
一 Windows线程进程 1)定义 按照MS的定义, Windows中的进程简单地说就是一个内存中的可执行程序, 提供程序运行的各种资源. 进程拥有虚拟的地址空间, 可执行代码, 数据, 对象句柄集 ...
- 利用 Windows 线程池定制的 4 种方式完成任务(Windows 核心编程)
Windows 线程池 说起底层的线程操作一般都不会陌生,Windows 提供了 CreateThread 函数来创建线程,为了同步线程的操作,Windows 提供了事件内核对象.互斥量内核对象.关键 ...
随机推荐
- [atARC121F]Logical Operations on Tree
(特判$n=1$的情况) 当确定权值和操作后,如何判定是否合法-- 考虑一个度为1的节点,对其权值即其对应边的边操作分类讨论: $1\or$,显然只需要最后选择这条边即可,一定合法 $1\and$或$ ...
- [atARC119D]Grid Repainting 3
将每一行和每一列分别作为一个点,当第$i$行第$j$列的格子为红色时,将第$i$行与第$j$列连边 此时,考虑选择第$i$行的红色格子并将第$i$行的格子全部改成白色: 关于这一操作的条件,即需要第$ ...
- [cf1217F]Forced Online Queries Problem
可以用并查集维护连通性,删除可以用按置合并并查集,但删掉一条边后无法再维护两点的联通性了(因为产生环的边是不加入的)暴力思路是, 考虑前i个操作后边的集合,暴力加入即可,但复杂度是$o(n^2)$的用 ...
- Plugin [id: 'org.jetbrains.kotlin.jvm'] was not found in any of the following sources: gradle配置:kotlin("jvm")后报错
本来打算兼容java和kotlin,可配置后,项目报错.查看之前项目 再打开当前报错项目: 很明显,报错的原因是jvm的运行文件没有加载进来,多次尝试无果... 只能重新搭建初始化项目了.
- 【Microsoft Azure 的1024种玩法】七.Azure云端搭建部署属于自己的维基百科
[简介] MediaWiki是全球最著名的开源wiki程序,运行于PHP+MySQL环境.MediaWiki从2002年2月25日被作为维基百科全书的系统软件,并有大量其他应用实例.MediaWiki ...
- 计算机系统->Hello World的一生 | 程序如何运行
2021年11月27日准备发在基地微信公众号上的推文. 综合了多篇大佬的博客,以及自己已经知道的知识,对一些疑惑进行了现阶段我认为还算满意的解答. 不过又产生了很多疑问: 内存和磁盘的关系 CPU是如 ...
- Linux系统编程之匿名管道
1.进程间通信介绍 1.1 进程通信的基本概念 在之前我们已经学习过进程地址空间.Linux 环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不 ...
- 洛谷 P6783 - [Ynoi2008] rrusq(KDT+势能均摊+根号平衡)
洛谷题面传送门 首先显然原问题严格强于区间数颜色,因此考虑将询问离线下来然后用某些根号级别复杂度的数据结构.按照数颜色题目的套路,我们肯定要对于每种颜色维护一个前驱 \(pre\),那么答案可写作 \ ...
- 洛谷 P5853 - [USACO19DEC]Tree Depth P(生成函数+背包)
洛谷题面传送门 神仙题. 首先考虑一个点的深度是什么,注意到对于笛卡尔树而言直接从序列的角度计算一个点的深度是不容易的,因为这样会牵扯到序列中多个元素,需要 fixed 的东西太多,计算起来太复杂了. ...
- Python 包管理工具 pip 与 conda
简介 pip是接触 python 后最早认识的包管理工具.通过使用 pip 能够自动下载和解决不同 python 模块的依赖问题,使 python 的配置过程变得简单. 与 pip 类似,conda ...