第八十五讲 win32多线程(2)

上一讲我说了我们的程序有个bug,不知道大家有没有看出来了,不过有朋友直接在微信里回复说,既然说多线程那就应该要说同步机制,不错,我们那个程序的bug就是在缺少同步,为什么呢?因为我们的火车票被定义定义为全局变量(全局变量也是线程间通信的一种方式),而四个窗口(4条线程)同时在销售这些火车票,一个窗口销售一张,开始的时候可能没问题,但最后当窗口一正在销售最后一张的时候第二个窗口也正在销售,当最后一张被一个窗口销售掉后,那么g_count–的结果就会是0,那么这张特殊d票就会被窗口二销售出去,当然,这是不科学的,火车站售票如果真出了这种bug,那他几亿的经费不就是白费了吗?所以,对于这种很难再现的bug要绝对的杜绝(多线程一旦产生异常,是很难捕获的,所以,当我们决定要用多线程来设计我们的程序的时候得相当的小心才是),当然,这就是我们要说的同步机制。

第一个要说的就是CRITICAL_SECTION,这就是传说中的临界区,这是win32中最为简单的同步机制,他就是原子操作,将共享资源致于一种被保护的状态,然后同一时间只允许一条线程访问资源,所以,这个实现起来比较容易,我们只需要简单的条用几个相关函数就好。
——————————————-
VOID InitializeCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);


——————————————-

当我们要使用临界区来同步的话,就得初始化之,要初始化就得用上面的函数。
——————————————-
VOID DeleteCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);


——————————————-

顾名思义,这就是回收函数,当我们用完临界区时记得使用该函数来删除临界区。
——————————————-
VOID EnterCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);


——————————————-

每条线程如果想要访问共享资源,我们得先让他进入临界区,进入临界区正是上面的函数,当操作完成之后我们就应该离开临界区,让别的线程获得访问资格,所以就得使用:
——————————————-
VOID LeaveCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);


——————————————–

当然,我们还应该遵守一条多线程编程规则:不要长时间的锁住资源,因为这样一来无异于至其他线程于死地(形同虚设),所以我们可能还会用一个函数:
——————————————–

void Sleep(DWORD dwMilliseconds);


——————————————–

该函数就是让线程睡眠一段时间,单位是毫秒。

Ok,只需要这么多,我们就可以编写出安全的多线程出来,现在我们再回头去看看上一讲的问题,我们加入同步机制:
———————————————
#include <windows.h>
#include <iostream>
using std::cout;


using std::endl;


unsigned
int g_count = 10000;


CRITICAL_SECTION g_cs;


DWORD WINAPI f1(LPVOID);


DWORD WINAPI f2(LPVOID);


DWORD WINAPI f3(LPVOID);


DWORD WINAPI f4(LPVOID);

int main(){

InitializeCriticalSection(&

g_cs);

//初始化临界区

HANDLE _handle[4];


DWORD _threadID[4];


_handle[0] = CreateThread(NULL,0,f1,NULL,0,&

_threadID[0]);


_handle[1] = CreateThread(NULL,0,f2,NULL,0,&

_threadID[1]);


_handle[2] = CreateThread(NULL,0,f3,NULL,0,&

_threadID[2]);


_handle[3] = CreateThread(NULL,0,f4,NULL,0,&

_threadID[3]);


for(int i=0;

i<4;

i++){

if(_handle[i]){

cout<<"

thread "

<<_threadID[i]<<"

launchedn"

<<endl;


CloseHandle(_handle[i]);


}

}

Sleep(40000);

//等待线程返回

DeleteCriticalSection(&

g_cs);


return 0;


}


DWORD WINAPI f1(LPVOID){

while(true){

EnterCriticalSection(&

g_cs);

//进入临界区

if(g_count>0){

cout<<"

F1: "

<<g_count–<<endl;


LeaveCriticalSection(&

g_cs);

//离开临界区

}

else

break;


Sleep(10);

//给别人一个机会

}

return 0;


}

DWORD WINAPI f2(LPVOID){

while(true){

EnterCriticalSection(&

g_cs);


if(g_count>0){

cout<<"

F2: "

<<g_count–<<endl;


LeaveCriticalSection(&

g_cs);


}

else

break;


Sleep(10);


}

return 0;


}

DWORD WINAPI f3(LPVOID){

while(true){

EnterCriticalSection(&

g_cs);


if(g_count>0){

cout<<"

F3: "

<<g_count–<<endl;


LeaveCriticalSection(&

g_cs);


}

else

break;


Sleep(10);


}

return 0;


}

DWORD WINAPI f4(LPVOID){

while(true){

EnterCriticalSection(&

g_cs);


if(g_count>0){

cout<<"

F4: "

<<g_count–<<endl;


LeaveCriticalSection(&

g_cs);


}

else

break;


Sleep(10);


}

return 0;


}
———————————————

Ok,关于临界区先这样,下一讲我们再来说互斥,等把几种同步机制说完后,我们再用他来创建多线程的数据结构(其实win32的多线程我很少用,因为C++标准库里有更好用的线程,当然,先说win32多线程的原因主要考虑到大部分朋友可能还不知道C++的多线程,而且等说win32多线程后再去说C++多线程大家会觉得很简单的)。

第八十五讲 win32多线程(2)

现在我们来看看今天的运行结果,看上去是不是舒服多了呢?顺便问一下,我们这个线程函数里到底有多少个全局变量是我们需要保护的。

============================
回复D直接查看目录

原文始发于微信公众号(

C/C++的编程教室

):第八十五讲 win32多线程(2)

|

发表评论