当前位置:   article > 正文

C++多线程编程——thread线程创建与使用(2W字保姆级介绍)_c++ thread

c++ thread

目录

前言

线程创建

标准库thread(同步线程的创建过程)

启动线程:实例thread

线程执行单元(可调用对象)

线程等待

线程传参

线程id

成员方法获取线程id

命名空间获取线程id

让出线程资源

sleep_for()

sleep_until

thread的构造函数

thread的成员函数

异步async(异步线程)、future

从实例中理解异步同步线程

std::future可以从异步线程中获取返回值结果

shared_future可以获取多次结果

async参数

std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;(如果没有调用,即使主线程结束,线程入口函数也永远不会被调用)

wait()函数

std::launch::async:在调用async函数的时候就开始创建线程

wait_for函数

同步和异步总结补充

std::promise

单向通信

 双向通信

std::package_task

创建使用(对参数的具体讨论)

线程传参

lambda表达式作为线程执行单元

函数对象作为线程执行单元

成员函数作为线程执行单元

引用传参

类成员作为执行单元的共享成员变量


前言

C++标准库封装了POSIX线程库,将其封装成了一个线程类。学习C++标准库多线程的好处是:第一可以跨平台,第二上层封装可以解决很多不必要的麻烦,同时解决了运行效率问题,尤其对于线程同步来说,比用原生态的C接口简单得多,提供了高效写多线程的一种方式

多进程编程主要考虑的是进程间通信,而多线程主要考虑的是线程同步

线程创建

C++提供了两种线程:同步线程和异步线程

  • 同步执行:同步,是所有的操作都做完,才返回给用户结果
  • 异步执行:异步,不用等所有操作都做完,就响应用户请求

基本上所有实际应用的线程都是异步的,比如点击浏览器进行下载,浏览器正在下载的同时,我们还可以做其它事情。

对于线程的创建,必须要掌握:

第一步:要学会用thread创建同步线程,

第二步:当某些情况下需要使用async创建异步任务,

第三步:在thread和async里最重要的就是future,用future来获取线程返回值结果,当thread创建时需要用packaged_task做绑定包装

第四步:当要做线程间通信时,需要用promise和future做绑定

标准库thread(同步线程的创建过程)

启动线程:实例thread

线程执行单元(可调用对象)

  • 普通函数
  • 函数对象
  • 类成员函数:第二个参数为对象的引用或指针
  • lambda表达式

线程等待

  • void std::thread::join();
  • void std::thread::detach();

线程传参

  • 用detach()时,如果主线程先结束,变量就会被回收;所以用detach()的话,不推荐用引用
  • 如果参数有引用就会发生错误,需要使用std::ref来告诉其传递的是引用。(std::ref()函数的作用:可以实现真正的引用传递)
  • 智能指针做参数传递,可以直接传

线程id

  • std::this_thread命名空间:命名空间保存了当前线程的所有信息
  • std::this_thread::get_id()来获取
成员方法获取线程id
  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. sleep(1);
  14. }
  15. return 0;
  16. }
  17. int myfun2()
  18. {
  19. for(int i=0;i<3;i++)
  20. {
  21. cout << "count = "<<m_count << endl;
  22. sleep(1);
  23. }
  24. return 0;
  25. }
  26. int m_count=0;
  27. };
  28. int main(int argc, char const *argv[])
  29. {
  30. MyThread mt;
  31. thread t1(&MyThread::myfun1,&mt);
  32. thread t2(&MyThread::myfun2,&mt);
  33. cout <<"t1:id = "<<t1.get_id()<<endl;
  34. cout <<"t2:id = "<<t2.get_id()<<endl;
  35. t1.join();
  36. t2.join();
  37. return 0;
  38. }

注:结果中count=1执行两次是因为两个线程执行次序问题,可以使用条件变量来解决

命名空间获取线程id
  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. sleep(1);
  14. }
  15. cout <<"t1:id = "<<this_thread::get_id()<<endl;
  16. return 0;
  17. }
  18. int myfun2()
  19. {
  20. for(int i=0;i<3;i++)
  21. {
  22. cout << "count = "<<m_count << endl;
  23. sleep(1);
  24. }
  25. cout <<"t2:id = "<<this_thread::get_id()<<endl;
  26. return 0;
  27. }
  28. int m_count=0;
  29. };
  30. int main(int argc, char const *argv[])
  31. {
  32. MyThread mt;
  33. thread t1(&MyThread::myfun1,&mt);
  34. thread t2(&MyThread::myfun2,&mt);
  35. t1.join();
  36. t2.join();
  37. return 0;
  38. }

让出线程资源

 在线程中使用this_thread::yield()函数可以交出执行当前线程的CPU去调度其它线程。

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. sleep(1);
  14. this_thread::yield();
  15. }
  16. cout <<"t1:id = "<<this_thread::get_id()<<endl;
  17. return 0;
  18. }
  19. int myfun2()
  20. {
  21. for(int i=0;i<3;i++)
  22. {
  23. cout << "count = "<<m_count << endl;
  24. sleep(1);
  25. }
  26. cout <<"t2:id = "<<this_thread::get_id()<<endl;
  27. return 0;
  28. }
  29. int m_count=0;
  30. };
  31. int main(int argc, char const *argv[])
  32. {
  33. MyThread mt;
  34. thread t1(&MyThread::myfun1,&mt);
  35. thread t2(&MyThread::myfun2,&mt);
  36. t1.join();
  37. t2.join();
  38. return 0;
  39. }

sleep_for()

作用和sleep一样,但可以实现精确传参。作用是使线程休眠某个指定的时间片(time span),该线程才被重新唤醒。

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. //sleep(1);
  14. this_thread::sleep_for(3000ms);
  15. }
  16. cout <<"t1:id = "<<this_thread::get_id()<<endl;
  17. return 0;
  18. }
  19. int myfun2()
  20. {
  21. for(int i=0;i<3;i++)
  22. {
  23. cout << "count = "<<m_count << endl;
  24. sleep(1);
  25. }
  26. cout <<"t2:id = "<<this_thread::get_id()<<endl;
  27. return 0;
  28. }
  29. int m_count=0;
  30. };
  31. int main(int argc, char const *argv[])
  32. {
  33. MyThread mt;
  34. thread t1(&MyThread::myfun1,&mt);
  35. thread t2(&MyThread::myfun2,&mt);
  36. t1.join();
  37. t2.join();
  38. return 0;
  39. }

sleep_until

线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

作用:阻塞当前正在执行的线程直到sleep_time溢出。

sleep_time是和时钟相关联的,也就是要注意时钟调整会影响到sleep_time。

因此, 时钟的时长有可能或没有可能会短于或长于sleep_time。Clock::now()返回调用这个函数时的时间,取决于调整方向。该函数也有可能因为调度或者资源竞争而导致阻塞时间延长到sleep_time溢出之后。

  1. #include <iostream>
  2. #include <iomanip>
  3. #include <chrono>
  4. #include <ctime>
  5. #include <thread>
  6. #pragma warning(disable:4996)//加上可去掉unsafe 请使用localtime_s的编译报错
  7. int main()
  8. {
  9. using std::chrono::system_clock;
  10. std::time_t tt = system_clock::to_time_t(system_clock::now());
  11. struct std::tm *ptm = std::localtime(&tt);
  12. std::cout << "Current time: " << std::put_time(ptm, "%X") << '\n'; //必须大写X,若小写x,输出的为日期
  13. std::cout << "Waiting for the next minute to begin...\n";
  14. ++ptm->tm_min;
  15. ptm->tm_sec = 0;
  16. std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
  17. std::cout << std::put_time(ptm, "%X") << "reached!\n";
  18. getchar();
  19. return 0;
  20. }

 

thread的构造函数

在C++中,std::thread的构造函数可以接受一个可调用的对象(函数、函数指针、lambda表达式、bind表达式等),以此来指定在新线程中要执行的任务。

以下是std::thread的几种常见的构造函数形式:

1.默认构造函数:创建一个表示未开始执行的线程的对象。

std::thread();

2.接受一个可调用的对象作为参数,并开始一个新的线程来执行该任务。

  1. template <typename Function, typename... Args>
  2. explicit thread(Function&& f, Args&&... args);

例如:

  1. void my_function(int x, int y) { ... }
  2. int main() {
  3. std::thread t(&my_function, 10, 20); // 创建一个新线程来执行 my_function,参数为 10 和 20
  4. t.join(); // 等待新线程执行完毕
  5. return 0;
  6. }

3.接受两个可调用的对象作为参数,并开始一个新的线程来执行这两个任务。这是两个任务的并行执行,不是第一个任务等待第二个任务。

  1. template <typename Function1, typename Function2>
  2. void thread(Function1&& f1, Function2&& f2);

例如:

  1. void my_function1() { ... }
  2. void my_function2() { ... }
  3. int main() {
  4. std::thread t1(&my_function1); // 创建一个新线程来执行 my_function1
  5. std::thread t2(&my_function2); // 创建一个新线程来执行 my_function2
  6. t1.join(); // 等待第一个线程执行完毕
  7. t2.join(); // 等待第二个线程执行完毕
  8. return 0;
  9. }

thread的成员函数

常用函数在前面和后面都有具体介绍,重要的有join、detach、get_id和native_handle。

此外C++11的线程可以使用平台的特性拓展

std::native_handle_type std::native_handle();

该函数的作用是获取当前平台真正的线程id,如windows就返回windows的线程id,Linux就返回Linux的线程id。转换之后就可以通过线程id使用C语言的线程接口了。

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. //sleep(1);
  14. this_thread::sleep_for(3000ms);
  15. }
  16. cout <<"t1:id = "<<this_thread::get_id()<<endl;
  17. return 0;
  18. }
  19. int myfun2()
  20. {
  21. for(int i=0;i<3;i++)
  22. {
  23. cout << "count = "<<m_count << endl;
  24. sleep(1);
  25. }
  26. cout <<"t2:id = "<<this_thread::get_id()<<endl;
  27. return 0;
  28. }
  29. int m_count=0;
  30. };
  31. int main(int argc, char const *argv[])
  32. {
  33. MyThread mt;
  34. thread t1(&MyThread::myfun1,&mt);
  35. thread t2(&MyThread::myfun2,&mt);
  36. pthread_t id1 = t1.native_handle();
  37. pthread_t id2 = t2.native_handle();
  38. pthread_join(id1,nullptr);
  39. pthread_join(id2,nullptr);
  40. // t1.join();
  41. // t2.join();
  42. return 0;
  43. }

异步async(异步线程)、future

std::async用于创建异步任务,实际上就是创建一个线程执行相应任务

std::futureresult = std::async(mythread);//创建一个线程并开始执行,绑定关系,流程并不卡在这里

std::futureresult = std::async(std::launch::deferred,&A::mythread2,&a,tmppar);//第二个参数是对象引用,才能确保线程里面是同一个对象

从实例中理解异步同步线程

创建同步线程,当主线程退出时,不用detach和join会导致子线程未退出而造成资源泄露。

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. thread t1(mythread,5);//创建同步线程
  17. //async(mythread,5);//创建异步线程
  18. return 0;
  19. }

当使用异步线程,则可以正常打印(创建异步线程,会使主线程阻塞,在次线程执行完之后再继续执行)。异步任务不属于当前开销,即使主线程结束,次线程任务仍可执行

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. async(mythread,5);//创建异步线程
  18. for(int i=0;i<3;i++)
  19. {
  20. cout <<"main exit!"<<endl;
  21. this_thread::sleep_for(1s);
  22. }
  23. return 0;
  24. }

std::future可以从异步线程中获取返回值结果

future可以和async结合使用,来接收异步线程的返回值,当结合时,主线程将不会阻塞,可以实现真正的异步任务操作

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. future<int>result = async(mythread,5);//创建异步线程
  18. for(int i=0;i<3;i++)
  19. {
  20. cout <<"main exit!"<<endl;
  21. this_thread::sleep_for(1s);
  22. }
  23. return 0;
  24. }

在主线程使用future::get方法会使主线程阻塞等待次线程结束

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. future<int>result = async(mythread,5);//创建异步线程
  18. for(int i=0;i<3;i++)
  19. {
  20. cout <<"main exit!"<<endl;
  21. this_thread::sleep_for(1s);
  22. }
  23. cout<<result.get()<<endl;
  24. return 0;
  25. }

shared_future可以获取多次结果

使用std::future创建的对象只可以调用一次get方法来获取线程结果,但使用shared_future可以共享某个共享状态的最终结果,可以拷贝多个

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. #include <future>
  5. using namespace std;
  6. int thread_func(int num)
  7. {
  8. for(int i=0;i<3;i++)
  9. {
  10. cout <<"num = "<<num<<endl;
  11. this_thread::sleep_for(1s);
  12. }
  13. return num+5;
  14. }
  15. int main(int argc, char const *argv[])
  16. {
  17. packaged_task<int(int)>mt(thread_func);
  18. thread t(ref(mt),10);
  19. t.join();
  20. cout <<"thread exit!"<<endl;
  21. shared_future<int>result = mt.get_future();
  22. cout <<"result1 = "<<result.get()<<endl;
  23. cout <<"result2 = "<<result.get()<<endl;
  24. return 0;
  25. }

async参数

std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;(如果没有调用,即使主线程结束,线程入口函数也永远不会被调用)

 

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. //future<int>result = async(mythread,5);//创建异步线程
  18. future<int>result1 = async(std::launch::deferred,mythread,6);
  19. //cout<<result1.get()<<endl;
  20. return 0;
  21. }

wait()函数

使用wait()方法只用于通知此线程任务开始执行

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. //future<int>result = async(mythread,5);//创建异步线程
  18. future<int>result1 = async(std::launch::deferred,mythread,6);
  19. cout << "main exit!"<<endl;
  20. result1.wait();
  21. return 0;
  22. }

std::launch::async:在调用async函数的时候就开始创建线程
  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. //future<int>result = async(mythread,5);//创建异步线程
  18. future<int>result1 = async(std::launch::async,mythread,6);
  19. //cout<<result1.get()<<endl;
  20. return 0;
  21. }

wait_for函数

wait_for函数第一个作用是用于创建同步线程时,主线程固定阻塞一段时间后退出

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. packaged_task<int(int)>mt(mythread);
  18. future<int>result=mt.get_future();
  19. thread t(ref(mt),5);
  20. //future<int>result = async(mythread,5);//创建异步线程
  21. //future<int>result1 = async(std::launch::async,mythread,6);
  22. cout << "main exit!"<<endl;
  23. result.wait_for(500ms);
  24. return 0;
  25. }

wait_for第二个作用是用来判断当前某个线程状态

  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. int mythread(int num)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "hello world"<<endl;
  10. this_thread::sleep_for(1s);
  11. }
  12. return num+5;
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t1(mythread,5);//创建同步线程
  17. //packaged_task<int(int)>mt(mythread);
  18. //future<int>result=mt.get_future();
  19. //thread t(ref(mt),5);
  20. //future<int>result = async(mythread,5);//创建异步线程
  21. future<int>result1 = async(std::launch::deferred,mythread,6);
  22. cout << "main exit!"<<endl;
  23. //result.wait_for(500ms);
  24. future_status status;
  25. do{
  26. this_thread::sleep_for(500ms);
  27. status = result1.wait_for(std::chrono::seconds(1));
  28. if(status == std::future_status::deferred){
  29. std::cout<<"deferred\n";
  30. }else if(status == std::future_status::timeout){
  31. std::cout<<"timeout\n";
  32. }else if(status == std::future_status::ready){
  33. std::cout<<"ready!\n";
  34. }
  35. }while(status !=std::future_status::ready);
  36. return 0;
  37. }

同步和异步总结补充

  • 同步执行:同步,是所有的操作都做完,才返回给用户结果
  • 异步执行:异步,不用等所有操作都做完,就响应用户请求
  • 处理耗时操作(数据库大量写入或者查询、文件下载、复杂计算)
    • 同步操作:所有的操作都做完,才返回给用户,这样用户在线等待的时间太长,给用户一种卡死了的感觉
    • 异步操作:即先响应用户请求然后慢慢去执行耗时操作,用户体验较好

std::promise

对于线程间的通信,我们一般通过全局变量来实现,但在C++中通过std::promise也可以实现线程间的通信,相对于全局变量,不需要用锁来确保安全性

  • 类模板,std::promise保存的值可被与之关联的std::promise读取,读取操作可以发生再其它线程
  • 作用:std::promise和std::future合作共同实现了多线程间通信
  • 注意事项
    • std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值)
    • set_value只能被调用一次,多次调用会抛出std::future_error异常
    • 一个std::promise实例只能与一个std::future关联共享状态,当在同一个std::promise上反复调用get_future会抛出future_error异常
    • 通过std::promise让std::future抛出异常
      • std::promise虽然支持自定义异常,但它并不直接接受异常对象
      • 自定义异常可以通过位于头文件exception下的std::make_exception_ptr函数转化为std::exception_ptr。它表示异常的指针。这个指针可以用于在不同的上下文中传递和处理异常,例如在多线程环境中传递异常,或者在捕获异常后延迟处理。
    • std::promise此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞
单向通信
  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. //程序目的:想要把线程1中的iVal值传给线程2
  6. void Thread_Fun1(std::promise<int> &p)
  7. {
  8. //为了突出效果,可以使线程休眠5s
  9. this_thread::sleep_for(std::chrono::seconds(5));
  10. int iVal = 233;
  11. cout<<"传入数据(int)"<<iVal<<endl;
  12. //传入数据iVal
  13. p.set_value(iVal);
  14. }
  15. //发送者必须定义有promise,接收者必须定义有future
  16. void Thread_Fun2(std::future<int> &f)
  17. {
  18. //阻塞函数,直到收到相关联的std::promise对象传入的数据
  19. auto iVal = f.get();//iVal=233
  20. cout<<"收到数据(int)"<<iVal<<endl;
  21. }
  22. int main(int argc, char const *argv[])
  23. {
  24. //声明一个std::promise对象prl,其保存的值类型为int
  25. promise<int> prl;
  26. //声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
  27. future<int>ful = prl.get_future();
  28. //创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
  29. std::thread t1(Thread_Fun1,std::ref(prl));
  30. //创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
  31. std::thread t2(Thread_Fun2,std::ref(ful));
  32. //阻塞至线程结束
  33. t1.join();
  34. t2.join();
  35. return 0;
  36. }

 双向通信
  1. #include <iostream>
  2. #include <thread>
  3. #include <future>
  4. using namespace std;
  5. //程序目的:想要把线程1中的iVal值传给线程2
  6. void Thread_Fun1(std::promise<int> &p,std::future<int> &f)
  7. {
  8. //为了突出效果,可以使线程休眠5s
  9. this_thread::sleep_for(std::chrono::seconds(5));
  10. int iVal = 233;
  11. cout<<"传入数据(int)"<<iVal<<endl;
  12. //传入数据iVal
  13. p.set_value(iVal);
  14. auto num = f.get();
  15. cout<<"num(int)"<<iVal<<endl;
  16. }
  17. //发送者必须定义有promise,接收者必须定义有future
  18. void Thread_Fun2(std::future<int> &f,std::promise<int> &p)
  19. {
  20. //阻塞函数,直到收到相关联的std::promise对象传入的数据
  21. auto iVal = f.get();//iVal=233
  22. cout<<"收到数据(int)"<<iVal<<endl;
  23. iVal = 250;
  24. p.set_value(iVal);
  25. }
  26. int main(int argc, char const *argv[])
  27. {
  28. //声明一个std::promise对象prl,其保存的值类型为int
  29. promise<int> prl1;
  30. promise<int> prl2;
  31. //声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
  32. future<int>ful1 = prl1.get_future();
  33. future<int>ful2 = prl2.get_future();
  34. //创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
  35. std::thread t1(Thread_Fun1,std::ref(prl1),std::ref(ful2));
  36. //创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
  37. std::thread t2(Thread_Fun2,std::ref(ful1),std::ref(prl2));
  38. //阻塞至线程结束
  39. t1.join();
  40. t2.join();
  41. return 0;
  42. }

std::package_task

std::package_task它允许传入一个函数,并将函数计算的结果传给std::future,包括函数运行时产生的异常

注意事项(拓展)

  • std::package_task支持move,但不支持拷贝(copy)
  • get_future仅能调用一次,多次调用会触发std::future_error异常
  • std::packaged_task::valid该函数用于判断std::packaged_task对象是否是有效状态
  • std::packaged_task::reset()使std::packged_task可以执行多次

使用实例

package_task相对于一个包装器,如function:可以将要调用的对象包装成统一类型。比如我们想要获取线程退出的结果,再C接口中的int pthread_join(pthread_t thread,void **retval);中的retval就用于存放线程id退出的返回值。但使用C++的join无法获得线程退出的返回值。

我们可以使用package_task配合future进行返回值的绑定,头文件为<future>

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. #include <future>
  5. using namespace std;
  6. int thread_fun1(int num)
  7. {
  8. cout << "num = "<<num<<endl;
  9. int count = num + 5;
  10. return count;//返回值
  11. }
  12. int main(int argc, char const *argv[])
  13. {
  14. //使用包装器包装线程单元,包装类型为<返回值类型(形参类型)>
  15. packaged_task<int(int)>mt(thread_fun1);//packaged_task为类模板
  16. future<int>result = mt.get_future(); //future也为类模板,要指定线程的返回值类型
  17. /*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
  18. //包装后需要按引用来传参
  19. thread t(std::ref(mt),5);
  20. t.join();
  21. cout<<"result count = "<<result.get()<<endl;
  22. return 0;
  23. }

其中future的get方法会阻塞,直到线程结束为止,获取返回值

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. #include <future>
  5. using namespace std;
  6. int thread_fun1(int num)
  7. {
  8. cout << "num = "<<num<<endl;
  9. int count = num + 5;
  10. this_thread::sleep_for(3s);
  11. return count;//返回值
  12. }
  13. int main(int argc, char const *argv[])
  14. {
  15. //使用包装器包装线程单元,包装类型为<返回值类型(形参类型)>
  16. packaged_task<int(int)>mt(thread_fun1);//packaged_task为类模板
  17. future<int>result = mt.get_future(); //future也为类模板,要指定线程的返回值类型
  18. /*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
  19. //包装后需要按引用来传参
  20. thread t(std::ref(mt),5);
  21. cout<<"result count = "<<result.get()<<endl;//其中future的get方法会阻塞,直到线程结束为止,获取返回值
  22. cout <<"thread exiting!!!"<<endl;
  23. t.join();
  24. return 0;
  25. }

创建使用(对参数的具体讨论)

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. void mythread(void)//线程函数可以随便写,参数和返回值不固定
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout<<"hello world"<<endl;
  10. sleep(3);
  11. }
  12. }
  13. int main(int argc, char const *argv[])
  14. {
  15. thread t(mythread);//使用构造函数,传参为线程执行单元
  16. t.join();//pthread_join
  17. return 0;
  18. }

线程传参

对于线程传参,直接在对象后面添加传入参数

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. void mythread(int num)//线程函数可以随便写,参数和返回值不固定
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "num = "<<num<<endl;
  10. cout<<"hello world"<<endl;
  11. sleep(3);
  12. }
  13. }
  14. int main(int argc, char const *argv[])
  15. {
  16. //thread t(mythread);//使用构造函数,传参为线程执行单元
  17. thread t(mythread,5);
  18. t.join();//pthread_join
  19. return 0;
  20. }

如果想要传入字符串,会报错,这是因为传入字符串,构造函数会默认转化成string类的对象

使用string,就不会报错

或者使用传入字符串指针或者数组

lambda表达式作为线程执行单元
  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. int main(int argc, char const *argv[])
  6. {
  7. auto F = [](int num,const char *str)
  8. {
  9. for(int i=0;i<3;i++)
  10. {
  11. cout << "num = "<<num<<endl;
  12. cout <<str<<endl;
  13. cout<<"lambda func"<<endl;
  14. sleep(1);
  15. }
  16. };
  17. const char *ptr = "hello";
  18. //thread t(mythread);//使用构造函数,传参为线程执行单元
  19. thread t(F,5,ptr);
  20. t.join();//pthread_join
  21. return 0;
  22. }

函数对象作为线程执行单元
  1. ... ...
  2. class Test
  3. {
  4. public:
  5. void operator()(int num,const char *ptr)
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "num = "<<num<<endl;
  10. cout <<ptr<<endl;
  11. cout<<"Function object"<<endl;
  12. sleep(1);
  13. }
  14. }
  15. };
  16. int main(int argc, char const *argv[])
  17. {
  18. const char *ptr = "hello";
  19. Test test;
  20. thread t(test,5,ptr);
  21. t.join();//pthread_join
  22. return 0;
  23. }

成员函数作为线程执行单元

线程构造函数第一个参数为成员函数时,由于成员函数的地址代表在类中的偏移量,因此第二个参数必须是对象的本身,可以是对象、对象的地址或者对象的引用

 

  1. class MyThread
  2. {
  3. public:
  4. void myfun(int num, const char *ptr)
  5. {
  6. for(int i=0;i<3;i++)
  7. {
  8. cout << "num = "<<num<<endl;
  9. cout <<ptr<<endl;
  10. cout<<"Function object"<<endl;
  11. sleep(1);
  12. }
  13. }
  14. };
  15. int main(int argc, char const *argv[])
  16. {
  17. MyThread mt;
  18. const char *ptr = "hello";
  19. thread t(&MyThread::myfun,mt,5,ptr);
  20. t.join();//pthread_join
  21. return 0;
  22. }
引用传参

如果参数传递引用

比如传入一个对象的左值引用,则会报错

在传入引用的时候,我们必须使用`std::ref `,它是 C++ 标准库中的一个函数模板,它的作用是将传递给它的对象包装成一个可以传递给函数的引用包装器。这样可以让那些原本不接受引用的函数接受引用参数,从而实现在函数中修改传入对象的值。

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. void mythread(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
  6. {
  7. for(int i=0;i<3;i++)
  8. {
  9. cout << "num = "<<num<<endl;
  10. cout <<str<<endl;
  11. cout<<"hello world"<<endl;
  12. sleep(3);
  13. }
  14. }
  15. int main(int argc, char const *argv[])
  16. {
  17. int num = 5;
  18. int &l_num = num;//基本类型左值引用
  19. string s = "8.23";
  20. string &ls = s;
  21. thread t(mythread,std::ref(l_num),std::ref(ls));
  22. t.join();//pthread_join
  23. return 0;
  24. }

C++线程不能直接传引用的原因是为了防止次线程还没来得及退出,主线程就结束了。比如我们忘记使用join(由主线程回收资源)或者使用了detach(主线程退出后,由系统回收线程资源),string &rs创建的rs对象当主线程退出时就会释放,rs作为次线程传入的参数,而次线程还没来得及退出,这就会导致安全性的问题。因此编译器默认引用传递存在危险,因此使用ref。

例:使用引用传递在线程中修改变量值

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. void mythread2(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
  6. {
  7. cout << "num = "<<num<<endl;
  8. cout <<str<<endl;
  9. str = "2023";
  10. }
  11. int main(int argc, char const *argv[])
  12. {
  13. int num = 5;
  14. int &l_num = num;//基本类型左值引用
  15. string s = "8.23";
  16. string &ls = s;
  17. thread t(mythread2,std::ref(l_num),std::ref(ls));
  18. t.join();//pthread_join
  19. cout << s << endl;
  20. //t.detach();
  21. return 0;
  22. }

如果接收参数是右值引用,不能用ref

需要使用move强制转换为右值引用再传参

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. void mythread3(int &&num,string& str)//线程函数可以随便写,参数和返回值不固定
  6. {
  7. cout << "num = "<<num<<endl;
  8. cout <<str<<endl;
  9. str = "2023";
  10. }
  11. int main(int argc, char const *argv[])
  12. {
  13. int num = 5;
  14. int &l_num = num;//基本类型左值引用
  15. string s = "8.23";
  16. string &ls = s;
  17. thread t(mythread3,std::move(l_num),std::ref(ls));
  18. t.join();//pthread_join
  19. cout << s << endl;
  20. //t.detach();
  21. return 0;
  22. }

类成员作为执行单元的共享成员变量

当使用类成员函数作为执行单元,传递对象,无法共用成员变量,传递对象的引用或者地址可以共用成员变量。

如传递对象,则结果如下:

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. sleep(1);
  14. }
  15. return 0;
  16. }
  17. int myfun2()
  18. {
  19. for(int i=0;i<3;i++)
  20. {
  21. cout << "count = "<<m_count << endl;
  22. sleep(1);
  23. }
  24. return 0;
  25. }
  26. int m_count=0;
  27. };
  28. int main(int argc, char const *argv[])
  29. {
  30. MyThread mt;
  31. thread t1(&MyThread::myfun1,mt);
  32. thread t2(&MyThread::myfun2,mt);
  33. t1.join();
  34. t2.join();
  35. return 0;
  36. }

可以发现打印结果全为0

如果使用地址或者引用,结果如下:

  1. #include <iostream>
  2. #include <thread>
  3. #include <unistd.h>
  4. using namespace std;
  5. class MyThread
  6. {
  7. public:
  8. int myfun1()
  9. {
  10. for(int i=0;i<3;i++)
  11. {
  12. m_count++;
  13. sleep(1);
  14. }
  15. return 0;
  16. }
  17. int myfun2()
  18. {
  19. for(int i=0;i<3;i++)
  20. {
  21. cout << "count = "<<m_count << endl;
  22. sleep(1);
  23. }
  24. return 0;
  25. }
  26. int m_count=0;
  27. };
  28. int main(int argc, char const *argv[])
  29. {
  30. MyThread mt;
  31. thread t1(&MyThread::myfun1,&mt);
  32. thread t2(&MyThread::myfun2,&mt);
  33. t1.join();
  34. t2.join();
  35. return 0;
  36. }

发现线程操作成员变量正常

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/153490
推荐阅读
相关标签
  

闽ICP备14008679号