C++并发与多线程(1)——创建线程
零、注意事项
- 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;当主线程从main()函数返回,则整个进程执行完毕。
- 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行,一旦这个函数运行完毕,线程也结束运行。
- 整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了,此时如果其他子线程还没有执行完,也会被强行终止(此条有例外,以后会解释)。
一、创建线程的一般方法
首先要引入头文件#include,C++11中管理线程的函数和类在该头文件中声明,其中包括std::thread类。
创建线程的格式是std::thread mythread(function);,意思是创建了一个名为mythread的线程,并且线程mythread开始执行。其中可调用对象名(即括号中的function)作为第一个参数,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
需要注意,线程类参数是一个可调用对象,C++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象。
当线程启动后,一定要在thread对象销毁前,对线程运用join()或者detach()方法。这是两种线程阻塞的方法,两者的区别在于是否等待子线程执行结束:
- join():主线程等待子线程运行完毕,子线程运行完后,主线程才可继续运行。
- detach():主线程与子线程分离,即主线程不必等待子线程运行完毕,子线程也不必理会主线程是否运行完毕。即使主线程运行完毕,子线程也可在后台继续运行。
以下程序展示了子线程的创建及执行过程:
1 |
|
程序输出的结果是可预料的:先是15个test(0-14),然后是10个main(0-9)。从程序中我们已经看到,join()方法其实更像有等待的意思,而不是加入的意思。如果我们忘记使用join()方法,编译虽然不会报错,但是执行过程中会报错,从而导致程序中止运行(abort)。
除此之外还有另外一种线程阻塞的方法:detach()方法,它有分离的意思,主线程不再和与子线程汇合,不再等待子线程。此时子线程和主线程失去关联,驻留在后台,由C++运行时库接管。
对于上面的程序,注意以下三个问题:
-
如果我们把上面程序中的
mytobj.join();改为mytobj.detach();,那么输出结果将会是不可预料的,因为你无法预知CPU在哪个时刻切换到哪个线程执行,因此很可能会出现输出内容打印不全的问题。因此,我们初学时一般不用detach,它违背了传统多线程程序的规律。 -
如果主线程比子线程先结束,那子线程剩余未输出的内容将不会显示在控制台窗口里,因为控制台窗口受主线程控制,主线程结束后控制台的输出也就结束了。所以,每次输出结果都会不一样。
-
在创建线程时,如果
thread类传入的参数含有引用或指针,则子线程中的数据依赖于主线程中的内存,主线程结束后会释放掉自身的内存空间,则子线程会出现错误。例如以下程序:
1 |
|
在VS2019环境中可以编译通过,但是运行时会发生错误。初步原因应该是main线程结束后释放了内存空间,使得print线程无法引用main线程的内存。解决方法:(1)去掉&后恢复正常;(2)将语句thread mythread(print, a);修改为thread mythread(print,ref(a));。关于ref函数的问题我以后再去研究研究。
二、判断线程是否可以join或detach
上面我们说了,detach()方法使用后,子线程与主线程独立运行,互相不受影响。join()和detach()两者中只能调用其中一个且只能调用一次,如果子线程分离(detach)后,再想调用join,是不行的;相反地,如果子线程join了以后再想detach也是不行的,这两种情况程序运行的会报错。幸好C++11为我们提供了一种判断方法。
thread类提供了joinable()方法,用以判断某个线程是否可以join或detach。若可以使用其中一个,则返回true;若都不可以使用,则返回false。
下面为一个例子:
1 |
|
程序的输出结果会有join fail。
三、创建线程的其他方法
1.类创建线程
使用该方法之前,首先需要重载括号运算符,否则在创建线程时圆括号不能被编译器解释。下面是一个例子:
1 |
|
容易发现,主线程中的对象和子线程中的对象的地址是不一样的。
2.lambda(匿名函数)表达式创建线程
下面是一个例子:
1 | auto lambdaThread = [] { |



