Windows下多线程编程(二)
线程的分类1. 有消息循环线程
MSG msg;
while(GetMessage(&msg,NULL,0,128)">0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
2. 无消息循环线程
线程间的通讯1. 同享内存变量l 由于线程是同享进程内存的,所以通过全局/静态变量来进行通讯效力最最高的。参数需要斟酌是不是加volitile。 l 通过传递的参数,如援用和指针。参数需要斟酌是不是加volitile。 2. 消息通知
SendMessage必须等待消息函数处理完成才返回,PostMessage则直接将消息放入消息队列立即返回。所以SendMessage的消息参数可以是临时变量,而PostMessage的消息参数必须保证足够的生存周期。
while(true)
{
if(GetMessage(&msg,128)">0)) //get msgfrom message queue
{
switch(msg.message)
{
case MY_MSG:
Todo:
break;
}
}
};
3. 其他方式
线程之间的状态 1. 异步即多个线程彼此独立,不受外部线程的影响。线程本身就是实现异步的1种方式。 2. 同步即多个线程彼此依赖,线程A的计算结果是线程B的计算的条件,也就是说在开始线程B的计算之前必须等待线程A的计算完。 3. 互斥即多个线程在操作同1个资源时,1个线程必须等另外一个线程结束了才能继续操作。互斥与同步不同的地方是,互斥没有前后关系。同1个资源,可以指全局变量,也能够指1个文件对象或是其他的内核对象。由于内核对象是跨进程的,所以更是跨线程的。 等待函数1. 概念WaitForSingleObject函数是等待内核对象从无信号状态到有信号状态或是超时即返回。也即无信号状态时等待,有信号或超时立即返回。 WaitForMulitpleObjects函数是等待多个内核对象从无信号状态到有信号状态或是超时即返回(可以指明是所有对象或是任1对象)。 Windows具有几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出/毛病流、事件、等待定时器、信号量、互斥对象。 2. 等待函数与内核对象之间的关系
l 线程和进程创建及运行时都是无信号状态,当结束运行时变成有信号状态。 l 自动重置的事件(FALSE)对象,当等待成功的时候,会被修改成无信号状态。 l 信号量对象,当调用ReleaseSemaphore(数量加1),处于有信号状态,WaitForSingleObject会被触发并且立行将信号数量减1.
用户模式与内核模式的优缺点1. 用户模式优点:线程同步机制速度快 缺点:容易堕入死锁状态多个进程之间的线程同步会出现问题。(比如竞争资源、死锁) 2. 内核模式优点:支持多个进程之间的线程同步,避免死锁 缺点:线程同步机制速度慢,线程必须从用户模式转为内核模式。这个转换需要很大的代价:来回1次需要占用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,0);
_beginthreadex(NULL,ThreadFun2,128)">0);
SetEvnet(g_hEvent);//
}
DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hEvent);
Todo...
SetEvent(g_hEvnet);
return 0;
}
DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hEvent);
0;
}
注意:如果上面创建的是人工重置事件,则两个线程函数都将履行。如果是自动重置事件,则只能履行1个线程,且不能保证哪个线程先履行。如果要保证1个线程先履行,可以添加事件对象用来确保指定线程已履行,不能通过代码的前后顺序确保线程已履行。 2. 信号量(Semaphore)内核对象信号量的使用规则: 当前信号量资源数大于0,则标记为有信号状态。 当前信号量资源数为0,则标记为无信号状态。 信号量资源数不能为负,且最大不能超过指定数量。 CreateSemaphore,创建信号量对象。 OpenSemaphore,打开指定信号量对象,可以跨进程。 ReleaseSemaphoer,资源计算加1。 HANDELg_hSema[2];
int Main()
{
g_hSema[0] =CreateSemaphore(NULL,128)">1,128)">1,NULL);
g_hSema[1] =CreateSemaphore(NULL,128)">0);
}
DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hSema[0]);
Todo...
ReleaseSemaphoer(g_hSema[1]);
0;
}
DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hSema[
这样就可以够保证ThreadFun1履行完了,再履行ThreadFun2,然后再履行ThreadFun1,并且保证每一个线程函数只能被调用1次.
3. 互斥量(Mutex)内核对象互斥量内核对象确保线程具有单个资源的互斥访问权。在行动特性上,互斥量与临界区的1样。只不过,互斥量是内核对象,使用时需要从用户模式切换到内核模式,比较耗时。但正由于是内核对象,所以互斥量能够跨进程,并且能够设置超时时间,这是它比临界区灵活的地方。 CreateMutex,创建互斥量对象。 OpenMutex,打开指定互斥量对象,可以跨进程。 ReleaseMutex,释放互斥量,对象被标记为有信号状态,触发WaitForSingleObject。 互斥量和临界区1样,具有1个线程具有权的概念,即当前互斥量和当前临界区的释放只能由当前线程释放,其他线程释放无效。由于互斥量是内核对象,如果线程已终止,但是其所属的互斥量仍然没有释放,内核管理器会自动释放。临界区没有这个功能,由于临界区不是内核对象,所以临界区如果没有正确释放会致使死锁。 HANDLECreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName); bInitialOwner标记是不是由创建线程具有线程所有权,TRUE表示创建者具有,FALSE表示创建者不具有,则是第1个调用WaitForSingleObject的线程将取得线程所有权。 HANDELg_hMutex;
int Main()
{
g_hMutex =CreateMutex(NULL,FALSE);
_beginthreadex(NULL,128)">0);
}
DWORD WINAPIThreadFun1(PVOID pParam)
{
WaitForSingleObject(g_hMutex);
Todo...
ReleaseMutex(g_hMutex);
0;
}
DWORD WINAPIThreadFun2(PVOID pParam)
{
WaitForSingleObject(g_hMutex);
两个函数谁先调用,谁即获得线程所有权。如果想指定线程先运行,需要判断指定线程已履行以后再创建新线程,不能依托线程的代码创建前后顺序。
3. 线程的互斥像互斥量对象一样可以到达互斥的效果,只是互斥量功能更丰富,并且如果是简单的资源互斥,使用临界区的效力更优。 临界区(Critical Section)是1段供线程独占式访问的代码,也就是说若有1线程正在访问该代码段,其它线程想要访问,只能等待当前线程离开该代码段方可进入,这样保证了线程安全。他工作于用户级(相对内核级),在Window系统中CRITICAL_SECTION实现临界区相干机制。 voidInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 初始化临界区 voidEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 进入临界区 voidLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 离开临界区 voidDeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) // 释放临界区资源 由于临界区具有线程所有权这个概念,即进入临界区的线程才有权释放临界区。由于必须当前线程进入和释放,更多的时候,临界区是在1个函数里使用,为了确保不会由于中间退出函数致使没有释放,我们可以用以下方式来确保释放。 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...
注意
1. 注意所有内核对象在结束时都需要调用closeHandle()。 2. 跨线程调用MFC对象函数都是不安全的。由于MFC对象的1些函数都与TLS有关联, 所以有些调用会出错。如UpdateData(),最好通过句柄发消息来完成相应的功能。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |