多线程与线程同步

一、在创建了线程之后,如果对线程句柄不感兴趣,应该将其关闭

在用windows API函数CreateThread创建了一个线程之后,如果对其返回的线程句柄不感兴趣,可以调用CloseHandle函数将线程句柄关闭。先来看看CreatThread函数的原型。

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadID
);
其返回值为所创建线程的句柄,CloseHandle函数原型如下所示,其输入参数为一个句柄值,即所创建线程的句柄。
BOOL CloseHandle(
  HANDLE hObject
);

通过调用CloseHandle函数关闭线程句柄,并没有中止新创建的线程,只是表示我们对所创建线程的句柄不感兴趣,在之后的程序中并不需要用到,因此将其关闭。其实,当关闭了该句柄后,系统就会减少对该线程内核对象的使用计数。当内核对象的使用计数为0时,系统就会释放该线程的内核对象。而如果不关闭该句柄,则系统会一直保持着对该线程内核对象的引用。即使线程已经执行完毕,由于其内核对象的使用计数仍不为0,则系统仍不会释放该线程的内核对象,直到进程终止时,系统才会清理掉残留的对象。

因此,当不需要线程句柄时,就调用CloseHandle将其关闭,让这个线程内核对象的引用计数减1。

二、通过互斥对象Mutex实现线程同步

互斥对象属于内核对象,它能够确保线程对单个资源的互斥访问权。互斥对象通过函数CreateMutex创建,CreateMutex的函数原型如下所示:

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);

互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

在主线程中创建了互斥对象之后,在各个需要访问共享资源的线程中,在需要保护的代码前面,需要调用WaitForSingleObject函数,用于主动请求互斥对象的使用权,这样才能够获得互斥对象的所有权。WaitForSingleObject函数原型如下所示:

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

其第一个参数hHandle即为之前所创建的对象的句柄,第二个参数dwMilliseconds为等待互斥对象使用权的等待时间,以ms为单位,如果为0,则WaitForSingleObject函数将测试互斥对象的状态(通知状态或未通知状态)并立即返回,如果该参数设置为INFINITE,则该函数将一直等待,直到获得对象的所有权才返回。

线程在访问完共享资源后,需要将释放互斥对象的所有权,让互斥对象处于通知状态,这时需要调用ReleaseMutex函数。该函数的原型如下所示:

BOOL ReleaseMutex(HANDLE hMutex);

该函数的参数为互斥对象的句柄。

三、通过事件对象来实现线程同步

事件对象与互斥对象一样,也属于内核对象,事件对象有两种不同的类型:人工重置的事件对象和自动重置的事件对象。事件对象包括三个成员,分别是:

使用计数;

用于指明该事件对象是一个人工重置的还是一个自动重置的布尔值;

用于指明该事件对象的初始状态是已通知状态还是未通知状态的布尔值。

与事件对象使用有关的几个函数如下所示:

HANDLE CreateEvent(
LPSECURITY_ATTIBUTES lpEventAttibutes,
BOOL bManulReset,
BOOL bInitialState,
LPCTSTR lpName
);

BOOL SetEvent(HANDEL hEvent);

BOOL ResetEvent(HANDLE hEvent);

CreateEvent用于创建一个事件对象,其参数用于指明该创建的事件对象的安全属性,是否是人工重置,初始状态是否已通知,以及其名字。

SetEvent用于设置指定的事件对象使其处于已通知状态;

ResetEvent用于设置打比赛的事件对象使其处于未通知状态;

由于人工重置的事件对象需要手动设置使其处于未通知状态,在使用上比较麻烦,因此在多线程编程中一般都使用自动重置的事件对象。

在主线程中,先创建了一个自动重置的事件对象,在其他线程需要访问共享资源之前,调用WaitForSingleObject等待获取事件对象的所有权,当WaitForSingleObject函数返回时,自动使事件对象处于未通知状态,阻止其他线程对共享资源的访问,当该线程访问完共享资源之后,调用SetEvent函数使事件对象处于已通知状态,从而其他等待获取该事件对象所有权的线程能够继续运行。

四、通过关键代码段实现线程同步

关键代码段,也叫临界区,使用临界区来实现线程同步需要用的的API函数有:

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void EnterCriticalSection((LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection((LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection((LPCRITICAL_SECTION lpCriticalSection);
先创建一个全局的临界区对象,CRITICAL_SECTION g_cs,然后在主线程中调用InitializeCriticalSection初始化该临界区对象,在需要访问共享资源的代码前面调用EnterCriticalSection获取临界区对象的所有权,在访问完共享资源后调用LeaveCriticalSection释放线程对临界区对象的所有权。之后在主线程返回前通过调用DeleteCriticalSection释放一个没有被任何线程所拥有的临界区对象的所有资源。


互斥对象事件对象关键代码段实现线程同步的比较

三种线程同步方式,它们之间的区别如下:

1.互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步;

2.关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。

通常,在编写多线程程序并需要实现线程同步时,首选关键代码段。而如果要实现在多个进程间的多个线程间实现同步的话,可以使用互斥对象和事件对象。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值