并发在生活中随处可见,边走路边说话,边听歌边写代码。计算机术语中的"并发",指的是在单个系统里同时执行多个独立的活动,而不是顺序的一个接一个的执行。对于单核CPU来讲,在某个时刻只可能处理一个任务,但它却不是彻底执行完一个任务再执行一个下一任务,而是一直在任务间切换,每一个任务完成一点就去执行下一个任务,看起来就像任务在并行发生,虽然不是严格的同时执行多个任务,可是咱们仍然称之为并发(concurrency)。真正的并发是在在多核CPU上,可以真正的同时执行多个任务,称为硬件并发(hardware concurrency)。ios
并发并不是没有代价,在单核CPU并发执行两个任务须要付出上下文切换的时间代价。以下图:编程
假设A和B两个任务都被分红10个大小相等的块,单核CPU交替的执行两个任务,每次执行其中一块,其花费的时间并非先完成A任务再玩成B任务所花费时间的两倍,而是要更多。这是由于系统从一个任务切换到另外一个任务须要执行一次上下文切换,这是须要时间的(图中的灰色块)。上下文切换须要操做系统为当前运行的任务保存CPU的状态和指令指针,算出要切换到哪一个任务,并为要切换的任务从新加载处理器状态。而后将新任务的指令和数据载入到缓存中。缓存
将应用程序分为多个独立的、单线程的进程,他们能够同时运行。进程内部实现原理比较复杂,这里就很少说了。安全
这些独立的进程能够经过常规的进程间通讯机制进行通讯,如管道、信号、消息队列、共享内存、存储映射I/O、信号量、套接字等。多线程
缺点:并发
优势:分布式
线程很像轻量级的进程,可是一个进程中的全部线程都共享相同的地址空间,线程间的大部分数据均可以共享。线程间的通讯通常都经过共享内存来实现。函数
优势:性能
缺点:测试
主要缘由有两个:任务拆分和提升性能。
在编写软件的时候,将相关的代码放在一块儿,将无关的代码分开,这是一个好主意,这样可以让程序更加容易理解和测试。将程序划分红不一样的任务,每一个线程执行一个任务或者多个任务,能够将整个程序的逻辑变得更加简单。
在两种状况下,并发可以提升性能。
C++98
标准中并无线程库的存在,而在C++11
中终于提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操做、原子操做等类。
多线程库对应的头文件是#include <thread>
,类名为std::thread
。
一个简单的串行程序以下:
#include <iostream> #include <thread> void function_1() { std::cout << "I'm function_1()" << std::endl; } int main() { function_1(); return 0; }
这是一个典型的单线程的单进程程序,任何程序都是一个进程,main()
函数就是其中的主线程,单个线程都是顺序执行。
将上面的程序改形成多线程程序其实很简单,让function_1()
函数在另外的线程中执行:
#include <iostream> #include <thread> void function_1() { std::cout << "I'm function_1()" << std::endl; } int main() { std::thread t1(function_1); // do other things t1.join(); return 0; }
分析:
std::thread
对象t1
,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完了,整个线程也就执行完了。start
的函数来显式的启动线程。std::thread
对象被销毁以前作出这个决定。这个例子中,对象t1
是栈上变量,在main
函数执行结束后就会被销毁,因此须要在main
函数结束以前作决定。t1.join()
,主线程会一直阻塞着,直到子线程完成,join()
函数的另外一个任务是回收该线程中使用的资源。线程对象和对象内部管理的线程的生命周期并不同,若是线程执行的快,可能内部的线程已经结束了,可是线程对象还活着,也有可能线程对象已经被析构了,内部的线程还在运行。
假设t1
线程是一个执行的很慢的线程,主线程并不想等待子线程结束就想结束整个任务,直接删掉t1.join()
是不行的,程序会被终止(析构t1
的时候会调用std::terminate
,程序会打印terminate called without an active exception
)。
与之对应,咱们能够调用t1.detach()
,从而将t1
线程放在后台运行,全部权和控制权被转交给C++
运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。参考UNIX
的守护进程(daemon process)的概念,这种被分离的线程被称为守护线程(daemon threads)。线程被分离以后,即便该线程对象被析构了,线程仍是可以在后台运行,只是因为对象被析构了,主线程不可以经过对象名与这个线程进行通讯。例如:
#include <iostream> #include <thread> void function_1() { //延时500ms 为了保证test()运行结束以后才打印 std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "I'm function_1()" << std::endl; } void test() { std::thread t1(function_1); t1.detach(); // t1.join(); std::cout << "test() finished" << std::endl; } int main() { test(); //让主线程晚于子线程结束 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s return 0; } // 使用 t1.detach()时 // test() finished // I'm function_1() // 使用 t1.join()时 // I'm function_1() // test() finished
分析:
500ms
的延时,因此在尚未打印的时候,test()
已经执行完成了,t1
已经被析构了,可是它负责的那个线程仍是可以运行,这就是detach()
的做用。main
函数中的1s
延时,会发现什么都没有打印,由于主线程执行的太快,整个程序已经结束了,那个后台线程被C++
运行时库回收了。t1.detach()
换成t1.join()
,test
函数会在t1
线程执行结束以后,才会执行结束。一旦一个线程被分离了,就不可以再被join
了。若是非要调用,程序就会崩溃,可使用joinable()
函数判断一个线程对象可否调用join()
。
void test() { std::thread t1(function_1); t1.detach(); if(t1.joinable()) t1.join(); assert(!t1.joinable()); }