网络编程(48)—— windows平台下的四种线程同步技术(二)

四、内核同步对象—— 互斥(Mutex)

        内核线程同步对象包括互斥、信号量和事件,它们使用时存在很多共通之处。下面先介绍在Windows平台利用互斥进行线程的同步。
HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCTSTR lpName
);
        创建互斥对象并返回句柄值。

lpMutexAttributes —— 是互斥对象的安全属性,一般情况下设置为NULL即可。

bInitialOwner —— 标志互斥对象是否属于本线程的标志量,如果设置为True,互斥对象将被置为non-signaled无信号状态;如果设置为False,互斥对象将被置为signaled有信号状态。

lpName —— 设置互斥名称,若置为NULL,则设置无名互斥变量。


HRESULT CloseHandle(
HANDLE hHandle 
);

        关闭互斥对象,回收内存资源。这是关闭所有内核对象的通用API,接收参数为内核对象的句柄值。


DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);

        我们利用WaitForSingleObject进行互斥对象的加锁,和其他内核对象同步技术加锁通用。WaitForSingleObject在内核对象signaled时返回;而互斥量在WaitForSingleObject返回后其自动进入non-signaled状态,因为互斥量是“auto-reset”模式的内核对象。


BOOL WINAPI ReleaseMutex(
__in HANDLE hMutex
);
        解锁互斥对象,接收参数为互斥对象句柄。
我们利用互斥对试验代码进行同步后,如下:
#include "stdafx.h"
#include "windows.h"
#include "iostream"

int g_i=0;
HANDLE mutex;

unsigned WINAPI AddThreadFunc(void* param)
{
	WaitForSingleObject(mutex,INFINITE);
	for (int i=0;i<1000000;i++)
	{
		g_i++;
	}
	ReleaseMutex(mutex);
	return 0;
}
unsigned WINAPI DevThreadFunc(void* param)
{
	WaitForSingleObject(mutex,INFINITE);
	for (int i=0;i<1000000;i++)
	{
		g_i--;
	}
	ReleaseMutex(mutex);
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	mutex=CreateMutex(NULL,false,NULL);
	HANDLE handles[2];
	handles[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)AddThreadFunc,NULL,0,NULL);
	handles[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DevThreadFunc,NULL,0,NULL);
	WaitForMultipleObjects(2,handles,true,INFINITE);
	std::cout<<"g_i="<<g_i<<std::endl;
	CloseHandle(mutex);
	return 0;
}


五、内核同步对象—— 信号量(Semaphore)

        类似于Linux中的信号量,Windows也提供内核级别的信号量。信号量创建时使用CreateSemaphore API:
HANDLE WINAPI CreateSemaphore(
__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
__in LONG lInitialCount,
__in LONG lMaximumCount,
__in_opt LPCTSTR lpName
);

lpSemaphoreAttributes —— 信号量的安全属性,设为NULL采用默认属性。

lInitialCount —— 信号量的初始值,不能小于0。

lMaximumCount —— 信号量的最大值。

lpName —— 信号量的名称,若设置为NULL,创建无名的信号量。


        信号量销毁使用CloseHandle API,而信号量的加锁使用WaitForSingleObject,这里我们就不再重复介绍,下面介绍下解锁信号量的API:
BOOL WINAPI ReleaseSemaphore(
__in HANDLE hSemaphore,
__in LONG lReleaseCount,
__out_opt LPLONG lpPreviousCount
);

hSemaphore —— 信号量的句柄值。

lReleaseCount —— 释放信号量的数值,我们知道信号量数值是可以大于1的,而这个参数就是本次释放的信号量的值,大于等于0。

lpPreviousCount —— 用来接收释放前的信号量的数值的指针,如果不需要,置为NULL即可。

将试验代码用信号量进行同步如下:
#include "stdafx.h"
#include "windows.h"
#include "iostream"

int g_i=0;
HANDLE sema;

unsigned WINAPI AddThreadFunc(void* param)
{
	WaitForSingleObject(sema,INFINITE);
	for (int i=0;i<1000000;i++)
	{
		g_i++;
	}
	ReleaseSemaphore(sema,1,NULL);
	return 0;
}
unsigned WINAPI DevThreadFunc(void* param)
{
	WaitForSingleObject(sema,INFINITE);
	for (int i=0;i<1000000;i++)
	{
		g_i--;
	}
	ReleaseSemaphore(sema,1,NULL);
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	sema = CreateSemaphore(NULL,1,1,NULL);
	HANDLE handles[2];
	handles[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)AddThreadFunc,NULL,0,NULL);
	handles[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DevThreadFunc,NULL,0,NULL);
	WaitForMultipleObjects(2,handles,true,INFINITE);
	std::cout<<"g_i="<<g_i<<std::endl;
	CloseHandle(sema);
	return 0;
}

六、内核同步对象 —— 事件(Event)

        事件是Windows中不同于Linux,独有的一个线程同步机制。事件也属于内核同步对象,但是它却略不同于前面介绍过的互斥和信号量。主要表现在事件在创建时可选auto-reset模式和manual-reset模式。当创建auto-reset模式的事件时,它和互斥和信号量一样,WaitForSingleObject返回时,自动进入non-signaled状态;但是当创建manual-reset模式的事件时,在WaitForSingleObject返回后,它不会自动进入non-signaled状态,需要我们手动进行reset。

        我们先来看下创建事件的API:
HANDLE WINAPI CreateEvent(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in BOOL bManualReset,
__in BOOL bInitialState,
__in_opt LPCTSTR lpName
);

lpEventAttributes —— 事件的默认安全属性,默认填写NULL即可。

bManualReset —— 创建事件的模式。若为True,创建manual-reset模式的事件;若为False,创建auto-reset模式的事件。

bInitialState —— 创建事件的初始化状态。若为True,创建完成后,事件被置为signaled状态;若为Flase,创建完成后,事件被置为non-signaled状态。

lpName —— 创建的事件名称,若为NULL,创建无名事件。

        函数返回创建的事件句柄值。当我们创建manual-reset模式的事件时,需要我们手动改变事件的状态,我们需要用到以下的API:
ResetEvent(
__in HANDLE hHandle
)
        ResetEvent将事件置为non-signaled状态。

SetEvent(
__in HANDLE hHandle
)
        SetEvent将事件置为signaled状态。

我们用下面的示例代码演示事件的用法,用于auto-reset模式的事件用法和互斥以及信号量一致,不再演示。这里只演示manual-reset模式的事件的用法。
#include "stdafx.h"
#include "windows.h"
#include "iostream"

int g_i=0;
HANDLE evt;

unsigned WINAPI AddThreadFunc(void* param)
{
	WaitForSingleObject(evt,INFINITE);
	ResetEvent(evt);
	for (int i=0;i<1000000;i++)
	{
		g_i++;
	}
	SetEvent(evt);
	return 0;
}
unsigned WINAPI DevThreadFunc(void* param)
{
	WaitForSingleObject(evt,INFINITE);
	ResetEvent(evt);
	for (int i=0;i<1000000;i++)
	{
		g_i--;
	}
	SetEvent(evt);
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	evt=CreateEvent(NULL,true,false,NULL);
	HANDLE handles[2];
	handles[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)AddThreadFunc,NULL,0,NULL);
	handles[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DevThreadFunc,NULL,0,NULL);
	SetEvent(evt);
	WaitForMultipleObjects(2,handles,true,INFINITE);
	std::cout<<"g_i="<<g_i<<std::endl;
	return 0;
}

第32行,我们创建了一个manal-reset模式的事件,事件的起始的状态是non-signaled状态。

第36行,我们利用SetEvent将事件的状态由non-signaled改为signaled,这样WaitSingleObjec函数才能从阻塞状态返回。

第10行和21行,在两个线程函数中调用WaitSingleObjec等待事件变为signaled状态。

第11行和22行,利用ResetEvent将事件的状态由signaled变成non-signaled状态,这就实现了类似互斥和信号量中加锁功能。

第10行和第27行,利用SetEvent将事件的状态由non-signaled变成signaled,类似于互斥和信号量中的解锁的功能。

        执行上述代码,我们也得到了期望的结果:



七、用户模式同步和内核模式同步的对比

用户模式同步:

1、由于不涉及和操作系统内核状态的切换,效率较高。

内核模式同步:

1、 和系统内核打交道,同步对象功能较多。

2、 可以设置超时等待。


Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL48














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值