目录
前言
线程创建
标准库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 = "<
注:结果中count=1执行两次是因为两个线程执行次序问题,可以使用条件变量来解决
#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一样,但可以实现精确传参。作用是使线程休眠某个指定的时间片(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 = "<
线程休眠至某个指定的时刻(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;
}
在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;
}
常用函数在前面和后面都有具体介绍,重要的有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 = "<
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"<
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!"<
使用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 = "<
#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()方法只用于通知此线程任务开始执行
#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!"<
#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函数第一个作用是用于创建同步线程时,主线程固定阻塞一段时间后退出
#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!"<
对于线程间的通信,我们一般通过全局变量来实现,但在C++中通过std::promise也可以实现线程间的通信,相对于全局变量,不需要用锁来确保安全性
#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::future,包括函数运行时产生的异常
注意事项(拓展)
使用实例
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,就不会报错
或者使用传入字符串指针或者数组
#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 = "<
发现线程操作成员变量正常