一、一些概念
并发:
有两个或两个以上的任务(独立的活动),同时发生(进行),或者说一个程序同时执行多个独立任务。
当cpu的数量少于任务数的时候,其实只是通过轮流调度来实现一种表面的并发进行。
使用并发的原因:可以同时执行多个任务,有提高性能的作用
进程:
可以理解为一个正在执行的可执行程序。
线程:
每个进程都有一个主线程,可以理解为代码的执行通路,我们可以写代码来创建其它线程,就可以实现,在同一个时间,在同一个进程执行多个任务
线程并非越多越好,线程切换也是需要时间的,需要保存恢复各种局部变量。(一般200~300个为佳)
多进程并发与多线程并发
多进程实现并发相比于多线程实现并发而言,它还需要切换虚拟地址空间,开销更大。
多进程之间通信需要借助管道、文件、消息队列、共享内存、socket等技术手段
多线程之间他们是共享一个虚拟地址空间的,即可以共享内存,线程间切换开销更小,但需要解决的一个重要问题就是:要保证数据一致性
以往在不同的开发平台上进行多线程开发都有不同的库、不同的函数,因此编写出来的程序一般不能跨平台。从c++11新标准开始,c++语言本身增加了对多线程的支持,使得编写的程序有了可移植性
二、创建线程的多种方法
- thread
( 一定会创建线程,如果资源太紧张导致创建不了,程序就会崩溃 ) - async
(也叫创建一个异步任务,但当参数被指定为launch::deferred时,就不会创建线程)(同时相比thread,它一般用于需要返回值的线程)
A.thread创建线程
① 创建一个函数,然后把该函数作为线程的入口地址
void readTheval(int num) { cout << "我是读线程" << this_thread::get_id() ; cout << " 我读到的数据是:" << num << endl; return; } //main函数里面: thread th1(readTheval, 5);//创建线程并开始执行 th1.join();
② 创建一个类,并编写圆括号重载函数,初始化一个该类的对象,把该对象作为线程入口地址
class CircleReLoad { public: void operator()() { cout << "创建了一个线程" << this_thread::get_id() << endl; /* ... */ cout << "线程执行结束!" << endl; } }; //main函数里的: CircleReLoad Acase; thread th1(Acase); th1.join();
③ lambda表达式创建线程
//main函数里 auto OneThread = [] { cout << "lambda表达式创建线程" << endl; /* … */ cout << this_thread::get_id() << "线程执行结束 " << endl; }; thread th1(OneThread); th1.join();
④把某个类中的某个函数作为线程的入口地址
class Data_ { public: void GetMsg(){} void SaveMsh(){} }; //main函数里 Data_ s; thread oneobj(&Data_::SaveMsh,&s); thread twoobj(&Data_::GetMsg,&s); oneobj.join(); twoobj.join();
多个线程的创建、管理:
可以用vector等容器来存放
void TextThread() { cout << "我是线程" << this_thread::get_id() << endl; /* … */ cout << "线程" << this_thread::get_id() << "执行结束" << endl; } //main函数里 vector threadagg; for (int i = 0; i < 10; ++i) { threadagg.push_back(thread(TextThread)); } for (int i = 0; i < 10; ++i) { threadagg[i].join(); }
join(), detach(), joinable()成员函数
join():
加入/汇合,起到了让主线程等待子线程执行完毕的作用,让子线程汇合到主线程。
(一般来讲,主线程都是最后结束的,保证所有子线程都能正常的执行完毕)
detach():
分离,将子线程和主线程分离开来,即子线程就不和主线程汇合了,主线程也不必等待子线程执行完毕
一旦detach,与主线程里的thread对象就会失去对这个子线程的控制,这个子线程就会驻留在后台运行(当此子线程执行完后,由系统自己来清理该线程的资源)
(注意如果用detach,并且该线程使用到一个主线程里的局部变量,比如说子线程中有引用或指针使指向主线程里的变量,那么就会使程序崩溃)
(当传入参数存在隐式转换的时候,也有可能出现一个问题,即主线程已经结束,传入参数的的这个变量已经被回收了,它才开始进行隐式转换并执行构造函数,这样就会引发错误)
(一句话能用join就不要detach)
joinable()
可以判断某thread对象是否还可以join或detach,可以就返回true,否则返回false
B.async创建线程
async是一个函数模板,用来启动异步任务,并返回future对象,future是一个类模板
future对象里边包含线程入口函数所返回的结果(线程返回结果),后续可通过get()成员函数得到返回值。
(当你希望多次调用future的成员函数get时,可改用shared_future类模板,当然这只是将结果存储起来,多次调用的get的值是一样的)
int TextThread(int num) { cout << "我是线程" << this_thread::get_id() << endl; /* … */ cout << "线程" << this_thread::get_id() << "执行结束" << endl; return num; } //main函数里 future result = async(launch::async,TextThread,6);// cout << result.get();
其中:
要是launch::async省略的话,系统会把它当成launch::async | launch::deferred ,即系统会根据当前资源状况自由调度,如果资源不紧张一般会当成launch::async,即会立刻创建一个异步任务并开始执行,反之,则会当成是launch::deferred,会等到你调用成员函数get或wait的时候才由主线程去完成(类似于调用普通函数),当省略该参数时,可以用future_status判断系统最终有无创建线程)
future_status
int GetAVaule(int num)
{
cout << "我正在执行啊!" << endl;
cout << "我的ID是:" << this_thread::get_id() << endl;
Sleep(3000);
return num;
}
//main函数里
shared_future result = async(GetAVaule, 55);
future_status status = result.wait_for(chrono::seconds(4));//等4秒看看能不能执行完(wait_for括号里可直接写4s)
if (status == future_status::timeout)
{
cout << "任务超时,还没执行玩呢" << endl;
}
else if (status == future_status::ready)
{
cout << "给的时间够用,我已经准备好了" << endl;
cout << result.get() << endl;
}
else if (status == future_status::deferred)
{
cout << "如果过你是延迟执行的参数,那就到这来" << endl;
}
packaged_task
打包任务,把任务包装起来,是一个类模板,是一个用来包装可调用对象的可调用对象
// TextThread为上面那个函数
packaged_task obj(TextThread);//包装
thread th1(ref(obj),6);//开始执行线程
th1.join();
future result = obj.get_future();
cout << result.get() << endl;
promise
一个类模板,能够在某个线程中给它赋值,然后可以在其它线程中,把这个值取出来用
void MyThread(promise& pro, int num)
{
num *= 2;
pro.set_value(num);
return;
}
//main函数里
promise my_prom;
thread th1(MyThread, ref(my_prom), 66);
th1.join();
future result = my_prom.get_future();//future和promise绑定,用于获取线程返回值
cout << result.get() << endl;