标签:
多线程并不一定是最好的,合适才是最好的。
多线程主要的优点是价廉物美,启动快、退出快、与其他线程共享核心对象,很容易实现共产主义的伟大梦想。但是其又有不可预期、测试困难的缺点。
使用好多线程,就是要知道何时应该用多线程,何时不该用。如果应该用多线程,如何解决Race Condition问题?如何共享数据?如何提高效率?如何同步线程和数据?总结起来就是:
但是有时候却不建议使用多线程:
如果要写一个多线程程序,第一步就是创建一个线程,我们可以使用CreateThread API函数,也可以使用_beginthreadex C 函数,其实我大多数时候使用的是Boost库上面的boost::thread对象来创建线程对象。如果有兴趣可以看看Boost库,这里暂且不讨论Boost库thread。
如果使用上面两个函数,可以去msdn查看。使用上面两种函数创建线程,其线程函数都必须符合以下格式,当然函数名可以更换:
DWORD WINAPI ThreadFunc(LPVOID n);
使用CreateThread API函数或者_beginthreadex函数,可以传回两个值用以识别一个新的线程——返回值Handle(句柄)和输出参数lpThread(线程ID)。为了安全防护的缘故,不能根据一个线程的ID获得其handle。
线程和进程一样,都是核心对象。如何释放线程属于如何释放核心对象的问题。CloseHandle函数在这里起了十分重要的作用。CloseHandle函数的功能是将核心对象的引用计数减1。其不能直接用来释放核心对象,核心对象只有在其引用计数为0的时候会被操作系统自动销毁。
BOOL CloseHandle(HANDLE hObject);
如果你不调用该函数,即使线程在创建之后执行完毕,引用计数还是不为0,线程无法被销毁。如果一个进程没有在结束之前对它所打开的核心对象调用CloseHandle,操作系统会自动把那些对象的引用计数减一。虽然操作系统会做这个工作,但是他不知道核心对象实际的意义,也就不可能知道解构顺序是否重要。如果你在循环结构创建了核心对象而没有CloseHandle,好吧!你可能会有几十万个句柄没有关闭,你的系统会因此没有可用句柄,然后各种异常现象就出现了。记住当你完成你的工作,应该调用CloseHandle函数释放核心对象。
在清理线程产生的核心对象时也要注意这个问题。不要依赖因线程结束而清理所有被这一线程产生的核心对象。面对一个打开的对象,区分其拥有者是进程或是线程是很重要的。这决定了系统何时做清理工作。程序员不能选择有进程或者线程拥有对象,一切都得视对象类型而定。如果被线程打开的核心对象被进程拥有,线程结束是无法清理这些核心对象的。
其实这两个是不同的概念。CreateThread函数返回的句柄其实是指向线程核心对象,而不是直接指向线程本身。在创建一个新的线程时,线程本身会开启线程核心对象,引用计数加1,CreateThread函数返回一个线程核心对象句柄,引用计数再加1,所以线程核心对象一开始引用计数就是2。
调用CloseHandle函数,该线程核心对象引用计数减一,线程执行完成之后,引用计数再减一为零,该核心对象被自动销毁。
首先得了解哪个线程是主线程:程序启动后就执行的线程。主线程有两个特点:
第二个特点也就意味着,如果你不等待其他线程结束,它们没有机会执行完自己的操作,也没有机会做最后的cleanup操作。我遇到过由于没有等待,而出现程序奔溃的情况。反正很危险。
这个没什么好说的,可以使用ExitThread函数退出线程,返回一个结束代码。GetExitCodeThread函数获取ExitThread函数或者return语句返回的结束代码。不过想通过GetExitCodeThread来等待线程结束是个很糟糕的注意——CPU被浪费了。下一节提及的WaitForSingleObject才是正道。
终止其他线程可以使用TerminateThread()函数,也可以使用全局标记。
TerminateThread()函数的缺点是:
1、线程没有机会在结束前清理自己,其堆栈也没有被释放掉,出现内存泄露;
2、任何与此线程有附着关系的DLLs也没有机会获得线程解除附着通知;
3、线程进入的Critical Section将永远处于锁定状态(Mutex会返回wait_abandoned状态)。
4、线程正在处理的数据会处于不稳定状态。
TerminateThread()唯一可以预期的是:线程handle变成激发状态,并且传回dwExitCode所指定的结束代码。
设立全局标记的优点:保证目标线程在结束之前安全而一致的状态
设立全局标记的缺点:线程需要一个polling机制,时时检查标记值。(可以使用一个手动重置的event对象)
使用WaitForSingleObject最显而易见的好处是你终于可以把以下代码精简成一句了。
for(;;)
{
int rc;
rc = GetExitCodeThread(hThread, &exitCode);
if(!rc && exitCode != STILL_ACTIVE)
break;
}
→ → → → → →
WaitForSingleObject(hThread, INFINITE);
其他好处就是:
WaitForSingleObject函数不好同时判断多个线程的状态,WaitForMultipleObjects可以同时等待多个线程,可以设定是否等待所有线程执行结束还是只要一个线程执行完立马返回。
在GUI线程中总是要常常回到主消息循环,上述两个wait api函数会阻塞主消息循环。MsgWaitForMultipleObjects函数可以在对象呗激发或者消息到达时被唤醒而返回。
线程同步主要有Critical Sections、Mutex、Semaphores、Event,除了Critical Section是存在于进程内存空间
内,其他都是核心对象
。
Critical Section用来实现排他性占有,适用范围时单一进程的各个线程之间。
使用示例:
CRITICAL_SECTION cs ; // here must be global attributes to related thread
InitializeCriticalSection (&cs );
EnterCriticalSection(&cs );
LeaveCriticalSection(&cs );
DeleteCriticalSection(&cs );
Critical Sections注意事项:
Critical Section的优点:
Critical Section的缺陷:
死锁
问题(一个著名的死锁问题:哲学家进餐问题)跨进程
Mutex可以在不同的线程之间实现排他性战友,甚至即使那些线程属于不同进程。
使用示例:
HANDLE hMutex ; // global attributes
hMutex = CreateMutex (
NULL, // default event attributes
false, // default not initially owned
NULL // unnamed
);
DWORD dwWaitResult = WaitForSingleObject (hMutex , INFINITE );
if (dwWaitResult == WAIT_OBJECT_0 )
{
// wait succeed, do what you want
...
}
ReleaseMutex(hMutex );
示例解释:
1、HMutex在创建时为未被拥有
和未激发
状态;
2、调用Wait...()函数,线程获得hMutex的拥有权,HMutex短暂变成激发状态,然后Wait...()函数返回,此时HMutex的状态是被拥有
和未激发
;
3、ReleaseMutex之后,HMutex的状态变为未被拥有
和未激发
状态
Mutex注意事项:
未被拥有
和未被激发
状态时返回。Mutex优点
Mutex缺点
Semaphore被用来追踪有限的资源。
和Mutex的对比
wait_abandoned
状态,一个线程可以反复调用Wait...()函数以产生锁定,而拥有mutex的线程不论在调用多少次Wait...()函数也不会被阻塞。Semaphore优点
Semaphore缺点
Event通常用于overlapped I/O,或者用来设计某些自定义的同步对象。
使用示例:
HANDLE hEvent ; // global attributes
hEvent = CreateEvent (
NULL, // default event attributes
true, // mannual reset
false, // nonsignaled
NULL // unnamed
);
SetEvent(hEvent);
PulseEvent(hEvent);
DWORD dwWaitResult = WaitForSingleObject (hEvent , INFINITE );
ResetEvent(hEvent);
if (dwWaitResult == WAIT_OBJECT_0 )
{
// wait succeed, do what you want
...
ResetEvent(hEvent );
}
示例解释:
1、CreateEvent默认为非激发状态、手动重置
2、SetEvent把hEvent设为激发状态
3、在手动重置情况下(bManualReset=true),PulseEvent把event对象设为激发状态,然而唤醒所有
等待中的线程,然后恢复为非激发状态;
4、在自动重置情况下(bManualReset=false),PulseEvent把event对象设为激发状态,然而唤醒一个
等待中的线程,然后恢复为非激发状态;
5、ResetEvent将hEvent设为未激发状态
Event注意事项:
Event优点:
Event缺点:
Win32之中三个基本的I/O函数:CreateFile()、ReadFile()和WriteFile()。
Overlapped I/O函数使用OVERLAPPED结构来识别每一个目前正在进行的Overlapped操作,同时在程序和操作系统之间提供了一个共享区域,参数可以在该区域双向传递。
如果一个进程死亡,系统中的其他进程还是可以继续执行。多进程程序的健壮性远胜于多线程。因为如果多个线程在同一个进程中运行,那么一个误入歧途的线程就可能把整个进程给毁了。
另一个使用多重进程的理由是,当一个程序从一个作业平台被移植到另一个作业平台,譬如Unix(不支持线程,但进程的产生与结束的代价并不昂贵),Unix应用程序往往使用多个进程,如果移植成为多线程模式,可能需要大改。
欢迎访问我的个人博客click me
博客原文地址:Win32 MultiThread Study Summary - Let‘s Thread
后续博客内容维护都会更新在该地址。
标签:
原文地址:http://www.cnblogs.com/cnstudy/p/5384450.html