VC++深入详解(13):详细分析控制台应用程序下的多线程输出

最近写了了个问题很多的多线程程序,在里面我没有使用互斥对象,用它来探索多线程程序到底是如何运行的,在众多网友的帮助下,得到了很多有意义的结果,现整理如下。(如果有错误的观点,敬请批判)首先说明,我的笔记本是双核的,编译器是VS2010:

int main()
{
	HANDLE hThread =(HANDLE) _beginthreadex(NULL, 0, Fun1Proc, NULL, 0, NULL);  
	CloseHandle(hThread);  
	cout<<"main thread is running"<
这个程序有两种可能的执行结果:
main thread is runningthread1 is running!


请按任意键继续. . .
或者是:
main thread is running
请按任意键继续. . .
是为什么呢?我们先分析第二种:执行main函数的主线程,执行完后直接就结束了,Fun1Proc线程根本没有执行的机会。再看第一种:当主线程执行完cout<<"main thread is running"后,并没有执行后面的内容,然后就跳到了Fun1Proc线程,执行了cout<<"thread1 is running!"<

顺便提一句,我们知道cout是一个ostream的全局对象,<<是重载操作符,那么endl又是什么呢?
答案是:一个函数名!

_CRTIMP2_PURE inline basic_ostream >&
	__CLRCALL_OR_CDECL endl(basic_ostream >& _Ostr)
	{	// insert newline and flush byte stream
	_Ostr.put('\n');
	_Ostr.flush();
	return (_Ostr);
	}
与之对应的<<重载方式为:

basic_ostream& operator << ( basic_ostream& (*_Pfn)(basic_ostream&) );

说明cout的<<操作符可以接受一个函数指针(函数的地址)作为参数。
这个重载正好与endl函数的声名相匹配,所以<<后面是可以跟着endl的,也就是说,cout对象的<<操作符接受到endl函数的地址后会在后台调用endl函数,而endl函数会结束当前行并冲洗buffer。
由于endl实际上是函数调用,而且还在其中还调用了2个函数,所以整个执行的过程是比较长的,在期间发生线程间的切换也就不足为奇了。


如果我们仅把输出改为:
cout<<"main thread is running\n"; 和cout<<"thread1 is running!\n";
那么,子线程就没有被跳转到的机会。只会输出:
main thread is running
请按任意键继续. . .
如果我们仅在主线程中增加一句:Sleep(1000);,让主线程休眠一会儿,执行子线程,那么就会打印:
thread1 is running!
main thread is running
请按任意键继续. . .
总之,使用cout输出时有可能出现打印混乱。问在这里不是很明显,我们换一个例子演示:

unsigned int __stdcall Fun1Proc( LPVOID lpParameter);

int index = 100;
int main()
{
    //建议用_beginthreadex创建线程而不是用CreateThread
    HANDLE hThread =(HANDLE) _beginthreadex(NULL, 0, Fun1Proc, NULL, 0, NULL);  
    CloseHandle(hThread);
	while((index--) > 0)
		cout<<"main thread number:"< 0)
		cout<<"thread1 is number:"<
这个程序的打印结果就欢乐了,我挑了其中的一段:
main thread number:thread1 is number:98
thread1 is number:97
thread1 is number:96
thread1 is number:95
thread1 is number:94
thread1 is number:93
thread1 is number:92
thread1 is number:91
thread1 is number:90
thread1 is number:89
thread1 is number:88
thread1 is number:87
thread1 is number:86
thread1 is number:85
thread1 is number:84
thread1 is number:83
thread1 is number:82
thread1 is number:81
thread1 is number:80
thread1 is number:79
thread1 is number:78
thread1 is number:7799
main thread number:76


thread1 is number:74
thread1 is number:73
原因上面已经分析过了,可能是调用<<的时候发生了线程的切换,所以主线程只打印了main thread number:就被切换到子线程了,然后线程成打印完thread1 is number:77,还没有调用endl,就切换回主线程了,继续打印了99,并调用了endl换行,然后打印76,换行以后,切换回子线程,继续子线程中的换行调用,然后再继续……


我么发现前面的把endl换成"\n"的方法是治标不治本的,因为可能在<

当然这个程序还有其他的问题,对于全局对象,应该使用保护机制保护起来,当一个线程使用完成以后,另一个线程再使用,否则,有可能会出现更加奇葩的错误。

DWORD WINAPI Fun1Proc(  LPVOID lpParameter);
DWORD WINAPI Fun2Proc(  LPVOID lpParameter);

int tickets = 100;
//HANDLE hMutex;
int main()
{
	HANDLE hThread1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
	CloseHandle(hThread1);
	HANDLE hThread2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
	CloseHandle(hThread2);
//	hMutex = CreateMutex(NULL,FALSE,NULL);
	Sleep(4000);
	return 0;
}

DWORD WINAPI Fun1Proc(  LPVOID lpParameter)
{
	while(TRUE)
	{
//		WaitForSingleObject(hMutex,INFINITE);
		if(tickets>0)
		{
			Sleep(10);
			cout<<"thread1 sell ticket: "<0)
		{
			Sleep(10);
			cout<<"thread2 sell ticket: "<

我注释掉的,是使用互斥对象保护全局变量的代码。则这个代码会出现问题:会有一个线程卖出第0张票!究其原因,是因为当还有一张票时,一个线程,比如Fun1Proc正在运行,它判断if(tickets>0)为真后,开始睡眠,此时切换到Fun2Proc,它也判断if(tickets>0)为真,开始睡眠,然后切换回Fun1Proc,卖出了最后一张票,并把tickets减为0,整个线程执行完以后,又切换到线程2,(此时tickets为0)然后卖出了第0张票。

关于互斥对象的使用,及其他多线程问题,我们在下一小节中再讨论。






你可能感兴趣的:(孙鑫VC++深入详解)