目录
前言
线程创建
标准库thread(同步线程的创建过程)
启动线程:实例thread
线程执行单元(可调用对象)
线程等待
线程传参
线程id
成员方法获取线程id
命名空间获取线程id
让出线程资源
sleep_for()
sleep_until
thread的构造函数
thread的成员函数
异步async(异步线程)、future
从实例中理解异步同步线程
std::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做绑定
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
sleep(1);
}
return 0;
}
int myfun2()
{
for(int i=0;i<3;i++)
{
cout << "count = "<
命名空间获取线程id
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
sleep(1);
}
cout <<"t1:id = "<
让出线程资源
在线程中使用this_thread::yield()函数可以交出执行当前线程的CPU去调度其它线程。
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
sleep(1);
this_thread::yield();
}
cout <<"t1:id = "<
sleep_for()
作用和sleep一样,但可以实现精确传参。作用是使线程休眠某个指定的时间片(time span),该线程才被重新唤醒。
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
//sleep(1);
this_thread::sleep_for(3000ms);
}
cout <<"t1:id = "<
sleep_until
线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。
作用:阻塞当前正在执行的线程直到sleep_time溢出。
sleep_time是和时钟相关联的,也就是要注意时钟调整会影响到sleep_time。
因此, 时钟的时长有可能或没有可能会短于或长于sleep_time。Clock::now()返回调用这个函数时的时间,取决于调整方向。该函数也有可能因为调度或者资源竞争而导致阻塞时间延长到sleep_time溢出之后。
#include
#include
#include
#include
#include
#pragma warning(disable:4996)//加上可去掉unsafe 请使用localtime_s的编译报错
int main()
{
using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t(system_clock::now());
struct std::tm *ptm = std::localtime(&tt);
std::cout << "Current time: " << std::put_time(ptm, "%X") << '\n'; //必须大写X,若小写x,输出的为日期
std::cout << "Waiting for the next minute to begin...\n";
++ptm->tm_min;
ptm->tm_sec = 0;
std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
std::cout << std::put_time(ptm, "%X") << "reached!\n";
getchar();
return 0;
}
thread的构造函数
在C++中,std::thread的构造函数可以接受一个可调用的对象(函数、函数指针、lambda表达式、bind表达式等),以此来指定在新线程中要执行的任务。
以下是std::thread的几种常见的构造函数形式:
1.默认构造函数:创建一个表示未开始执行的线程的对象。
std::thread();
2.接受一个可调用的对象作为参数,并开始一个新的线程来执行该任务。
template
explicit thread(Function&& f, Args&&... args);
例如:
void my_function(int x, int y) { ... }
int main() {
std::thread t(&my_function, 10, 20); // 创建一个新线程来执行 my_function,参数为 10 和 20
t.join(); // 等待新线程执行完毕
return 0;
}
3.接受两个可调用的对象作为参数,并开始一个新的线程来执行这两个任务。这是两个任务的并行执行,不是第一个任务等待第二个任务。
template
void thread(Function1&& f1, Function2&& f2);
例如:
void my_function1() { ... }
void my_function2() { ... }
int main() {
std::thread t1(&my_function1); // 创建一个新线程来执行 my_function1
std::thread t2(&my_function2); // 创建一个新线程来执行 my_function2
t1.join(); // 等待第一个线程执行完毕
t2.join(); // 等待第二个线程执行完毕
return 0;
}
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语言的线程接口了。
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
//sleep(1);
this_thread::sleep_for(3000ms);
}
cout <<"t1:id = "<异步async(异步线程)、future
std::async用于创建异步任务,实际上就是创建一个线程执行相应任务
std::futureresult = std::async(mythread);//创建一个线程并开始执行,绑定关系,流程并不卡在这里
std::futureresult = std::async(std::launch::deferred,&A::mythread2,&a,tmppar);//第二个参数是对象引用,才能确保线程里面是同一个对象
从实例中理解异步同步线程
创建同步线程,当主线程退出时,不用detach和join会导致子线程未退出而造成资源泄露。
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<
当使用异步线程,则可以正常打印(创建异步线程,会使主线程阻塞,在次线程执行完之后再继续执行)。异步任务不属于当前开销,即使主线程结束,次线程任务仍可执行
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<
std::future可以从异步线程中获取返回值结果
future可以和async结合使用,来接收异步线程的返回值,当结合时,主线程将不会阻塞,可以实现真正的异步任务操作
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<result = async(mythread,5);//创建异步线程
for(int i=0;i<3;i++)
{
cout <<"main exit!"<
在主线程使用future::get方法会使主线程阻塞等待次线程结束
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<result = async(mythread,5);//创建异步线程
for(int i=0;i<3;i++)
{
cout <<"main exit!"<
shared_future可以获取多次结果
使用std::future创建的对象只可以调用一次get方法来获取线程结果,但使用shared_future可以共享某个共享状态的最终结果,可以拷贝多个
#include
#include
#include
#include
using namespace std;
int thread_func(int num)
{
for(int i=0;i<3;i++)
{
cout <<"num = "<mt(thread_func);
thread t(ref(mt),10);
t.join();
cout <<"thread exit!"<result = mt.get_future();
cout <<"result1 = "<
async参数
std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;(如果没有调用,即使主线程结束,线程入口函数也永远不会被调用)
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<result = async(mythread,5);//创建异步线程
futureresult1 = async(std::launch::deferred,mythread,6);
//cout<
wait()函数
使用wait()方法只用于通知此线程任务开始执行
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<result = async(mythread,5);//创建异步线程
futureresult1 = async(std::launch::deferred,mythread,6);
cout << "main exit!"<
std::launch::async:在调用async函数的时候就开始创建线程
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<result = async(mythread,5);//创建异步线程
futureresult1 = async(std::launch::async,mythread,6);
//cout<
wait_for函数
wait_for函数第一个作用是用于创建同步线程时,主线程固定阻塞一段时间后退出
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<mt(mythread);
futureresult=mt.get_future();
thread t(ref(mt),5);
//futureresult = async(mythread,5);//创建异步线程
//futureresult1 = async(std::launch::async,mythread,6);
cout << "main exit!"<
wait_for第二个作用是用来判断当前某个线程状态
#include
#include
#include
using namespace std;
int mythread(int num)
{
for(int i=0;i<3;i++)
{
cout << "hello world"<mt(mythread);
//futureresult=mt.get_future();
//thread t(ref(mt),5);
//futureresult = async(mythread,5);//创建异步线程
futureresult1 = async(std::launch::deferred,mythread,6);
cout << "main exit!"<
同步和异步总结补充
- 同步执行:同步,是所有的操作都做完,才返回给用户结果
- 异步执行:异步,不用等所有操作都做完,就响应用户请求
- 处理耗时操作(数据库大量写入或者查询、文件下载、复杂计算)
- 同步操作:所有的操作都做完,才返回给用户,这样用户在线等待的时间太长,给用户一种卡死了的感觉
- 异步操作:即先响应用户请求然后慢慢去执行耗时操作,用户体验较好
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()解除阻塞
单向通信
#include
#include
#include
using namespace std;
//程序目的:想要把线程1中的iVal值传给线程2
void Thread_Fun1(std::promise &p)
{
//为了突出效果,可以使线程休眠5s
this_thread::sleep_for(std::chrono::seconds(5));
int iVal = 233;
cout<<"传入数据(int)"< &f)
{
//阻塞函数,直到收到相关联的std::promise对象传入的数据
auto iVal = f.get();//iVal=233
cout<<"收到数据(int)"< prl;
//声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
futureful = prl.get_future();
//创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
std::thread t1(Thread_Fun1,std::ref(prl));
//创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
std::thread t2(Thread_Fun2,std::ref(ful));
//阻塞至线程结束
t1.join();
t2.join();
return 0;
}
双向通信
#include
#include
#include
using namespace std;
//程序目的:想要把线程1中的iVal值传给线程2
void Thread_Fun1(std::promise &p,std::future &f)
{
//为了突出效果,可以使线程休眠5s
this_thread::sleep_for(std::chrono::seconds(5));
int iVal = 233;
cout<<"传入数据(int)"< &f,std::promise &p)
{
//阻塞函数,直到收到相关联的std::promise对象传入的数据
auto iVal = f.get();//iVal=233
cout<<"收到数据(int)"< prl1;
promise prl2;
//声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
futureful1 = prl1.get_future();
futureful2 = prl2.get_future();
//创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
std::thread t1(Thread_Fun1,std::ref(prl1),std::ref(ful2));
//创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
std::thread t2(Thread_Fun2,std::ref(ful1),std::ref(prl2));
//阻塞至线程结束
t1.join();
t2.join();
return 0;
}
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进行返回值的绑定,头文件为
#include
#include
#include
#include
using namespace std;
int thread_fun1(int num)
{
cout << "num = "<
packaged_taskmt(thread_fun1);//packaged_task为类模板
futureresult = mt.get_future(); //future也为类模板,要指定线程的返回值类型
/*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
//包装后需要按引用来传参
thread t(std::ref(mt),5);
t.join();
cout<<"result count = "<
其中future的get方法会阻塞,直到线程结束为止,获取返回值
#include
#include
#include
#include
using namespace std;
int thread_fun1(int num)
{
cout << "num = "<
packaged_taskmt(thread_fun1);//packaged_task为类模板
futureresult = mt.get_future(); //future也为类模板,要指定线程的返回值类型
/*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
//包装后需要按引用来传参
thread t(std::ref(mt),5);
cout<<"result count = "<
创建使用(对参数的具体讨论)
#include
#include
#include
using namespace std;
void mythread(void)//线程函数可以随便写,参数和返回值不固定
{
for(int i=0;i<3;i++)
{
cout<<"hello world"<
线程传参
对于线程传参,直接在对象后面添加传入参数
#include
#include
#include
using namespace std;
void mythread(int num)//线程函数可以随便写,参数和返回值不固定
{
for(int i=0;i<3;i++)
{
cout << "num = "<
如果想要传入字符串,会报错,这是因为传入字符串,构造函数会默认转化成string类的对象
使用string,就不会报错
或者使用传入字符串指针或者数组
lambda表达式作为线程执行单元
#include
#include
#include
using namespace std;
int main(int argc, char const *argv[])
{
auto F = [](int num,const char *str)
{
for(int i=0;i<3;i++)
{
cout << "num = "<
函数对象作为线程执行单元
... ...
class Test
{
public:
void operator()(int num,const char *ptr)
{
for(int i=0;i<3;i++)
{
cout << "num = "<
成员函数作为线程执行单元
线程构造函数第一个参数为成员函数时,由于成员函数的地址代表在类中的偏移量,因此第二个参数必须是对象的本身,可以是对象、对象的地址或者对象的引用
class MyThread
{
public:
void myfun(int num, const char *ptr)
{
for(int i=0;i<3;i++)
{
cout << "num = "<
引用传参
如果参数传递引用
比如传入一个对象的左值引用,则会报错
在传入引用的时候,我们必须使用`std::ref `,它是 C++ 标准库中的一个函数模板,它的作用是将传递给它的对象包装成一个可以传递给函数的引用包装器。这样可以让那些原本不接受引用的函数接受引用参数,从而实现在函数中修改传入对象的值。
#include
#include
#include
using namespace std;
void mythread(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
{
for(int i=0;i<3;i++)
{
cout << "num = "<
C++线程不能直接传引用的原因是为了防止次线程还没来得及退出,主线程就结束了。比如我们忘记使用join(由主线程回收资源)或者使用了detach(主线程退出后,由系统回收线程资源),string &rs创建的rs对象当主线程退出时就会释放,rs作为次线程传入的参数,而次线程还没来得及退出,这就会导致安全性的问题。因此编译器默认引用传递存在危险,因此使用ref。
例:使用引用传递在线程中修改变量值
#include
#include
#include
using namespace std;
void mythread2(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
{
cout << "num = "<
如果接收参数是右值引用,不能用ref
需要使用move强制转换为右值引用再传参
#include
#include
#include
using namespace std;
void mythread3(int &&num,string& str)//线程函数可以随便写,参数和返回值不固定
{
cout << "num = "<
类成员作为执行单元的共享成员变量
当使用类成员函数作为执行单元,传递对象,无法共用成员变量,传递对象的引用或者地址可以共用成员变量。
如传递对象,则结果如下:
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
sleep(1);
}
return 0;
}
int myfun2()
{
for(int i=0;i<3;i++)
{
cout << "count = "<
可以发现打印结果全为0
如果使用地址或者引用,结果如下:
#include
#include
#include
using namespace std;
class MyThread
{
public:
int myfun1()
{
for(int i=0;i<3;i++)
{
m_count++;
sleep(1);
}
return 0;
}
int myfun2()
{
for(int i=0;i<3;i++)
{
cout << "count = "<
发现线程操作成员变量正常