Linux下C语言版多线程: https://xingzhu.top/archives/duo-xian-cheng
创建进程 1 2 #include <thread> thread name (function_name, args..) ;
name
:进程名
function_name
:函数名字,仿函数
args
:函数传的参数
这个函数是在创建线程的时候就执行这个函数
1 2 3 4 5 6 7 8 9 10 11 12 void print (string str) { cout << "Hello Thread!" << str << endl; } int main () { thread t1 (print, "hh" ) ; thread t1; t1 = thread (print, "hh" ); return 0 ; }
这样实际有问题,有可能编译器报错,没报错实际也有问题
因为创建线程后,仿函数就执行了,但是主线程不会等待子线程结束后才往下继续执行,可能仿函数还没执行完,也就是上面的还没打印完,主程序就执行完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 t1. join (); t1. detach (); if (t1. joinable ()) { t1. join (); } if (t1. joinable ()) { t1. detach (); }
易错点 传参为引用型
std:: ref ( ) 这是一个将变量转换为引用类型 传递引用,不管自身是不是引用,都要使用 ref ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void func (int &x) { x += 1 ; } int main () { int a = 1 ; int &b = a; thread t1 (func, ref(a)) ; thread t1 (func, ref(1 )) ; thread t1 (func, a) ; thread t1 (func, b) ; func (a); }
传递指针或引用指向局部变量的问题
一般就是函数执行完后的局部变量,释放了内存,而 线程还在操作
错误演示 1:
1 2 3 4 5 6 7 8 9 10 11 12 thread t1; void func (int &x) { x += 1 ; } void test () { int x = 1 ; t1 = thread (func, ref (x)); } int main () { test (); }
因为 test
函数执行完,x
内存就释放了,线程在执行 x += 1
非法访问了
当时的疑惑是为啥会非法访问,因为感觉定义的时候就执行函数了,实际不是这样
多线程执行顺序和函数执行顺序不一致,有可能函数结束后才执行线程,因为线程是并发的
错误示范 2
1 2 3 4 5 6 7 8 9 void func1 (int *x) { x += 1 ; } int main () { int *ptr = new int (1 ); thread t2 (func1, ptr) ; delete ptr; t.join (); }
潜在的问题,虽然没报错,但是问题仍在
delete ptr;
正确的是放在 join
函数后,也就是等待子线程完成之后,因为有可能还未执行完成,就通过 delete ptr
把内存释放了,就非法访问了
类成员函数调用的问题 错误示范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class MyClass {public : void func (int &x) { x += 1 ; } }; int main () { int a = 1 ; MyClass obj; thread t1 (&MyClass::func, &obj, ref(a)) ; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <thread> #include <memory> #include <chrono> using namespace std;class MyClass {public : void memberFunction (int &x) { this_thread::sleep_for (chrono::seconds (2 )); x += 1 ; cout << "Thread is modifying x to: " << x << endl; } }; int main () { int a = 1 ; auto obj = make_shared <MyClass>(); thread t1 (&MyClass::memberFunction, obj, ref(a)) ; t1. join (); cout << "Final value of a: " << a << endl; return 0 ; }
互斥量 常见问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <mutex> mutex mt1; int a = 0 ; void func () { for (int i = 1 ; i <= 100 ; i++) { a += 1 ; } } int main () { thread t1 (func) ; thread t2 (func) ; t1. join (); t2. join (); return 0 ; }
按理说结果为 200,但是这里存在线程同时访问情况,也就是都拿到 a,进行操作,那么结果比 200 小或者等
现在编译器输出一直等于 200,应该是编辑器的操作,进行了强制加锁
实际此时线程不安全,那么应该加个互斥信号量,也就是上述代码中的注释,在加操作前后加锁和解锁
互斥量处理不周到导致的死锁情况 一个情景是 t1
先获取 mt1
锁控制,t2
获取 mt2
锁控制,然后再各自获取 mt2
, mt1
下面就出现了循环等待的情况,死锁
错误示范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 mutex mt1, mt2; void func1 () { mt1.l ock(); _sleep(100 ); mt2.l ock(); mt2. unlock (); mt1. unlock (); } void func2 () { mt2.l ock(); mt1.l ock(); mt1. unlock (); mt2. unlock (); } int main () { thread t1 (func1) ; thread t2 (func2) ; t1. join (); t2. join (); cout << "over" << endl; return 0 ; }
类型 lock_guard std:: lock_guard
是 C++ 标准库中的一种互斥量封装类,用于保护共享数据,防止多个 线程同时访问同一资源而导致的数据竞争问题
std:: lock_guard
的特点如下:
当构造函数被调用时,该互斥量会被自动锁定
当析构函数被调用时,该互斥量会被自动解锁
std:: lock_guard
对象不能复制或移动,因此它只能在局部作用域中使用
1 2 3 4 5 6 7 void func () { for (int i = 1 ; i <= 100 ; i++) { lock_guard<mutex> lg (mt1) ; a += 1 ; } }
unique lock 主要特点:
可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等
自动加锁和解锁
成员函数:
lock ()
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁
try_lock ()
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false
,否则返回 true
try_lock_for (const std::chrono::duration<Rep, Period>& rel_time)
:加锁,加的延迟锁,如果超过了这个时间,直接返回,不必阻塞了
try_lock_until (const std::chrono:: time point<Clock, Duration>& abs_time)
:加时间点锁,超过了这个时间点,直接返回,不阻塞了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void func () { for (int i = 1 ; i <= 100 ; i++) { unique_lock<mutex> lg (mt1) ; a += 1 ; } } #include <mutex> timed_mutex mt1; int a = 0 ; void func () { for (int i = 1 ; i <= 2 ; i++) { unique_lock<timed_mutex> lg (mt1, defer_lock) ; if (lg.try_lock_for (chrono::seconds (2 ))) { this_thread::sleep_for (chrono::seconds (1 )); a += 1 ; } } } int main () { thread t1 (func) ; thread t2 (func) ; t1. join (); t2. join (); cout << a; return 0 ; }
解释可能为 3 的一种情况由来
假设 t1
先执行,然后休眠 1s
,然后 t1
又执行,t2
总共等 2s
都没等到,然后返回 false
,不执行当前 if
语句内的内容,i++
然后执行下一次的 for
循环
一次对于 t2
来说就只进行了一次 +1 操作,t1
执行两次,所以返回 3
条件变量 condition_variable
std::condition_variable
只能与 std::unique_lock<std::mutex>
配合使用
也就是只能和独占锁一起使用
常用的独占锁:mutex
, timed_mutex
条件变量 condition_variable
类的 wait()
还有一个重载的方法,可以接受一个条件,这个条件也可以是一个返回值为布尔类型的函数
条件变量会先检查判断这个条件是否满足,如果满足条件(布尔值为 true),则当前线程重新获得互斥锁的所有权,结束阻塞,继续向下执行;如果不满足条件(布尔值为 false),当前线程会释放互斥锁(解锁)同时被阻塞,等待被唤醒
普通的 wait
也是阻塞后都会释放互斥锁,唤醒后就会重新抢占互斥锁,然后执行
condition_variable_any
std::condition_variable_any
可以与任何满足 BasicLockable 概念的锁类型一起使用,这包括自定义的锁类型,或其他标准库中的锁(如 std::shared_mutex
)
比如读写锁,递归锁,信号量等等,都是一些存在共享的锁
上述的阻塞后是用 notify_one()
和 notify_all()
唤醒
生产者消费者问题 1 void wait (std::unique_lock<std::mutex>& lock
该方法该方法使调用线程进入等待状态,并且自动把传给他的锁解开
当被唤醒时,它会重新锁定互斥锁,然后继续执行
1 template <class Predicate > void wait (std::unique_lock<std::mutex>& lock, Predicate pred)
这个方法除了执行上述功能外,还会在等待之前和被唤醒之后检查传入的谓词 pred 是否为真
如果谓词为假,线程会继续等待;如果谓词为真,线程会退出等待
这里就可以传一个 bool 值,可以用 lambda 格式写
1 2 void notify_one () void notify_all ()
唤醒一个正在等待该条件变量的线程,如果没有线程在等待,该调用会被忽略
唤醒所有正在等待该条件变量的线程,如果没有线程在等待,该调用会被忽略
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <mutex> #include <condition_variable> queue<int > q; condition_variable g_cv; mutex mt1; bool done = false ; void Producer () { for (int i = 1 ; i <= 10 ; i++) { { unique_lock<mutex> lock (mt1) ; q.push (i); g_cv.notify_one (); } this_thread::sleep_for (chrono::microseconds (100 )); } { unique_lock<mutex> lock (mt1) ; done = true ; g_cv.notify_all (); } } void Consumer () { while (true ) { unique_lock<mutex> lock (mt1) ; g_cv.wait (lock, []() { return !q.empty () || done; }); if (!q.empty ()) { int value = q.front (); q.pop (); cout << "Consumed: " << value << endl; } if (done && q.empty ()) { break ; } } } int main () { thread t1 (Producer) ; thread t2 (Consumer) ; t1. join (); t2. join (); return 0 ; }
原子变量
可以指定 bool
、char
、int
、long
、指针
等类型作为模板参数(不支持浮点类型和复合类型)
设置了一个变量为原子变量后,就不需要互斥锁的实现了,因为内部实现了冲突的解决,也会等待,但是比互斥锁要好,它本质是一种原子操作
原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换,而互斥锁使用需要大量上下文切换,所以性能比互斥锁好很多
构造函数 1 2 3 4 5 6 atomic () noexcept = default ;constexpr atomic ( T desired ) noexcept ;atomic ( const atomic& ) = delete ;
构造函数①:默认无参构造函数。 构造函数②:使用 desired 初始化原子变量的值。 构造函数③:使用=delete 显示删除拷贝构造函数, 不允许进行对象之间的拷贝
常用成员
别名
原始类型定义
atomic_bool
(C++11)
std::atomic<bool>
atomic_char
(C++11)
std::atomic<char>
atomic_schar
(C++11)
std::atomic<signed char>
atomic_uchar
(C++11)
std::atomic<unsigned char>
atomic_short
(C++11)
std::atomic<short>
atomic_ushort
(C++11)
std::atomic<unsigned short>
atomic_int
(C++11)
std::atomic<int>
atomic_uint
(C++11)
std::atomic<unsigned int>
atomic_long
(C++11)
std::atomic<long>
atomic_ulong
(C++11)
std::atomic<unsigned long>
atomic_llong
(C++11)
std::atomic<long long>
atomic_ullong
(C++11)
std::atomic<unsigned long long>
atomic_char8_t
(C++20)
std::atomic<char8_t>
atomic_char16_t
(C++11)
std::atomic<char16_t>
atomic_char32_t
(C++11)
std::atomic<char32_t>
1 2 3 4 5 6 7 8 9 atomic_int m_value = 0 ; int temp = m_value.load (); m_value.store (10 ); m_value++, m_value--, m_value += 10 ;
使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <thread> #include <atomic> using namespace std;struct Counter { void increment () { for (int i = 0 ; i < 10 ; ++i) { int current_value = m_value.load (); if (current_value < 5 ) { m_value.store (current_value + 1 ); cout << "increment to: " << m_value.load () << ", threadID: " << this_thread::get_id () << endl; } this_thread::sleep_for (chrono::milliseconds (500 )); } } void decrement () { for (int i = 0 ; i < 10 ; ++i) { int current_value = m_value.load (); if (current_value > -5 ) { m_value.store (current_value - 1 ); cout << "decrement to: " << m_value.load () << ", threadID: " << this_thread::get_id () << endl; } this_thread::sleep_for (chrono::milliseconds (500 )); } } atomic_int m_value = 0 ; }; int main () { Counter c; thread t1 (&Counter::increment, &c) ; thread t2 (&Counter::decrement, &c) ; t1. join (); t2. join (); return 0 ; }
call_once( ) 在某些特定情况下,某些函数只能在多线程环境下调用一次,比如:要初始化某个对象,而这个对象只能被初始化一次,就可以使用 std::call_once()
来保证函数在多线程环境下只能被调用一次。使用 call_once()
的时候,需要一个 once_flag
作为 call_once()
的传入参数
1 2 3 template < class Callable, class ... Args >void call_once ( std::once_flag& flag, Callable&& f, Args&&... args ) ;
flag
:once_flag
类型的对象,要保证这个对象能够被多个线程同时访问到 f
:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数 args
:作为实参传递给回调函数
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <thread> #include <mutex> using namespace std;once_flag g_flag; void do_once (int a, string b) { cout << "name: " << b << ", age: " << a << endl; } void do_something (int age, string name) { static int num = 1 ; call_once (g_flag, do_once, 19 , "luffy" ); cout << "do_something() function num = " << num++ << endl; } int main () { thread t1 (do_something, 20 , "ace" ) ; thread t2 (do_something, 20 , "sabo" ) ; thread t3 (do_something, 19 , "luffy" ) ; t1. join (); t2. join (); t3. join (); return 0 ; }
输出:
1 2 3 4 name: luffy, age: 19 do_something () function num = 1 do_something () function num = 2 do_something () function num = 3
线程池
注意:这个是线程池简略版本,详细版本: https://xingzhu.top/archives/c-xian-cheng-chi-tong-bu
线程池是提前开辟好的多个线程组成的
任务是开启后,等着任务进来执行任务
因为线程的开辟和销毁十分耗时间
因此线程池需要具备一个线程数组,一个任务队列 生产者往里面加任务,线程池维护线程数组去任务队列取任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <vector> #include <functional> #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> class ThreadPool { public : ThreadPool (int numThreads) :stop (false ) { for (int i = 0 ; i < numThreads; i++) { threads.emplace_back ([this ] { while (true ) { 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 <<" " << "is running" << std::endl; std::this_thread::sleep_for (std::chrono::seconds (1 )); std::cout << "task : " << i <<" " << "is over" << std::endl; }); } }
详细解析版本的线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <vector> #include <functional> #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> class ThreadPool { public : ThreadPool (int numThreads) :stop (false ) { for (int i = 0 ; i < numThreads; i++) { threads.emplace_back ([this ] { while (true ) { 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 T , class ... Args> void enqueue (T &&f, Args&&... args) { std::function<void ()>task = std::bind (std::forward<T>(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 <<" " << "is running" << std::endl; std::this_thread::sleep_for (std::chrono::seconds (1 )); std::cout << "task : " << i <<" " << "is over" << std::endl; }); } }
异步并发
异步就是不会出现死等情况,发起任务后,干其他事情去了,这个任务执行后,通知进程
future
想要在主线中得到某个子线程任务函数返回的结果
1 2 3 4 5 6 future () noexcept ;future ( future&& other ) noexcept ;future ( const future& other ) = delete ;
构造函数①:默认无参构造函数
构造函数②:移动构造函数,转移资源的所有权
构造函数③:使用=delete
显示删除拷贝构造函数, 不允许进行对象之间的拷贝
成员函数
1 2 3 4 5 6 7 8 9 10 11 T get () ;T& get () ;void get () ;void wait () const ;template < class Rep, class Period >std::future_status wait_for ( const std::chrono::duration<Rep,Period>& timeout_duration ) const ;template < class Clock, class Duration >std::future_status wait_until ( const std::chrono::time_point<Clock,Duration>& timeout_time ) const ;
当 wait_until()
和 wait_for()
函数返回之后,并不能确定子线程当前的状态,因此我们需要判断函数的返回值,这样就能知道子线程当前的状态了:
常量
解释
future_status::deferred
子线程中的任务函仍未启动
future_status::ready
子线程中的任务已经执行完毕,结果已就绪
future_status::timeout
子线程中的任务正在执行中,指定等待时长已用完
以下实现中使用 ref()
原因是都删除了拷贝构造,因此只能传引用或者移动构造
promise 成员函数 1 2 std::future<T> get_future () ;
存储要传出的 value
值,并立即让状态就绪,这样数据被传出其它线程就可以得到这个数据了
1 2 3 4 void set_value ( const R& value ) ;void set_value ( R&& value ) ;void set_value ( R& value ) ;void set_value () ;
存储要传出的 value
值,但是不立即令状态就绪。在当前线程退出时,子线程资源被销毁,再令状态就绪
1 2 3 4 void set_value_at_thread_exit ( const R& value ) ;void set_value_at_thread_exit ( R&& value ) ;void set_value_at_thread_exit ( R& value ) ;void set_value_at_thread_exit () ;
使用 通过 promise
传递数据的过程一共分为 5步:
在主线程中创建 std::promise
对象
将这个 std::promise
对象通过引用的方式传递给子线程的任务函数
在子线程任务函数中给 std::promise
对象赋值
在主线程中通过 std::promise
对象取出绑定的 future
实例对象
通过得到的 future
对象取出子线程任务函数中返回的值
用于在一个线程中产生一个值,并在另一个线程中获取这个值
1 2 3 4 5 6 7 8 9 10 11 void func (std::promise<int > &f) { f.set_value (1000 ); } int main () { std::promise<int > f; auto future_result = f.get_future (); std::thread t1 (func, ref(f)) ; t1. join (); cout << future_result.get (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <thread> #include <future> using namespace std;int main () { promise<int > pr; thread t1 ([](promise<int > &p) { p.set_value_at_thread_exit(100 ); this_thread::sleep_for(chrono::seconds(3 )); cout << "睡醒了...." << endl; }, ref(pr)) ; future<int > f = pr.get_future (); int value = f.get (); cout << "value: " << value << endl; t1. join (); return 0 ; }
packaged_task
std::packaged_task
类包装了一个可调用对象包装器类对象(可调用对象包装器包装的是可调用对象,可调用对象都可以作为函数来使用)
这个类可以将内部包装的函数和 future
类绑定到一起,以便进行后续的异步调用,它和 std::promise
有点类似,std::promise
内部保存一个共享状态的值,而 std::packaged_task
保存的是一个函数。
构造函数 1 2 3 4 5 6 7 8 9 packaged_task () noexcept ;template <class F >explicit packaged_task ( F&& f ) ;packaged_task ( const packaged_task& ) = delete ;packaged_task ( packaged_task&& rhs ) noexcept ;
构造函数①:无参构造,构造一个无任务的空对象
构造函数②:通过一个可调用对象,构造一个任务对象
构造函数③:显示删除,不允许通过拷贝构造函数进行对象的拷贝
构造函数④:移动构造函数
使用 packaged_task
其实就是对子线程要执行的任务函数进行了包装,和可调用对象包装器的使用方法相同,包装完毕之后直接将包装得到的任务对象传递给线程对象就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <thread> #include <future> using namespace std;int main () { packaged_task<int (int ) > task ([](int x) { return x += 100 ; }) ; thread t1 (ref(task), 100 ) ; future<int > f = task.get_future (); int value = f.get (); cout << "value: " << value << endl; t1. join (); return 0 ; }
std::thread
可以直接使用 packaged_task
在这个例子中,std::thread
的构造函数可以直接接受一个可调用对象,包括 packaged_task
thread
构造时,将 task
和参数 100
直接传给了 std::thread
,并且 task
在 thread t1(ref(task), 100)
中直接被调用
std::thread
可以直接接受 packaged_task
,因为线程构造函数知道如何调用这个可调用对象,并且会立即执行它
但是在线程池中,如果任务队列是个可调用对象包装器,就不能直接使用这个,需要先用 lambda
包装成为一个匿名仿函数,再传入任务队列中
也可使用移动语义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <future> int func () { int res = 0 ; for (int i = 0 ; i < 1000 ; i++) { res ++; } return res; } int main () { std::packaged_task<int () > task (func) ; auto future_result = task.get_future (); std::thread t1 (std::move(task)) ; cout << func ()<< endl; t1. join (); cout << future_result.get () << endl; }
async
std::async
函数比前面提到的 std::promise
和 packaged_task
更高级一些,因为通过这函数可以直接启动一个子线程并在这个子线程中执行对应的任务函数
异步任务执行完成返回的结果也是存储到一个 future
对象中,当需要获取异步任务的结果时,只需要调用 future
类的 get()
方法即可
如果不关注异步任务的结果,只是简单地等待任务完成的话,可以调用 future
类的 wait()
或者 wait_for()
方法
构造函数 1 2 3 async ( Function&& f, Args&&... args );async ( std::launch policy, Function&& f, Args&&... args );
f
:可调用对象,这个对象在子线程中被作为任务函数使用
Args
:传递给 f
的参数(实参)
policy
:可调用对象 f
的执行策略
策略
说明
std::launch:: async
调用 async 函数时创建新的线程执行任务函数
std::launch::deferred
调用 async 函数时不执行任务函数,直到调用了 future 的 get()或者 wait()时才执行任务(这种方式不会创建新的线程)
使用 调用 async()函数直接创建线程执行任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <thread> #include <future> using namespace std;int main () { cout << "主线程ID: " << this_thread::get_id () << endl; future<int > f = async ([](int x) { cout << "子线程ID: " << this_thread::get_id () << endl; this_thread::sleep_for (chrono::seconds (5 )); return x += 100 ; }, 100 ); future_status status; do { status = f.wait_for (chrono::seconds (1 )); if (status == future_status::deferred) { cout << "线程还没有执行..." << endl; f.wait (); } else if (status == future_status::ready) { cout << "子线程返回值: " << f.get () << endl; } else if (status == future_status::timeout) { cout << "任务还未执行完毕, 继续等待..." << endl; } } while (status != future_status::ready); return 0 ; }
其实直接调用 f.get()
就能得到子线程的返回值
这里演示一下 wait_for
使用
输出
1 2 3 4 5 6 7 8 主线程ID: 8904 子线程ID: 25036 任务还未执行完毕, 继续等待... 任务还未执行完毕, 继续等待... 任务还未执行完毕, 继续等待... 任务还未执行完毕, 继续等待... 任务还未执行完毕, 继续等待... 子线程返回值: 200
调用 async()函数不创建线程执行任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <thread> #include <future> using namespace std;int main () { cout << "主线程ID: " << this_thread::get_id () << endl; future<int > f = async (launch::deferred, [](int x) { cout << "子线程ID: " << this_thread::get_id () << endl; return x += 100 ; }, 100 ); this_thread::sleep_for (chrono::seconds (5 )); cout << f.get (); return 0 ; }
1 2 3 4 主线程ID: 24760 主线程开始休眠5 秒... 子线程ID: 24760 200
总结
使用 async()
函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值
使用 std::promise
类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活
使用 std::packaged_task
类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值
说明: 参考学习: https://subingwen.cn/
xingzhu
keep trying!keep doing!believe in yourself!
此文章版权归作者所有,如有转载,请注明来自原作者