临界区Critical Section以及与锁Mutex的比较

临界区Critical Section

一、临界区概念

临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如:semaphore。只能被单一线程访问的设备,例如:打印机,每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程程必须互斥地对它进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。临界区线程同步适用范围:它只能同步一个进程中的线程,不能跨进程同步。一般用它来做单个进程内的代码快同步,效率比较高。

二、程序调度法则

进程进入临界区的调度原则是:
1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。
3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

三、相关使用

临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。

#include "stdafx.h"
#include <iostream>
#include <Windows.h>  //CRITICAL_SECTION结构所要包含的文件
using namespace std;

int index = 0;
CRITICAL_SECTION g_cs;
HANDLE hMutex = NULL;

void changeMe()
{
	cout << "change1:" << index++ << endl;
}

void changeMe2()
{
	cout << "change2:" << index++ << endl;
}

void changeMe3()
{
	cout << "change3:" << index++ << endl;
}

DWORD WINAPI th1(LPVOID lpParameter)
{
	while (index<50)
	{
		Sleep(1000);  //sleep 1s
		//进入临界区
		EnterCriticalSection(&g_cs);

		//对共享资源进行写入操作
		changeMe();
		
		//离开临界区
		LeaveCriticalSection(&g_cs);
	}
	return 0;
}

DWORD WINAPI th2(LPVOID lpParameter)
{
	while (1)
	{
		Sleep(1000);  //sleep 2s
		//进入临界区
		EnterCriticalSection(&g_cs);

		changeMe2();
		changeMe3();
		
		//离开临界区
		LeaveCriticalSection(&g_cs);
	}
	return 0;
}

int main()
{
	//初始化临界区
	InitializeCriticalSection(&g_cs);
	HANDLE hThread1;
	HANDLE hThread2;
	//创建线程
	hThread1 = CreateThread(NULL, 0, th1, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, th2, NULL, 0, NULL);

	int k;
	cin >> k;  //控制停止
	printf("Hello World!\n");
	return 0;
}

四、临界区使用的注意事项

在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程

1、 临界区的退出,不会检测是否是已经进入的线程,也就是说,我可以在A线程中调用进入临界区函数,在B线程调用退出临界区的函数,同样是成功
2、 在测试临界区的时候,如果没有调用进入临界区的函数,直接退出的话,系统没有进行判断,但是计数发生了改变,此时此临界区就再也用不了了,因为结构中的数据已经乱掉了

五、Mutex和Critical_Section的比较

Mutex和Critical Section都是主要用于限制多线程(Multithread)对全局或共享的变量、对象或内存空间的访问。下面是其主要的异同点(不同的地方用绿色表示)

 

Mutex

Critical Section

性能和速度

慢。

Mutex 是内核对象,相关函数的执行 (WaitForSingleObject,

ReleaseMutex)需要用户模式(User Mode)到内核模式

(Kernel Mode)的转换,在x86处理器上这种转化一般要

发费600个左右的 CPU指令周期。

快。

Critical Section本身不是内核对象,相关函数

(EnterCriticalSection,LeaveCriticalSection)

的调用一般都在用户模式内执行,在x86处理器上

一般只需要发费9个左右的 CPU指令周期。只有

当想要获得的锁正好被别的线程拥有时才会退化

成和Mutex一样,即转换到内核模式,发费600个

左右的 CPU指令周期。

能否跨越进程(Process)边界

可以

不可

定义写法

HANDLE hmtx;

CRITICAL_SECTION cs;

初始化写法

hmtx= CreateMutex (NULL, FALSE, NULL);

InitializeCriticalSection(&cs);

结束清除写法

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

无限期等待的写法

WaitForSingleObject (hmtx, INFINITE);

EnterCriticalSection(&cs);

0等待(状态检测)的写法

WaitForSingleObject (hmtx, 0);

TryEnterCriticalSection(&cs);

任意时间等待的写法

WaitForSingleObject (hmtx, dwMilliseconds);

不支持

锁释放的写法

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

能否被一道用于等待其他内核对象

可以(使用WaitForMultipleObjects,

WaitForMultipleObjectsEx,

MsgWaitForMultipleObjects,

MsgWaitForMultipleObjectsEx等等)

不可

当拥有锁的线程死亡时

Mutex变成abandoned状态,其他的等待线程可以获得锁。

Critical Section的状态不可知(undefined),

以后的动作就不能保证了。

自己会不会锁住自己

不会(对已获得的Mutex,重复调用WaitForSingleObject不会

锁住自己。但最后你别忘了要调用同样次数的

ReleaseMutex)

不会(对已获得的Critical Section,重复调用

EnterCriticalSection不会锁住自己。但最后

你别忘了要调用同样次数的

LeaveCriticalSection)

相关补充:

1、请先检查你的设计,把不必要的全局或共享对象改为局部对象。全局的东西越少,出问题的可能就越小。
2、每次你使用EnterCriticalSection时,请不要忘了在函数的所有可能返回的地方都加上LeaveCriticalSection。对于Mutex也同样。若你把这个问题和Win32 structured exception或C++ exception一起考虑,你会发现问题并不是那么简单。自定义一个封装类可能是一种解决方案
 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值