C++线程开发_锁

线程执行顺序

在启动多个线程时线程的执行顺序完全却决于CPU的线程调度机制有关,因此可能存在线程运行期间是无法保证顺序的,而且随时会被切换到另一个线程,线程的调度不可控因此也就产生了多个线程访问同一个数据时,造成数据脏读这一类问题,其中最经典的例子就是多个线程买票问题,简单示例代码如下:

#include <iostream>
#include <thread>
#include <vector>

using namespace std;

int ticket = 500;

class Wicket
{
public:
	Wicket(const string & _wicket_name) {
		wicket_name_ = _wicket_name;
	}
	void operator() () {
		while (ticket > 0) {
			ticket -= 1;
			cout << wicket_name_ << " 售出1张票,剩余 " << ticket <<" 张票." << endl;
		}
	}

private:
	string							wicket_name_;
};

int main()
{
	vector<thread> thread_arrays;

	thread_arrays.push_back(thread(Wicket("售票窗口A")));
	thread_arrays.push_back(thread(Wicket("售票窗口B")));

	for (int index = 0; index < 2; ++index) {
		thread_arrays.at(index).join();
	}

	system("pause");

    return 0;
}

数据脏读

互斥量(mutex)

lock和unlock

在多线程访问同一个数据变量时常常会提到互斥量这个概念,即在线程读写一个共享数据变量时,将数据锁住,其他线程一看被锁住就不再操作,而是等到该变量解锁的时候由当前获得执行权限的线程访问该变量,示例代码如下:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

using namespace std;

int ticket = 500;
// 定义一个锁对象变量
mutex sell_ticket_mutex_;

class Wicket
{
public:
	Wicket(const string & _wicket_name) {
		wicket_name_ = _wicket_name;
	}
	void operator() () {
		while (ticket > 0) {
			// 在减票前加上锁
			sell_ticket_mutex_.lock();
			ticket -= 1;
			cout << wicket_name_ << " 售出1张票,剩余 " << ticket <<" 张票." << endl;
			// 在减票后解开锁
			sell_ticket_mutex_.unlock();
			this_thread::sleep_for(chrono::milliseconds(1));
		}
	}

private:
	string							wicket_name_;
	
};

int main()
{
	vector<thread> thread_arrays;

	thread_arrays.push_back(thread(Wicket("售票窗口A")));
	thread_arrays.push_back(thread(Wicket("售票窗口B")));

	for (int index = 0; index < 2; ++index) {
		thread_arrays.at(index).join();
	}

	system("pause");

    return 0;
}

上面代码使用lock()unlock()对某一段数据进行锁定,注意这两个函数一定要成对出现,否则将会导致其他线程停止在lock()处卡死

lock_guard

可以使用lock_guard替换lock()unlock(),示例代码如下:

void operator() () {
	while (ticket > 0) {
		{
			// 声明一个lock_guard对象,将互斥量作为构造参数传入
			lock_guard<mutex>	guard_thread_(sell_ticket_mutex_);
			ticket -= 1;
			cout << wicket_name_ << " 卖出1张票,剩余 " << ticket << " 张票." << endl;
		}
		this_thread::sleep_for(chrono::milliseconds(100));
	}
}

使用lock_guard时要注意不能再使用lock()unlock()两个函数,同时,要将锁定的范围进行局部化。这个lock_guard会在声明的时候锁住,然后出了声明的作用域后就会自动解锁,因此不能在一个全局的地方声明它。例如,上面的代码中使用{...}将减票操作和打印操作括起来,而将耗时等待放到外部,进入花括号的时候声明lock_guard上锁,出了花括号就自动解锁,进入睡眠状态。其他线程就可以执行了。

lock_guard的机制其实就是在构造的时候用传进去的互斥量参数调用一次lock(),当出了作用域后声明的对象可能会析构,在析构函数中调用一次unlock()

如果需要在构造时不调用lock()可以在构造的时候增加std::adop_lock参数。

unique_lock

unique_locklock_guard相比可以使用的方式更加丰富,常见参数如下:

  • adop_lock

如果使用这个参数那么在构造对象前需要手动进行加锁处理。

  • defer_lock_t

adop_lock正好相反,在构造前不能上锁。

  • try_to_lock

这个标志表示在构造的时候会使用try_lock()进行尝试上锁。注意使用try_lock()不会阻塞后边程序的运行,通常要与if...else配合使用否则起不到互斥的作用,作为unique_lock构造参数时,使用方式如下:

class Wicket
{
public:
	Wicket(const string & _wicket_name) {
		wicket_name_ = _wicket_name;
	}
	void operator() () {
		while (ticket > 0) {
			{
				unique_lock<mutex>	guard_thread_(sell_ticket_mutex_, try_to_lock);
				// 判断是否拥有锁的占用权
				if (guard_thread_.owns_lock()) {
					ticket -= 1;
					cout << wicket_name_ << " 卖出1张票,剩余 " << ticket << " 张票." << endl;
				} else {
					cout << wicket_name_ << " 不可买票" << endl;
				}
			}
			this_thread::sleep_for(chrono::milliseconds(100));
		}
	}

private:
	string							wicket_name_;
};

等待函数

现在有一种情况,当A线程需要等待B线程提供的数据,因此启动线A程并开始执行前先判断一下是否存在共享的数据是否存在,如果不存在则睡眠一段时间,这是一种定时器唤醒模式它的执行效率并不高,这就需要使用条件变量(condition_variable)配合互斥量可以实现等待模式唤醒模式`,示例代码:

int goods_count = 0;
mutex sell_goods_mutex_;
condition_variable condition_lock;

class ReplenishStock 
{
public:
	ReplenishStock() {}

	void operator() () {
		while (true) {
			unique_lock<mutex>	guard_thread_(sell_goods_mutex_);

			condition_lock.wait(guard_thread_, [this] {
				return goods_count == 0;
			});

			cout << "正在进货 " << endl;
			goods_count += 5;
			this_thread::sleep_for(chrono::seconds(1));
			condition_lock.notify_one();
		}

	}
};

class Sales
{
public:
	Sales() {}
	void operator() () {
		while (true) {
			unique_lock<mutex>	guard_thread_(sell_goods_mutex_);

			condition_lock.wait(guard_thread_, [this] {
				return goods_count != 0;
			});
			
			int need_sales_count = goods_count;
			for (int index = 0; index < need_sales_count; ++index) {
				;
				cout << "卖出货物, 剩余 " << --goods_count << " 件货" << endl;
				this_thread::sleep_for(chrono::milliseconds(500));
			}
			
			condition_lock.notify_one();
		}
	}
};

条件变量演示

等待函数解析

上面代码中主最主要的就是wait()函数,

unique_lock<mutex> guard_thread_(sell_goods_mutex_);

condition_lock.wait(guard_thread_, [this] {
    return goods_count != 0;
});

该函数第一个参数是锁对象这里使用的是unique_lock,第二个参数是一个函数,该函数的返回值必须要是bool类型

当前程序进入while后首先创建unique_lock对象此时互斥量被锁主,执行wait()函数,该函数的执行过程机器原理如下:

  • 如果没有设置函数(即,没有第二个参数)则阻塞在此处。
  • 如果设置了函数,则根据函数的返回值再决定是否阻塞在此处,其中如果返回的是true则不阻塞,返回的是false则阻塞
  • 当处于阻塞状态时释放锁资源
  • 在阻塞状态下只有在其他线程调用了notify_one之后被唤醒。
  • 被唤醒后首先是获取互斥量的锁,如果获取失败则继续等待,成功后如果有第二个参数则根据返回值决定是执行还是阻塞。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值