多线程
常见函数
构造函数
函数 | 对应作用 |
---|---|
thread() noexcept | 空线程,什么都不做,noexcept作用是标识该函数不会抛出任何异常(简化代码生成过程) |
template <class Fn, class… Args> explicit thread(Fn&& fn, Args&&… args) | 创建一个线程,以args 为参数执行fn 函数 |
thread(thread&& x) noexcept | 构造一个与x 相同的对象,会破坏x 对象 |
成员函数
函数 | 作用 |
---|---|
void join() | 等待线程结束并清理资源(会阻塞) |
bool joinable() | 返回线程是否可以执行join函数 |
void detach() | 将线程与调用其的线程分离,彼此独立执行(此函数必须在线程创建时立即调用,且调用此函数会使其不能被join) |
std::thread::id get_id() | 获取线程id |
thread& operator=(thread &&rhs) | 见移动构造函数(如果对象是joinable的,那么会调用std::terminate() 结果程序) |
多线程的基本使用
使用lamda表达式初始化线程
void doit()
{
std::cout << "Hello " << std::endl;
}
void test01()
{
std::thread t1([]
{ std::cout << "Linux" << std::flush; }),
t2(doit);
t1.join();
t2.join();
}
让thread执行带参函数
void consumer(int id , int n)
{
for(int i = 1; i <= n; ++i);
std::cout << "thread" << id << "is work done!!!" << std::endl;
}
//test thread
void test02()
{
std::thread th[10];
for(int i = 0; i < 10 ; ++i)
{
th[i] = std::thread(consumer, i, 10000000);
}
for(int i = 0 ; i < 10 ; ++i)
{
th[i].join();
}
}
传递模板类型的函数给线程
std::ref
可以包装按引用传递的值。
std::cref
可以包装按const引用传递的值。
template<class T >
void CHange_Value(T& x,T value)
{
x = value;
}
void test03()
{
std::thread th[100];
int nums[100];
for(int i = 0 ; i < 100; ++i)
{
th[i] = std::thread( CHange_Value<int>, std::ref(nums[i]), i+1);
}
for(int i = 0; i < 100; ++i)
{
th[i].join();
if(i !=0 && i % 10 == 0)
{
std::cout << std::endl;
}
std::cout << nums[i] << " ";
}
std::cout << std::endl;
}
保护临界资源(全局类数据)的两种方式
线程互斥
函数声明:
#include<mutex>
成员函数:
std::mutex _mutex;
//加锁:_mutex.lock();
_mutex.trylock(); //未被上锁,则该函数实现上锁+ 返回 true 已被上锁返回false
//解锁: _mutex.unlock();
#include<mutex>
std::mutex _mutex;
int n = 0;
void Count_Hun()
{
for(int i = 0; i < 100; ++i)
{
_mutex.lock();
n++;
_mutex.unlock();
}
}
void test04()
{
std::thread th[100];
for(std::thread& s : th)
{
s = std::thread(Count_Hun);
}
for(std::thread& s : th)
{
s.join();
}
std::cout << n << std::endl;
}
优点:执行逻辑一目了然,避免了数据不一致现象。
缺点:频繁的加锁/解锁造成效率的低下。
加锁所带来的问题:
- 在加锁与解锁途中,函数内部退出造成锁未释放的情况。
- 申请锁顺序不一致造成双方都占有部分资源,但都无法推进的情况。
如何解决?
- 通过RAII思想来解决,将锁的生命周期交由对象管理。本质来说就是构造加锁,出了作用域调用析构,在析构函数中解锁这个思想
std::lock_guard std::mutex 用来管理锁的生命周期 std::unique_lock --两者对比位于下方代码行
void Count_Hun()
{
for (int i = 0; i < 100; ++i)
{
std::lock_guard<std::mutex> lg(_mutex);
//_mutex.lock();
n++;
// _mutex.unlock();
}
}
void test07()
{
std::thread th[100];
for (std::thread &s : th)
{
s = std::thread(Count_Hun);
}
for (std::thread &s : th)
{
s.join();
}
std::cout << n << std::endl;
}
compare:
void Count_Hun()
{
for (int i = 0; i < 100; ++i)
{
std::unique_lock<std::mutex> ul(_mutex);// 相比较std::lock_guard来说其更加灵活,可以手动控制加锁粒度,因为lock_guard不提供解锁方法
//而只是根据其生命周期决定,但unique_lock却可以在作用域内手动解锁,更加灵活
n++;
ul.unlock();
}
}
- 使用std::lock对多个对象同时上锁(一个锁的话会报错)
void Count_Hun()
{
for (int i = 0; i < 100; ++i)
{
//std::lock_guard<std::mutex> lg(_mutex);
std::lock(_mutex,_mutex1);
//_mutex.lock();
n++;
// _mutex.unlock();
_mutex1.unlock();
_mutex.unlock();
}
}
原子变量
可以初始化的类型: 如bool 、char 、int 等。初始化方式
兼容C: 如std::atomic src = ATOMIC_VAR_INIT(0);
C++:std::atomic src(0);
#include<atomic>
std::atomic<int> tickets(0);
void Add_Tickets()
{
for (int i = 0; i < 100; ++i)
{
tickets++;
}
}
void test05()
{
std::thread th[100];
for (std::thread &s : th)
{
s = std::thread(Add_Tickets);
}
for (std::thread &s : th)
{
s.join();
}
std::cout << tickets << std::endl;
}
C++11中this_thread
上面讲了那么多关于创建、控制线程的方法,现在该讲讲关于线程控制自己的方法了。
在<thread>
头文件中,不仅有std::thread这个类,而且还有一个std::this_thread命名空间,它可以很方便地让线程对自己进行控制。
函数 | 作用 |
---|---|
std::thread::id get_id() noexcept | 获取当前线程id |
template<class Rep, class Period>void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration ) | 等待sleep_duration (sleep_duration 是一段时间) |
void yield() noexcept | 暂时放弃线程的执行,将主动权交给其他线程(放心,主动权还会回来) |
std::atomic<bool> ready(false);
void sleep(uintmax_t ms)
{
std::this_thread::sleep_for(std::chrono::microseconds(ms));
}
void count()
{
while(!ready)
std::this_thread::yield(); //**暂时**放弃线程的执行,将主动权交给其他线程(放心,主动权还会回来)
for(int i = 0; i <= 2000000000; ++i);
std::cout << "Thread" << std::this_thread::get_id() << "Finished" << std::endl;
}
void test06()
{
std::thread th[10];
for(int i = 0 ; i < 10 ; ++ i)
{
th[i] = std::thread(count);
}
::sleep(5000);
ready = true;
std::cout << "start" << std::endl;
for(int i = 0 ; i < 10 ; ++i)
{
th[i].join();
}
}
C++条件变量
#include<condition_variable>
#include<queue>
#include<random>
#include<ctime>
struct task{
int x;
int y;
task(int _x,int _y):x(_x),y(_y)
{}
int operator()(){
return x + y;
}
};
//test for cindition_variable
std::condition_variable cv;
std::queue<task > tasks; //任务队列
void consumer01()
{
while(true)
{
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::unique_lock<std::mutex> ul(_mutex);
while(tasks.empty())
{
//为了避免被虚假唤醒,有多消费者的情况下
cv.wait(ul);
}
task t = tasks.front();
std::cout << t.x<< "+" << t.y << " = " << t() << std::endl;
tasks.pop();
}
}
void productor()
{
while(true)
{
std::this_thread::sleep_for(std::chrono::microseconds(500));
std::unique_lock<std::mutex> ul(_mutex);
int x = rand() % 10;
int y = rand() % 10;
task t(x,y);
tasks.push(t);
std::cout << "put a new task to the queue:" << " " << t.x << " + " << t.y << " = " << " ? " << std::endl;
cv.notify_all();
}
}
void test()
{
srand(time(NULL));
std::thread t1(consumer01);
std::thread t2(productor);
t1.join();
t2.join();
}
C++ future promise
C++中的future
和promise
是一对在多线程编程中用来实现异步计算的工具。future
对象用来表示异步操作的结果,而promise
对象则用来生成这个结果。当程序需要实现异步操作时,可以使用promise
对象生成一个future
对象,将计算任务提交到一个线程中执行,同时程序可以继续执行其他任务。当异步操作完成后,计算结果将被写入promise
对象中,并最终通过future
对象被返回给程序使用。
通过使用future
和promise
,我们可以将程序中的计算分成多个独立的线程,从而提高程序的执行效率和响应速度。同时,通过使用这些工具,我们可以有效地处理多个异步计算任务的执行顺序,并避免因为缺少资源导致死锁情况的发生。
注:其不支持赋值操作(std::promise t ; std::promise t1 = t (error); ),但可以支持move语义
(std::promise t ; std::promise t1 = std::move(t)😉。
背景:c++线程没有提供一个我们获取返回值的一个接口。比如:
#include<condition_variable>
std::condition_variable cv;
void task(int x,int y,int &ref)
{
std::unique_lock<std::mutex> ul(_mutex);
ref = x + y;
cv.notify_one();
}
void test01()
{
int ret = 0 ;
//背景:普通线程执行无法获取到线程执行结果,我们只可以通过传引用的方式去获取线程结果,通过加锁/条件变量来控制执行的顺序,非常复杂。
std::thread t1(task,1,2,std::ref(ret));
std::unique_lock<std::mutex> ul(_mutex);
cv.wait(ul);
std::cout << ret << std::endl;
t1.join();
}
第一种:获取到线程执行结果
void task(int x,int y, std::promise<int> &ref)
{
ref.set_value ( x + y);
}
void test01()
{
//用法1:获取到程序执行的结果,不使用任何的条件变量及锁
int ret = 0 ;
std::promise<int> t;
std::future<int> fu = t.get_future(); //两个数据结构之间做了连接,此时调用fu.get()的时候,会阻塞在那里,知道子线程(即就是set_value执行)返回
std::thread t1(task,1,2,std::ref(t));
std::cout << fu.get() << std::endl;
t1.join();
}
第二种:线程先开辟,随后等值到来再执行
#include<future>
void task(int x,std::future<int>& y, std::promise<int> &ref)
{
ref.set_value ( y.get() + x);
}
void test01()
{
//用法2:先开辟出线程,然后延迟去执行(线程的执行依赖后面设置的结果)
int ret = 0 ;
std::promise<int> t;
std::future<int> fu = t.get_future(); //两个数据结构之间做了连接,此时调用fu.get()的时候,会阻塞在那里,知道子线程(即就是set_value执行)返回
std::promise<int> t_in;
std::future<int> fu_in = t_in.get_future();
std::thread t1(task,1,std::ref(fu_in),std::ref(t));
t_in.set_value(12);
std::cout << fu.get() << std::endl;
t1.join();
}
shared_future
在C++中,shared_future
是一个类模板,用于表示一种可用于异步操作的共享状态,可以被多个线程读取。它是std::future
的另一个版本,shared_future
可以从future
对象创建,也可以将其从其他shared_future
对象复制。
对shared_future
对象的get()
操作是线程安全的,多个线程可以同时调用这个函数,但是对于其他的操作,需要线程之间进行同步。
以下是一个简单的示例,展示了如何使用shared_future
来并行地计算两个数字的乘积:
第一好处:线程安全,可同时调用这个对象 第二:不需要转换,直接传值即可
#include <iostream>
#include <future>
int main() {
std::promise<int> prom;
std::shared_future<int> fut(prom.get_future());
std::cout << "Calculating in parallel (threads)...\n";
auto prod = [](std::shared_future<int> f1, std::shared_future<int> f2) {
std::cout << "Thread id: " << std::this_thread::get_id() << '\n';
int val1 = f1.get();
int val2 = f2.get();
return val1 * val2;
};
auto f1 = std::async(std::launch::async, prod, fut, fut);
auto f2 = std::async(std::launch::async, prod, fut, fut);
prom.set_value(5);
std::cout << "Results are: " << f1.get() << ", " << f2.get() << '\n';
return 0;
}
这个示例会创建一个共享的future
对象,两个线程并行地计算这个对象中的值,并返回一个新的future
对象,别的线程用get()
来等待这些计算的结果。
std::packaged_task std::async
std::async
std::async :可能并不是真正的去开启一个线程,而是根据需要去开启的。
返回值: std:;future fu = std::async(task,args…);
如果想必须采用新线程去处理应该怎么办?
std::future ret = std::async(std::launch::async,task,1,2);
std::cout << "return_value is " << ret.get() << std::endl;
//mean:延迟处理,意思是在真正需要的时候才去处理它,比如在cout那行,ret.get()的时候再去处理 – 同一个线程去处理
std::future ret = std::async(std::launch::deferred,task,1,2); -->当缺省第一个参数的时候,默认采取的也是这种策略。
std::cout << "return_value is " << ret.get() << std::endl;
std::async(std::launch::deferred| std::launch::async,task,1,2)== std::async(task,args…);
#include<future>
int task(int x,int y)
{
return x + y;
}
void test01()
{
// std::async 返回值: std::future 类型
//std::future<int> ret = std::async(task,1,2);
//std::future<int> ret = std::async(std::launch::async,task,1,2);
//mean:延迟处理,意思是在真正需要的时候才去处理它,比如在cout那行,ret.get()的时候再去处理
std::future<int> ret = std::async(std::launch::deferred,task,1,2);
std::cout << "return_value is " << ret.get() << std::endl;
}
std::packaged_task 任务打包
std::packaged_task
是一个可以将函数或可调用对象封装为异步任务的类模板。
//type: std::packaged_task<return_type(args)> tk (function_name)
method1:
#include<future>
int task(int x,int y)
{
return x + y;
}
void test01()
{
//type: std::packaged_task<return_type(args)> tk (function_name)
std::packaged_task<int(int,int)> tk (task) ;
tk(1,2);
//运行到tk.get_future().get()时,去调用tk
std::cout << "value is " << tk.get_future().get() << std::endl;
}
mothod2:
#include <iostream>
#include <future>
#include <thread>
int factorial(int n) {
int res = 1;
for (int i = 1; i <= n; ++i)
res *= i;
return res;
}
int main() {
std::packaged_task<int(int)> task(factorial);
std::future<int> fut = task.get_future();
std::thread thr(std::move(task), 6);
int result = fut.get();
std::cout << "factorial(6) = " << result << '\n';
thr.join();
return 0;
}
在这个代码中,我们创建了一个std::packaged_task
对象,并将一个计算阶乘的函数factorial
作为其构造函数的参数。然后,我们使用std::future
对象通过调用get_future()
方法来获取异步任务的返回值。之后,我们通过创建新的线程来执行该异步任务,并通过调用future
对象的get()
方法来获取其返回值。最后,我们输出这个返回值并让新线程加入到主线程中。