C++11多线程编程

本文详细介绍了C++11中线程库的基础使用,包括线程创建、数据同步问题(如临时变量、指针引用、互斥量、死锁),以及高级工具如lock_guard、std::unique_lock、call_once、condition_variable、线程池、异步并发和原子操作的原理和使用场景。
摘要由CSDN通过智能技术生成


0 前言

C++11 多线程编程-小白零基础到手撕线程池

1 线程库的基本使用

进程是进行中的程序,一个操作系统里可以由多个进程(资源调度的最小单位)
线程是进程中的进程,一个进程里可以有多个线程(CPU调度的基本单位)
函数
首先需要有一个函数用于创建线程
在这里插入图片描述
在这里插入图片描述

如果线程的函数有参数,则
在这里插入图片描述
主线程不等待子线程运行完,就继续执行
在这里插入图片描述
在这里插入图片描述
join是需要主线程回收子线程资源,detach是主线程与子线程分离,子线程退出后的资源由其他进程回收、
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
join是阻塞的

2. 线程函数中的数据未定义错误

2.1 传递临时变量

在这里插入图片描述

因为临时变量在主线程结束后临时变量会被直接释放,所以出错
std::ref 传递引用类型 使用右值引用
在这里插入图片描述

2.2 传递指针或引用指向局部变量问题

因为a是局部变量,只在test中有效,
在这里插入图片描述
解决问题:把a放在全局区
在这里插入图片描述

2.3 传递指针或引用指向已释放的内存的问题

在这里插入图片描述
使用智能指针

在这里插入图片描述

2.4 入口函数为类的私有函数


使用友元类
在这里插入图片描述

3 互斥量解决多线程数据共享问题

在这里插入图片描述

使用互斥锁

在这里插入图片描述

4.互斥量死锁

假如有两个线程T1,T2,需要对两个互斥量mtx1喝mtx2进行访问,并且需要按照以下顺序获得互斥量的所有权

T1先获取mtx1的所有权,再获得mtx2的所有权
T2先获取mtx2的所有权,在获取mtx1的所有权

在这里插入图片描述
T1使用完mtx1后想要获得mtx2的所有权后再mtx1释放所有权,但mtx2需要mtx1释放mtx1的所有权后再释放mtx2的所有权.就造成了互斥量死锁

在这里插入图片描述

在这里插入图片描述
解决方案:
T1获取完所有的mtx,再给T2获取所有的mtx
在这里插入图片描述
在这里插入图片描述

5 lock_guard 和C++11 的std::unique_lock

5.1 lock_guard

lock_guard 是一种互斥量的封装
当构造函数调用时,会自动锁定
当析构函数调用时,该互斥量会被自动解锁
std::lock_guard 对象不能复制或者移动,只能在局部作用域中使用
在这里插入图片描述

5.2 std::unique_lock

也是多线程中对于互斥量进行加锁解锁的操作。但是比lock_guard有更多的功能,比如延迟加锁、条件变量、超时等。

在这里插入图片描述
这样写会和lock_guard一样
在这里插入图片描述

5.1 更多的操作

此时需要手动加锁
在这里插入图片描述
此时加锁时,会有更多的操作

在这里插入图片描述
在5秒内进行阻塞并尝试加锁,如果超时,则直接返回
在这里插入图片描述
这里函数的意思是:如果在5秒内没有获得资源并且锁到他,则直接返回
在这里插入图片描述
try_lock_until 意思是等到什么时间,一般传系统时间

支持右值引用,支持

6 call_once与其使用场景

在单例模式下使用该场景
在这里插入图片描述
在这里插入图片描述

call_once

在这里插入图片描述
如果多个线程同时调用单例模式,需要保证这个函数在多个线程中只被执行一次

在这里插入图片描述
call_once只能在线程中使用,在main函数中使用会报错

7 condition_variable

7.1 生产者和消费者模型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
生产者加任务,消费者取任务。这时候可能出现生产者正在加任务,但消费者正在取任务。所以需要加锁

#include<iostream>
#include<thread>
#include<string>
#include<memory>
#include<mutex>
#include<condition_variable>
#include<queue>

std::queue<int> g_queue;
std::condition_variable g_cv;
std::mutex mtx;

void Producer() {
	for (int i = 0; i < 10; i++) {
	{
		// 这里使用两个大括号,是为了限制锁的作用域
		std::unique_lock<std::mutex> lock(mtx);
		g_queue.push(i);
		// 通知消费者取
		g_cv.notify_one();
		std::cout << "task:" << i << std::endl;

	}

	std::this_thread::sleep_for(std::chrono::microseconds(100));
	}
}

void Consumer() {
	while (1) {
		std::unique_lock<std::mutex> lock(mtx);
		// 等待,如果队列为空,需要等待
		
		g_cv.wait(lock, []() {return !g_queue.empty(); });
		int value = g_queue.front();
		g_queue.pop();

		std::cout << "Consumer: " << value << std::endl;
	}
}

int main() 
{
	std::thread t1(Producer);
	std::thread t2(Consumer);
	t1.join();
	t2.join();

	return 0;
}

8 C++ 跨平台线程池

提前开辟好一堆线程
在这里插入图片描述

#include<iostream>
#include<thread>
#include<string>
#include<memory>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<functional>

class ThreadPool {
public:
	ThreadPool(int numThreads) :stop(false)
	{
		for (int i = 0; i < numThreads; i++) {
			// pushback会使用拷贝构造函数,thread不支持拷贝构造函数,所以使用emplace
			threads.emplace_back([this] {
				while (1) {
					std::unique_lock<std::mutex> lock(mtx);

					// 等待任务来
					condition.wait(lock, [this] {
						return !tasks.empty() || stop;

						});

					if (stop && tasks.empty()) {
						return;
					}

					// 取任务,取任务列表最左边的任务
					std::function<void()> task(std::move(tasks.front()));
					tasks.pop();
					lock.unlock();
					task();
				}

				});
		}
	}

	~ThreadPool()
	{
		{
			// 线程池结束了
			std::unique_lock<std::mutex> lock(mtx);
			stop = true; 
		}

		// 通知所有线程完成任务
		condition.notify_all();
		for (auto& t : threads) {
			t.join();
		}
	}

	template<class F,class ... Args>
	// 万能引用 给用户提供的接口
	void enqueue(F&& f, Args&&...args) {
		// 把函数和参数绑定在一起
		std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
		{
			// 先锁队列
			std::unique_lock<std::mutex> lock(mtx);
			// 把函数加入队列
			tasks.emplace(std::move(task));
		}


		condition.notify_one();
	}
private:

	// 线程数组
	std::vector<std::thread> threads;
	// 任务队列
	std::queue<std::function<void()>> tasks;

	// 互斥锁
	std::mutex mtx;
	// 通知消费者进行取任务的变量
	std::condition_variable condition;

	bool stop;
};




int main() 
{
	ThreadPool pool(4);

	for (int i = 0; i < 10; i++) {
		pool.enqueue([i] {
			std::cout << "task :" << i << "task is running" << std::endl;
			std::this_thread::sleep_for(std::chrono::seconds(2));
			std::cout << "task :" << i << "task is done" << std::endl;

			});
	}

	return 0;
}

线程池需要维护的两个东西 1.线程池2.任务数组

9 异步并发 async future packaged_task promise

9.1 async、future

是C++引入的函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果,使用std::async可以方便的进行异步编程
比如一个函数要执行两次
在这里插入图片描述
可以使用打包 当传入async传入func时,就已经开始执行函数了,函数执行的结果存储在future_result中,可以使用get获取。如果get的时候没有执行完 ,会等待执行完再继续往下走
在这里插入图片描述

9.2 packaged_task

将函数对象封装成一个std::future对象,可以方便的将函数转化为异步操作,供其他线程使用
在这里插入图片描述

9.3 promise

在C++中,promise用于在一个线程中产生一个值,在另一个线程中获得。(也可以使用函数传递,或者通过共享变量)
在这里插入图片描述

10 原子操作 std::atomic

用于实现多线程环境下的原子操作。提供了一种线程安全的方式来访问和修改共享变量。
和自己通过锁mutex一样
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值