C++11并发与多线程笔记(12) windows临界区、其他各种mutex互斥量

1、windows临界区

Windows临界区,同一个线程是可以重复进入的,但是进入的次数与离开的次数必须相等。
C++互斥量则不允许同一个线程重复加锁 (相同的mutex变量不容许连续调用)。

windows临界区是在windows编程中的内容,了解一下即可,效果几乎可以等同于c++11的mutex
包含**#include <windows.h>**
windows中的临界区同mutex一样,可以保护一个代码段。但windows的临界区可以进入多次,离开多次,但是进入的次数与离开的次数必须相等,不会引起程序报异常出错。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <Windows.h>

#define __WINDOWSJQ_

using namespace std;

class A
{
public:
	// 把收到的消息传入队列
	void inMsgRecvQueue()
	{
		for (size_t i = 0; i < 10000; ++i)
		{
			cout << "收到消息,并放入队列 " << i << endl;
//windows开关
#ifdef  __WINDOWSJQ_ 
			EnterCriticalSection(&my_winsec);	//	进入临界区
			//EnterCriticalSection(&my_winsec);	//	可以再次进入临界区,程序不会出错
			msgRecvQueue.push_back(i);
			LeaveCriticalSection(&my_winsec);	//	离开临界区
			//LeaveCriticalSection(&my_winsec);	//	如果进入两次,必须离开两次不会报错
#else
			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
#endif //  __WINDOWSJQ_
		}

		cout << "消息入队结束" << endl;
	}

	// 从队列中取出消息
	void outMsgRecvQueue()
	{
		for (size_t i = 0; i < 10000; ++i)
		{
#ifdef  __WINDOWSJQ_
			EnterCriticalSection(&my_winsec);	//	进入临界区
			if (!msgRecvQueue.empty())
			{
				// 队列不为空
				int num = msgRecvQueue.front();
				cout << "从消息队列中取出 " << num << endl;
				msgRecvQueue.pop_front();
			}
			else
			{
				// 消息队列为空
				cout << "消息队列为空 " << endl;
			}
			LeaveCriticalSection(&my_winsec);	//	离开临界区
#else
			my_mutex.lock();
			if (!msgRecvQueue.empty())
			{
				// 队列不为空
				int num = msgRecvQueue.front();
				cout << "从消息队列中取出 " << num << endl;
				msgRecvQueue.pop_front();
				my_mutex.unlock();
			}
			else
			{
				// 消息队列为空
				cout << "消息队列为空 " << endl;
				my_mutex.unlock();
			}
#endif //  __WINDOWSJQ_
		}

		cout << "消息出队结束" << endl;
	}

	A()
	{
#ifdef __WINDOWSJQ_
	InitializeCriticalSection(&my_winsec);	//	用临界区之前要初始化
#endif // __WINDOWSJQ_
	}

private:
	list<int> msgRecvQueue;
	mutex my_mutex;

#ifdef __WINDOWSJQ_
	CRITICAL_SECTION my_winsec;	//	windows中的临界区,非常类似C++11中的mutex,使用之前必须初始化
#endif // __WINDOWSJQ_

};

int main()
{
	A myobj;
	thread	myInMsgObj(&A::inMsgRecvQueue, &myobj);
	thread	myOutMsgObj(&A::outMsgRecvQueue, &myobj);
	myInMsgObj.join();
	myOutMsgObj.join();

	getchar();
	return 0;
}

2、自动析构技术

C++:lock_guard防止忘了释放信号量,自动释放
windows:可以写个类自动释放临界区

//本类用于自动释放windows下的临界区,防止忘记LeaveCriticalSection导致死锁情况的发生
class CWinLock {//RALL类
public:
    CWinLock(CRITICAL_SECTION *pCritmp)//构造函数
    {
        my_winsec =pCritmp;
        EnterCriticalSection(my_winsec);//进入
    }
    ~CWinLock()//析构函数
    {
        LeaveCriticalSection(my_winsec);//离开
    }
private:
    CRITICAL_SECTION *my_winsec;
};


class A
{
public:
	// 把收到的消息传入队列
	void inMsgRecvQueue()
	{
		for (size_t i = 0; i < 10000; ++i)
		{
			cout << "收到消息,并放入队列 " << i << endl;
//windows开关
#ifdef  __WINDOWSJQ_ 
			CWinLock wlock(&my_winsec);//调用
			msgRecvQueue.push_back(i);
#else
			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
#endif //  __WINDOWSJQ_
		}

		cout << "消息入队结束" << endl;
	}
};

上述这种类RAII类(Resource Acquisition is initialization),即资源获取及初始化。容器,智能指针属于这种类。

3、递归独占互斥量 std::recursive_mutex

  • std::mutex: 独占式互斥量,自己lock时别人lock不了
  • std::recursive_mutex允许在同一个线程中同一个互斥量多次被 lock(),(但是递归加锁的次数是有限制的,太多可能会报异常),效率要比mutex低
void testfunc1(){
	std::lock_guard<std::mutex> sbguard(my_mutex);
	//...干各种事情
	testfunc2();//多次加锁,异常
}
void testfunc2(){
	std::lock_guard<std::mutex> sbguard(my_mutex);
	//...干各种另外一些事情
}

如果你真的用了 recursive_mutex 要考虑代码是否有优化空间,如果能调用一次 lock()就不要调用多次。

4、带超时的互斥量 std::timed_mutex 和 std::recursive_timed_mutex

4.1 std::timed_mutex:是带超时的独占互斥量

1. try_lock_for():等待一段时间
如果拿到了锁,或者超时了未拿到锁,就继续执行(有选择执行)如下:

std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_for(timeout)){
    //......拿到锁返回ture
    my_mymutex.unlock();//用完了要解锁
}else{
	//没拿到锁,不会卡着,流程继续往下走
}

2. try_lock_until():参数是一个未来的时间点
在这个未来的时间没到的时间内,如果拿到了锁头,流程就走下来,如果时间到了没拿到锁,流程也可以走下来。

std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_until(chrono::steady_clock::now() + timeout)){
    //......拿到锁返回ture
    my_mymutex.unlock();//用完了要解锁
}
else{
    //没拿到锁,不会卡着,流程继续往下走
}

两者的区别就是一个参数是时间段,一个参数是时间点

4.2 std::recursive_timed_mutex:是带超时的递归独占互斥量

允许同一个线程多次获取这个互斥量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
临界区是指在多线程编程中,多个线程共享的临界资源对应的代码片段。例如,在一个银行存取款的场景中,假设有多个客户同时进行存取款操作,每个客户对应一个线程。为了保证数据的一致性和正确性,需要通过临界区来实现线程互斥访问。 在C语言中,可以使用互斥锁(mutex)来实现临界区互斥访问。互斥锁是一种同步机制,用于保护共享资源,确保同时只有一个线程可以访问临界区。下面是一个简单的银行存取款的示例: ``` #include <stdio.h> #include <pthread.h> int balance = 1000; // 银行账户余额 pthread_mutex_t mutex; // 互斥锁 void* deposit(void* arg) { pthread_mutex_lock(&mutex); // 上锁 int* amount = (int*)arg; balance += *amount; printf("存款 %d 元,余额为 %d 元\n", *amount, balance); pthread_mutex_unlock(&mutex); // 解锁 return NULL; } void* withdraw(void* arg) { pthread_mutex_lock(&mutex); // 上锁 int* amount = (int*)arg; if (balance >= *amount) { balance -= *amount; printf("取款 %d 元,余额为 %d 元\n", *amount, balance); } else { printf("余额不足\n"); } pthread_mutex_unlock(&mutex); // 解锁 return NULL; } int main() { pthread_t tid1, tid2; int amount1 = 500; int amount2 = 200; pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 pthread_create(&tid1, NULL, deposit, &amount1); // 创建存款线程 pthread_create(&tid2, NULL, withdraw, &amount2); // 创建取款线程 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&mutex); // 销毁互斥锁 return 0; } ``` 在这个示例中,通过互斥mutex来保护临界资源balance。存款和取款操作都加了互斥锁,确保每次只有一个线程访问临界区。这样就避免了多个线程同时修改balance导致数据不一致的问题。最后,通过pthread_mutex_init()初始化互斥锁,pthread_mutex_lock()上锁,pthread_mutex_unlock()解锁,pthread_mutex_destroy()销毁互斥锁来完成对临界区互斥访问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值