赞
踩
多线程:是指从软件或者硬件上实现多个线程并法执行的技术。
进程与线程的区别:
函数 | 作用 |
---|---|
void join() | 等待线程结束并清理资源(会阻塞) |
bool joinable() | 返回线程是否可以执行join函数 |
void detach() | 将线程与调用其的线程分离,彼此独立执行(此函数必须在线程创建时立即调用,且调用此函数会使其不能被jion) |
std::thread::id get_id() | 获取线程id |
CMakeLists:
find_package(Thread)
add_executable(thread_test thread_test.cpp)
target_link_libraries(thread_test ${CMAKE_THREAD_LIBS_INIT})
#include <iostream>
#include <thread>
using namespace std;
void doit(){cout << "world !" << endl;}
int main(){
// flush函数:刷新缓冲区。
// endl函数:终止一行并刷新缓冲区。
thread a([]{cout << "hello," << flush;}), b(doit);
a.join();
b.join();
return 0;
}
输出结果:
Hello, World!
或者是
World!
Hello,
多线程运行时是以异步方式执行的,与我们平时写的同步方式不同。异步方式可以同时执行多条语句。
#include <iostream> #include <thread> using namespace std; void output(int id){ cout << "thread" << id << "finish!" << endl; } int main(){ thread th[10]; for(int i = 0 ; i < 10; i++){ th[i] = thread(output,i); } for(int i = 0; i < 10 ; i++){ th[i].join(); } return 0; }
输出结果:
threadthread01finish!finish!
thread3finish!
thread2thread4finish!
finish!
thread5finish!
thread6finish!
thread7finish!
thread8finish!
thread9finish!
#include <iostream> #include <thread> using namespace std; template<class T> void changeValue(T &x, T val){ x = val; } int main(){ thread th[10]; int num[10]; for(int i = 0 ; i < 10;i++){ th[i] = thread(changeValue<int> , ref(num[i]), i + 1); } for(int i =0 ;i <10;i++){ th[i].join(); cout << num[i] << " "; } return 0; }
输出结果:
1 2 3 4 5 6 7 8 9 10
由于thread在传递参数时,是以右值传递的:
template <class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args)
划重点:Args&&… args
很明显的右值引用,那么我们该如何传递一个左值呢?std::ref和std::cref很好地解决了这个问题。
std::ref 可以包装按引用传递的值。 std::cref 可以包装按const引用传递的值。
join
或detach
的线程在程序结束时会引发异常#include <iostream> #include <thread> using namespace std; int n = 0; void plus_n(){ for(int i =0 ;i < 10000;i++){ n++; } } int main(){ thread th[100]; for(thread &x : th){ x = thread(plus_n); } for(thread &x: th){ x.join(); } cout << " n : " << n << endl; return 0; }
输出结果:
n : 311747
我们的输出结果应该是1000000,可是为什么实际输出结果比1000000小呢?
在上文我们分析过多线程的执行顺序——同时进行、无次序,所以这样就会导致一个问题:多个线程进行时,如果它们同时操作同一个变量,那么肯定会出错。为了应对这种情况,c++11中出现了std::atomic和std::mutex。
std::mutex
是c++11中最基本的互斥量,一个线程将mutex锁住时,其他的线程就不能操作mutex,直到这个线程将mutex解锁。根据这个特征,我们可以修改一下上述的代码:
主要需要引入头文件#include <mutex>
#include <iostream> #include <thread> #include <mutex> using namespace std; int n = 0; std::mutex mtx; void plus_n(){ for(int i =0 ;i < 10000;i++){ mtx.lock(); n++; mtx.unlock(); } } int main(){ thread th[100]; for(thread &x : th){ x = thread(plus_n); } for(thread &x: th){ x.join(); } cout << " n : " << n << endl; return 0; }
输出结果:
n : 1000000
(这里用mutex
代指对象
)
函数 | 作用 |
---|---|
void lock() | 将mutex上锁。如果mutex已经被其他线程上锁,那么会阻塞,直到解锁; 如果mutex已经被同一个线程锁住,那么就会产生死锁 |
void unlock() | 解锁mutex,释放其所有权。如果有线程因为调用lock()不能上锁而被阻塞,则调用此函数会将mutex的主动权随机交给其中一个线程; 如果mutex不是被此线程上锁,那么会引发未定义的异常 |
bool try_lock() | 尝试将mutex上锁;如果mutex未被上锁,这将其上锁并返回true;如果mutex已被锁则返回false。 |
使用lock_guard相比于mutex更加安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像mutex.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。
int cnt = 20; mutex m; void t2() { while (cnt > 0) { lock_guard<mutex> lockGuard(m); if (cnt > 0) { --cnt; cout << cnt << endl; } } }
mutex很好地解决了多线程资源争抢的问题,但是它太…慢…了…!
以例四为标准,我们定义了100个thread,每个thread要循环10000次,每次循环都要加锁、解锁,这样固然会浪费很多的时间,那么该怎么办呢?接下来就是atomic大展拳脚的时间了。
#include <iostream> #include <thread> #include <atomic> using namespace std; atomic_int n = 0; void plus_n(){ for(int i =0 ;i < 10000;i++){ n++; } } int main(){ thread th[100]; for(thread &x : th){ x = thread(plus_n); } for(thread &x: th){ x.join(); } cout << " n : " << n << endl; return 0; }
输出结果:
n : 1000000
代码解释
可以看到,我们只是改动了n的类型(int -> std::atomic _int
),其他地方一点没动,输出却正常了。其中std::atomic_int
是std::atomic<int>
的别名。
atomic本意为原子,官方的解释是原子操作是最小且不可并行化的操作。这意味着即使是多线程,也要像同步进行一样同步操作atomic对象,从而省去了mutex上锁、解锁的时间消耗。
注意:std::async定义在future头文件中
thread可以快速、方便的创建线程,但是在async面前,就是小巫见大巫了。
async可以根据情况选择同步执行或创建线程来异步执行,当然也可以手动操作。对于async的返回值操作也比thread更加方便。
不同于thread,async是一个函数,所以没有成员函数。
重载版本 | 作用 |
---|---|
template <class Fn, class… Args> future<typename result_of<Fn(Args…)>::type> async (Fn&& fn, Args&&… args) | 异步或同步(根据操作系统而定)以args为参数执行fn 同样地,传递引用参数需要 std::ref 或std::cref |
template <class Fn, class… Args> future<typename result_of<Fn(Args…)>::type> async (launch policy, Fn&& fn, Args&&… args); | 异步或同步(根据policy参数而定(见下文))以args为参数执行fn 引用参数同上 |
std::launch强枚举类(enum class)
标识符 | 作用 |
---|---|
枚举值:launch::async | 异步启动 |
枚举值:launch::deferred | 在调用future::get、future::wait时同步启动(std::future见后文) |
特殊值:launch::async | launch::defereed | 同步或异步,根据操作系统而定 |
#include <iostream>
#include <future>
using namespace std;
int main(){
async(launch::async, [](const char *message){
cout << message << flush;
},"Hello,");
cout << "World!" << endl;
return 0;
}
输出结果:
Hello,World!
我们已经知道如何使用async来异步或同步执行任务,但如何获得函数的返回值呢?这时候,async的返回值std::future就派上用场了。
在之前的所有例子中,我们创建线程时调用的函数都没有返回值,但如果调用的函数有返回值呢?
#include <iostream> #include <future> using namespace std; template<class ... Args> // c++17折叠表达式 // decltype(auto)是C++14新增的类型指示符,可以用来声明变量以及指示函数返回类型。 decltype(auto) sum(Args&&... args){ return (0 + ... + args); // "0+"避免空参数包错误 } int main(){ future<int> val = async(launch::async, sum<int,int,int>,1,10,100); cout << val.get() << endl ; // 阻塞等待线程结束并返回值 return 0; }
输出结果:
111
代码解释:
我们定义了一个函数sum,它可以计算多个数字的和,之后我们又定义了一个对象val,它的类型是std::future<int>
, 这里的int
代表这个函数的返回值是int类型。在创建线程后,我们使用了future::get()
来阻塞等待线程结束并获取其返回值。
函数 | 作用 |
---|---|
当类型为引用:R& future<R&>::get() 当类型为void:void future::get() | 阻塞等待线程结束并获取返回值。 若类型为void,则与future::wait()相同。只能调用一次。 |
void wait() const | 阻塞等待线程结束 |
template <class Rep, class Period> future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const; | 阻塞等待rel_time(rel_time是一段时间), 若在这段时间内线程结束则返回future_status::ready 若没结束则返回future_status::timeout 若async是以launch::deferred启动的,则不会阻塞并立即返回future_status::deferred |
std::future的作用并不只有获取返回值,它还可以检测线程是否已结束、阻塞等待,所以对于返回值是void的线程来说,future也同样重要。
#include <iostream> #include <future> using namespace std; void count_big_number(){ // c++14中,可以给数字中间加上单引号来分割数组,增强可读性 for(int i =0 ; i <= 100'0000'0000 ; i++); } int main(){ future<void> fut = async(launch::async, count_big_number); cout << "Please wait" << flush; // 每次等待1秒 while(fut.wait_for(chrono::seconds(1)) != future_status::ready){ cout << "," << flush; } cout << endl << "finished!" << endl; return 0; }
等待线程运行结束,等待期间每过1秒输出一个“,”
输出结果:
Please wait,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
finished
在上文,我们已经讲到如何获取async创建线程的返回值。不过在某些特殊情况下,我们可能需要使用thread而不是async,那么如何获得thread的返回值呢?
还记得之前我们讲的thread成员函数吗?thread::join()的返回值是void类型,所以你不能通过join来获得线程返回值。只能通过传递引用的方式来获取返回值。
假如你写一个函数,需要返回3个值,那你会怎么办呢?vector?嵌套pair?不不不,都不需要,3个引用参数就可以了。
void get_circle(double r, double &d, double &c, double &s) {
d = r * 2;
c = PI * d;
s = PI * r * r;
}
promise实际上是std::future的一个包装,在讲解future时,我们并没有牵扯到改变future值的问题,但是如果使用thread以引用传递返回值的话,就必须要改变future的值,那么该怎么办呢?
实际上,future的值不能被改变,但你可以通过promise来创建一个拥有特定值的future
函数 | 作用 |
---|---|
当类型为引用:void promise<R&>::set_value (R& val) 当类型为void:void promise::set_value (void) | 设置promise的值并将共享状态设为ready(将future_status设为ready) void特化:只将共享状态设为ready |
future get_future() | 构造一个future对象,其值与promise相同,status也与promise相同 |
#include <iostream> #include <future> using namespace std; template<class ... Args> decltype(auto) sum(Args&& ... args){ return (0 + ... + args); } template<class ... Args> void sum_thread(promise<long long> &val, Args&& ... args){ val.set_value(sum(args...)); } int main(){ promise<long long> sum_value; thread get_sum(sum_thread<int,int,int>,ref(sum_value),1,10,100); cout << sum_value.get_future().get() << endl; get_sum.join(); return 0; }
输出结果:
111
在头文件中,不仅有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 | 暂时放弃线程的执行,将主动权交给其他线程 (放心,主动权还会回来) |
#include <iostream> #include <future> using namespace std; atomic_bool ready = false; void sleep(uintmax_t ms){ this_thread::sleep_for(chrono::microseconds(ms)); } void record(){ while(!ready) { this_thread::yield(); } cout << "thread [" << this_thread::get_id() << "] finished!" << endl; } int main(){ thread th[10]; for(int i =0 ; i < 10; i++){ th[i] = thread(record); } sleep(5000); ready = true; cout << "Start! " << endl; for (int i = 0; i < 10; i++){ th[i].join(); } return 0; }
输出结果:
thread [thread [140244805338880140244796946176thread [] finished!] finished!Start! 140244813731584
thread [140244838909696] finished!
thread [140244788553472] finished!
thread [140244855695104] finished!
thread [140244780160768thread [] finished!
thread [140244830516992
140244822124288] finished!
] finished!
thread [140244847302400] finished!
] finished!
https://blog.csdn.net/sjc_0910/article/details/118861539
https://www.cnblogs.com/whlook/p/6573659.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。