【C++】多线程相关
多线程的问题,在C++11之前,都是和平台相关联的。比如Windows和Linux平台各有自己的一套多线程的接口,这就使得多线程编程相关的代码的可移植性比较差。而从C++11开始,引入了线程相关的库,使得C++多线程编程时不再需要接入第三方的库。
 
线程控制
-  线程对象的构造: 
  
 线程本质上还是操作系统层次的概念,C++11创建的线程对象可以通过关联一个线程,从而达到控制线程以及获取线程状态的目的。
 thread():创建线程对象后,因为没有提供线程的执行函数,所以实际上没有关联任何线程;
 thread(Fn&& fn, Args&&... args):创建线程对象时,提供了线程执行函数Fn,和函数的一系列参数args;
 thread(const thread&) = delete&thread(thread&& x):线程对象是防拷贝的,不能拷贝构造以及赋值,但提供了移动构造以及移动赋值。也就是说可以将线程对象关联线程的状态转移给其它线程对象,转移期间不影响线程执行。
 在创建线程对象时,关联线程执行函数,线程就将启动,和主线程一同运行。而线程执行函数可以通过函数指针、仿函数、lambda表达式的方式进行传入。
-  join线程: 
  
 join:会阻塞式等待线程退出;
 joinable:可以判断线程是否有效,以下情况之一无效:
  
-  detach线程: 
  
 用于把被创建的线程与线程对象分离开,分离的线程变为后台线程,线程执行结束会自动销毁。
线程执行函数的参数问题
线程执行函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此即使形参使用引用类型,其修改也不能影响外部实参。
 但可以使用ref()强制进行引用。
void Print2N(int n, int& count)
{
	for (int i = 0; i < n; ++i)
		++count;
}
void test1()
{
	int count = 0;
	//thread t1(Print2N, 10, count);
	thread t1(Print2N, 10, ref(count));
	t1.join();
	cout << count << endl;
}
如果是类成员函数作为参数传入,必须同时将this指针作为参数传入。
mutex
C++11中,一共包含了四种互斥量锁。
 
- mutex
 mutex:是C++11提供的最基本的互斥量。
 mutex常用的三个接口如下:
  
lock()时,可能会发生以下三种情况:
- 如果互斥量当前没有被锁住,则线程lock()会将互斥量锁住,在调用unlock()之前,该线程一直占有该锁;
- 如果互斥量当前被其它线程锁住,则当前线程lock()会被阻塞;
- 如果当前线程已经锁住互斥量,在未unlock()之前,再次lock()会产生死锁(deadlock)。
try_lock()时,可能会发生以下三种情况:
- 如果互斥量当前没有被锁住,则线程try_lock()会将互斥量锁住,在调用unlock()之前,该线程一直占有该锁;
- 如果互斥量当前被其它线程锁住,则当前线程try_lock()会失败,返回false,但并不会阻塞
- 如果当前线程已经锁住互斥量,在未unlock()之前,再次try_lock()会产生死锁(deadlock)。
-  recursive_mutex 
 recursive_mutex(递归锁),允许同一个线程对互斥量多次上锁(即递归上锁),来获取对互斥量对象的多层所有权;但相应的,释放锁时,需要调用相同层次深度的unlock()。
-  timed_mutex 
 timed_mutex比mutex多了两个成员函数:
  
 这两个函数的使用可以做到见名知意。同时第四种锁recursive_timed_mutex的使用也应该明了了。
void test2()
{
	mutex mtx;
	vector<thread> threads(4);
	for (size_t i = 0; i < threads.size(); ++i)
	{
		// 线程执行函数通过lambda传入;构造匿名的线程对象,移动赋值给vector中的线程对象
		threads[i] = thread([&mtx]() {
			mtx.lock();
			for (int i = 0; i < 10; ++i)
			{
				cout << this_thread::get_id() << ": " << i << endl;
				this_thread::sleep_for(chrono::milliseconds(100)); // 线程 sleep 100毫秒
			}
			// 一个线程在没有从0到N打印完之前不会释放锁
			mtx.unlock();
		});
	}
	for (auto& t : threads)
		t.join();
}
RAII 管理锁

lock_guard
lock_guard主要通过RAII的方式,对其管理的互斥量进行了封装。在需要加锁的地方,只需实例化一个lock_guard对象,调用构造函数即可上锁;出作用域时,lock_guard对象被销毁,调用析构函数即可自动解锁。这样的处理方式可以有效地避免一些死锁的问题。
// lock_guard 的模拟实现
template<class lk>
class lockGuard
{
public:
	lockGuard(lk& lock)
		: _lock(lock) { _lock.lock();}
	~lockGuard() { _lock.unlock();}
private:
	lk& _lock;
};
void test3()
{
	mutex mtx;
	vector<thread> threads(4);
	for (size_t i = 0; i < threads.size(); ++i)
	{
		threads[i] = thread([&mtx]() {
			// 如果中间抛异常就会出现死锁
			// 解法1: try-catch
			// 解法2: RAII - lock_guard
			lock_guard<mutex> lg(mtx);
			for (int i = 0; i < 10; ++i)
			{
				cout << this_thread::get_id() << ": " << i << endl;
				this_thread::sleep_for(chrono::milliseconds(100));
			}
		});
	}
	for (auto& t : threads)
		t.join();
}
unique_lock
lock_guard可以理解为是全自动的,把互斥量交给lock_guard后,用户就没有办法再通过lock_guard对象对互斥量进行操控,为了弥补这一点,C++11又提供了unique_lock。
 unique_lock比lock_guard更加灵活,提供了更多的构造方式和功能操作。
 
- 上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until,unlock;
- 修改操作:operator=(移动赋值),swap(与另一个unique_lock对象交换它们所管理的mutex对象的所有权),release(返回所管理mutex对象的指针,并释放所有权)
- 获取属性操作:owns_lock(返回当前unique_lock对象是否获得了锁),operator bool(与owns_lock功能相同),mutex(返回unique_lock对象所管理的mutex对象的指针)
原子操作
加锁操作也是有缺陷的:大量的加锁解锁可能会影响到程序效率;一个线程加锁访问数据,其它线程只能在锁外阻塞,锁的使用如果控制不好,还会造成死锁等问题。
 而引入原子操作(不可中断的一个或一系列操作),可以使得线程不在像加锁解锁那样繁重,对数据的访问更加高效。
void test4()
{
	atomic<int> count = 0;
	vector<thread> threads(4);
	for (size_t i = 0; i < threads.size(); ++i)
	{
		threads[i] = thread([&count]() {
			for (int i = 0; i < 100000; ++i)
			{
				++count; // 原子的++操作
			}
		});
	}
	for (auto& t : threads)
		t.join();
	cout << count << endl;
}
不需要对原子类型变量进行加锁解锁操作,县城也能够对原子类型变量互斥地访问。
两个线程交替打印奇偶数
交替打印,需要进行线程间运行的同步操作,可以使用条件变量进行同步操作。
void test5()
{
	mutex mtx;
	condition_variable cv;
	bool ready = true;
	thread t1([&mtx, &cv, &ready]() {
		for (int i = 1; i <= 100; i += 2)
		{
			unique_lock<mutex> u_lock(mtx);
			cv.wait(u_lock, [&ready]() {return ready; });
			cout << "t1: " << i << endl;
			ready = !ready;
			cv.notify_one();
		}
		});
	thread t2([&mtx, &cv, &ready]() {
		for (int i = 2; i <= 100; i += 2)
		{
			unique_lock<mutex> u_lock(mtx);
			cv.wait(u_lock, [&ready]() {return !ready; });
			cout << "t2: " << i << endl;
			ready = !ready;
			cv.notify_one();
		}
		});
	t1.join();
	t2.join();
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!