Windows下多线程编程(二)
线程的分类
1. 有消息循环线程
- MFC中有用户界面线程,从CWinThread派生出一个新的类作为UI线程类CUIThread,然后调用AfxBeginthread(RUNTIME_CLASS(CUIThread));启动线程。UI线程可以直接创建模态对话框,而不用担心消息循环的问题,因为UI线程默认自带消息循环。
- MFC非用户界面线程,不能创建模态对话框,但是可以创建非模态对话框或普通窗口,但是必须自己写消息循环。
MSG msg;
while(GetMessage(&msg, NULL, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
2. 无消息循环线程
- MFC中的工作者线程
- 其他没有加消息循环的普通线程。
线程间的通信
1. 共享内存变量
l 因为线程是共享进程内存的,所以通过全局/静态变量来进行通信效率最最高的。参数需要考虑是否加volitile。
l 通过传递的参数,如引用和指针。参数需要考虑是否加volitile。
2. 消息通知
- 如果是子线程向主线程通信,因为主线程有消息循环,所以子线程可以通过发送消息来向主线程通信。通过消息通信能够避免使用全局变量带来的耦合性。
SendMessage必须等待消息函数处理完成才返回,PostMessage则直接将消息放入消息队列立即返回。所以SendMessage的消息参数可以是临时变量,而PostMessage的消息参数必须保证足够的生存周期。
- 如果子线程有自定义的消息循环,也可以通过PostThreadMessage来指定线程通信。
while(true)
{
if(GetMessage(&msg,,,)) //get msgfrom message queue
{
switch(msg.message)
{
case MY_MSG:
// Todo:
break;
}
}
};
3. 其他方式
- 所有跨进程的通信方式,当然可以用于跨线程了。
线程之间的状态
1. 异步
即多个线程彼此独立,不受外部线程的影响。线程本身就是实现异步的一种方式。
2. 同步
即多个线程彼此依赖,线程A的计算结果是线程B的计算的前提,也就是说在开始线程B的计算之前必须等待线程A的计算完。
3. 互斥
即多个线程在操作同一个资源时,一个线程必须等另一个线程结束了才能继续操作。互斥与同步不同之处是,互斥没有先后关系。同一个资源,可以指全局变量,也可以指一个文件对象或是其他的内核对象。因为内核对象是跨进程的,所以更是跨线程的。
等待函数
1. 概念
WaitForSingleObject函数是等待内核对象从无信号状态到有信号状态或是超时即返回。也即无信号状态时等待,有信号或超时立即返回。
WaitForMulitpleObjects函数是等待多个内核对象从无信号状态到有信号状态或是超时即返回(可以指明是所有对象或是任一对象)。
Windows拥有几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出/错误流、事件、等待定时器、信号量、互斥对象。
2. 等待函数与内核对象之间的关系
对象 |
无信号状态 |
有信号状态 |
成功等待副作用 |
进程 |
进程活动时 |
进程终止时 |
无 |
线程 |
线程活动时 |
线程终止时 |
无 |
文件 |
I/O请求正在处理时 |
I/O请求结束时 |
无 |
控制台输入 |
不存在任何输入 |
存在输入时 |
无 |
文件修改通知 |
没有任何文件修改通知 |
文件系统发现修改时 |
重置通知 |
自动重置事件 |
ResetEvent, PulseEvent或等待成功 |
当调用SetEvent或PulseEvnet时 |
重置事件 |
人工重置事件 |
ResetEvent,或PulseEvent |
当调用SetEvent或PulseEvnet时 |
无 |
自动重置定时器 |
CancelWaitableTimer或等待成功 |
当时间到时(SetWaitableTimer) |
重置定时器 |
人工重置定时器 |
CancelWaitableTimer |
当时间到时(SetWaitableTimer) |
无 |
信号量 |
等待成功 |
当资源数量>0时(ReleaseSemaphore) |
数量减1 |
互斥量 |
等待成功 |
当未被线程拥有时(ReleaseMutex) |
获取线程所有权 |
l 线程和进程创建及运行时都是无信号状态,当结束运行时变为有信号状态。
l 自动重置的事件(FALSE)对象,当等待成功的时候,会被修改为无信号状态。
l 信号量对象,当调用ReleaseSemaphore(数量加1),处于有信号状态,WaitForSingleObject会被触发并且立即将信号数量减1.
用户模式与内核模式的优缺点
1. 用户模式
优点:线程同步机制速度快
缺点:容易陷入死锁状态多个进程之间的线程同步会出现问题。(比如竞争资源、死锁)
2. 内核模式
优点:支持多个进程之间的线程同步,防止死锁
缺点:线程同步机制速度慢,线程必须从用户模式转为内核模式。这个转换需要很大的代价:往返一次需要占用x 8 6平台上的大约1 0 0 0个C P U周期。
线程间的状态处理
1. 线程的异步
因为线程本身就是异步的。
2. 线程的同步
线程的同步主要是通过事件(Event)内核对象、信号量(Semaphore)内核对象和互斥量(Mutex)内核对象。因为都是内核对象,所以不仅可以跨线程操作,还可以跨进程同步。
1. 线程的同步
线程的同步主要是通过事件(Event)内核对象、信号量(Semaphore)内核对象和互斥量(Mutex)内核对象。因为都是内核对象,所以不仅可以跨线程操作,还可以跨进程同步。
事件(Event)内核对象
事件分两种类型:人工重置事件和自动重置事件,前者在触发WaitForSingleObject之后需要手动调用ResetEvent将事件设置为无信号;而后者在触发WaitForSingleObject之后自动将事件设置为无信号状态。
常用函数:
CreateEvent,创建事件对象。
OpenEvent,打开已经创建的事件对象,可以跨进程打开。
SetEvent,将事件对象设置为有信号状态。
ResetEvent,将事件对象设置为无信号状态。
PulseEvent,将事件对象设置为有信号状态,然后又设置为无信号状态,此函数不常用。
HANDELg_hEvent;
int Main()
{
g_hEvent =CreateEvent(NULL, TRUE, FALSE, NULL);
_beginthreadex(NULL,, ThreadFun1, );
_beginthreadex(NULL,, ThreadFun2, );
SetEvnet(g_hEvent);//
} DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hEvent);
// Todo...
SetEvent(g_hEvnet);
return ;
} DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hEvent);
// Todo...
SetEvent(g_hEvnet); return ;
}
注意:如果上面创建的是人工重置事件,则两个线程函数都将执行。如果是自动重置事件,则只能执行一个线程,且不能保证哪一个线程先执行。如果要保证一个线程先执行,可以添加事件对象用来确保指定线程已经执行,不能通过代码的先后顺序确保线程已经执行。
2. 信号量(Semaphore)内核对象
信号量的使用规则:
当前信号量资源数大于0,则标记为有信号状态。
当前信号量资源数为0,则标记为无信号状态。
信号量资源数不能为负,且最大不能超过指定数量。
常用函数:
CreateSemaphore,创建信号量对象。
OpenSemaphore,打开指定信号量对象,可以跨进程。
ReleaseSemaphoer,资源计算加1。
HANDELg_hSema[]; int Main()
{
g_hSema[] =CreateSemaphore(NULL, , , NULL); g_hSema[] =CreateSemaphore(NULL, , , NULL); _beginthreadex(NULL,, ThreadFun1, ); _beginthreadex(NULL,, ThreadFun2, );
} DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hSema[]); // Todo...
ReleaseSemaphoer(g_hSema[]); return ;
} DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hSema[]); // Todo...
ReleaseSemaphoer(g_hSema[]); return ;
}
这样就能够保证ThreadFun1执行完了,再执行ThreadFun2,然后再执行ThreadFun1,并且保证每个线程函数只能被调用一次.
3. 互斥量(Mutex)内核对象
互斥量内核对象确保线程拥有单个资源的互斥访问权。在行为特性上,互斥量与临界区的一样。只不过,互斥量是内核对象,使用时需要从用户模式切换到内核模式,比较耗时。但正因为是内核对象,所以互斥量能够跨进程,并且能够设置超时时间,这是它比临界区灵活的地方。
常用函数:
CreateMutex,创建互斥量对象。
OpenMutex,打开指定互斥量对象,可以跨进程。
ReleaseMutex,释放互斥量,对象被标记为有信号状态,触发WaitForSingleObject。
互斥量和临界区一样,拥有一个线程拥有权的概念,即当前互斥量和当前临界区的释放只能由当前线程释放,其他线程释放无效。因为互斥量是内核对象,如果线程已经终止,但是其所属的互斥量依然没有释放,内核管理器会自动释放。临界区没有这个功能,因为临界区不是内核对象,所以临界区如果没有正确释放会导致死锁。
HANDLECreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes,
BOOL bInitialOwner, LPCTSTR lpName);
bInitialOwner标记是否由创建线程拥有线程所有权,TRUE表示创建者拥有,FALSE表示创建者不拥有,则是第一个调用WaitForSingleObject的线程将获得线程所有权。
HANDELg_hMutex; int Main()
{
g_hMutex =CreateMutex(NULL,FALSE); _beginthreadex(NULL,, ThreadFun1, ); _beginthreadex(NULL,, ThreadFun2, );
} DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hMutex); // Todo... ReleaseMutex(g_hMutex); return ;
} DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hMutex); // Todo...
ReleaseMutex(g_hMutex); return ;
}
两个函数谁先调用,谁即获取线程所有权。如果想指定线程先运行,需要判断指定线程已经执行之后再创建新线程,不能依靠线程的代码创建先后顺序。
3. 线程的互斥
像互斥量对象同样可以达到互斥的效果,只是互斥量功能更丰富,并且如果是简单的资源互斥,使用临界区的效率更优。
临界区(Critical Section)是一段供线程独占式访问的代码,也就是说若有一线程正在访问该代码段,其它线程想要访问,只能等待当前线程离开该代码段方可进入,这样保证了线程安全。他工作于用户级(相对于内核级),在Window系统中CRITICAL_SECTION实现临界区相关机制。
常用函数:
voidInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 初始化临界区
voidEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 进入临界区
voidLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 离开临界区
voidDeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 释放临界区资源
因为临界区拥有线程所有权这个概念,即进入临界区的线程才有权释放临界区。因为必须当前线程进入和释放,更多的时候,临界区是在一个函数里使用,为了确保不会由于中间退出函数导致没有释放,我们可以用下列方式来确保释放。
class Mutex {
public:
Mutex() {InitializeCriticalSection(section); } ~Mutex() { DeleteCriticalSection(section);} void Enter() {EnterCriticalSection(section); } void Leave() {LeaveCriticalSection(section); } struct Lock; protected:
Mutex(const Mutex&); Mutex& operator=(const Mutex&); CRITICAL_SECTION section;
}; structMutex::Lock {
Mutex& s; Lock(Mutex& s) : s(s) { s.Enter(); } ~Lock() { s.Leave(); }
}; DWORD WINAPIThreadFun(PVOID pParam)
{
Mutex::Locklock(mutex); // Todo... return ;
}
注意
1. 注意所有内核对象在结束时都需要调用closeHandle()。
2. 跨线程调用MFC对象函数都是不安全的。因为MFC对象的一些函数都与TLS有关联, 所以有些调用会出错。如UpdateData(),最好通过句柄发消息来完成相应的功能。
Windows下多线程编程(二)的更多相关文章
- Windows下多线程编程(一)
前言 熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. ...
- Windows 下多线程编程技术
(1) 线程的创建:(主要以下2种) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID lParam, int nPrio ...
- Windows环境下多线程编程原理与应用读书笔记(1)————基本概念
自从学了操作系统知识后,我就对多线程比较感兴趣,总想让自己写一些有关多线程的程序代码,但一直以来,发现自己都没怎么好好的去全面学习这方面的知识,仅仅是完成了操作系统课程上的小程序,对多线程的理解也不是 ...
- 【转】Windows的多线程编程,C/C++
在Windows的多线程编程中,创建线程的函数主要有CreateThread和_beginthread(及_beginthreadex). CreateThread 和 ExitThread 使 ...
- 初尝Windows 下批处理编程
本文叫“ 初尝Windows 下批处理编程”是为了延续上一篇“初尝 Perl”,其实对于博主而言批处理以及批处理编程早就接触过了. 本文包括以下内容 1.什么是批处理 2.常用批处理命令 3.简介批处 ...
- Windows下串口编程
造冰箱的大熊猫@cnblogs 2019/1/27 将Windows下串口编程相关信息进行下简单小结,以备后用. 1.打开串口 打开串口使用CreateFile()函数.以打开COM6为例: HAN ...
- 初探WINDOWS下IME编程
初探WINDOWS下IME编程作者:广东南海市昭信科技有限公司-李建国 大家知道,DELPHI许多控件有IME属性.这么好用的东西VC可没自带,怎么办呢?其实,可通过注册表,用API实现.下面说一下本 ...
- Linux下多线程编程
一.为什么要引入线程? 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式.在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维 ...
- Windows下GUI编程——窗口
windows下创建一个基于GUI的窗口程序很简单,使用MFC或者Win32 API都可以实现.本文简单整理下windows API创建GUI应用程序的基本编码框架. 比较常见的窗口包括:桌面窗口.应 ...
随机推荐
- django博客项目-设置django为中文语言
找到项目级别里面的setting文件,修改如下配置 """ LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' & ...
- POJ 2932 Coneology计算最外层圆个数
平面上有n个两两没有公共点的圆,i号圆的圆心在(xi,yi),半径为ri,编号从1开始.求所有最外层的,即不包含于其他圆内部的圆.输出符合要求的圆的个数和编号.n<=40000. (注意此题无相 ...
- MVC的BundleConfig应用
1.MVC可以通过BundleConfig类来配置css和js的统一引用,分别通过StyleBundle和ScriptBundle来创建. 2.可以在母版页中统一加载设置在BundleConfig.c ...
- HDU - 4118 Holiday's Accommodation
Problem Description Nowadays, people have many ways to save money on accommodation when they are on ...
- [Usaco2005 Open]Disease Manangement 疾病管理 BZOJ1688
分析: 这个题的状压DP还是比较裸的,考虑将疾病状压,得到DP方程:F[S]为疾病状态为S时的最多奶牛数量,F[S]=max{f[s]+1}; 记得预处理出每个状态下疾病数是多少... 附上代码: # ...
- vue-cli 动态绑定图片失败
1.template 中引用图片,第一个为固定路径,第二个为动态绑定路径 eg: <img src="XXXXXX.png" alt=""> < ...
- WPF 初学VisifireChart
visifire今天登陆他们官网的时候,发现好像是挂掉了,不知道是不再运营了,还是单纯服务器出了问题. VisifireChart的效果不炫,但是对于一些项目,感觉够用的,所以,今天大概看了几篇博客, ...
- JS关闭窗口而不提示
使用js关闭窗口而不提示代码: window.opener = null; window.open( '', '_self' ); window.close();
- idea java方法中 传多个参数对象 的复制粘贴快速处理方法
比如像这种的传多个参数对象,我是直接复制过来,然后把第一个字母改成大写,然后后面的实例对象敲一个第一个字符的小写,回车就直接出来了 在写调用参数的地方,ctrl+p 调出提示,然后按下提示里的实例的第 ...
- 按键精灵对APP自动化测试(上)
简单介绍下应用背景:测试安卓app时发现重复点击某一按钮的时候会出现报错,开发修复后提交测试.如果采用手动点击按钮,效率不高,在领导提示下使用按键精灵实现自动操作. 一. 安卓手机按键精灵 ...