1 线程与进程
进程,拥有资源并且独立运行的基本单位;进程间通信用管道或socket。
线程,程序执行的最小单元;线程间共享资源 互斥量。状态:就绪、运行、阻塞。
C++11中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。
用到的头文件:
#include <atomic> // C++11 原子操作,限制并发程序对共享数据的使用,避免数据竞争
#include <thread> // 该头文件主要声明了std::thread类,另外std::this_thread命名空间也在该头文件中
#include <mutex> // C++11 互斥量Mutex。在多线程环境中,有多个线程竞争同一个公共资源,就很容易引发线程安全的问题
#include <condition_variable> // C++11 并发编程 主要包含了与条件变量相关的类和函数
#include <deque>//是双端队列(double-ended queue)是一种具有动态大小、可以在两端进行插入和删除操作的容器
编译时添加编译参数:
set(CMAKE_CXX_FLAGS -pthread)
message(STATUS "CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}")
2. 使用举例
一个简单的多线程:
#include <iostream>
#include <thread>
void myfun() {
std::cout << "I'm myfun()" << std::endl;
}
int main() {
std::thread thread1(myfun);
thread1.join();
return 0;
}
解释:
1 首先,构建一个std::thread对象thread1,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完了,整个线程也就执行完了。
2 线程创建成功后,就会立即启动,并没有一个类似start的函数来显式的启动线程。
3 一旦线程开始运行, 就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。
4 注意:只需要在std::thread对象被销毁之前做出这个决定。
这个例子中,对象thread1是栈上变量,在main函数执行结束后就会被销毁,所以需要在main函数结束之前做决定。
这个例子中选择了使用 thread1.join(),主线程会一直阻塞着,直到子线程完成,join()函数的另一个任务是回收该线程中使用的资源。
#include <iostream>
#include <thread>
void myfun() {
//延时500ms 为了保证test()运行结束之后才打印
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "I'm myfun()" << std::endl;
}
int main() {
std::thread thread1(myfun);
//thread1.join();
thread1.detach();
//让主线程晚于子线程结束
std::cout << "main() finished" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
return 0;
}
运行结果:
使用 thread1.detach()时
main() finished
I'm myfun()
使用 thread1.join()时
I'm myfun()
main() finished
分析:
由于线程入口函数内部有个500ms的延时,所以在还没有打印的时候,main()已经执行完成了,thread1已经被析构了,但是它负责的那个线程还是能够运行,这就是detach()的作用。
如果去掉main函数中的1s延时,会发现什么都没有打印,因为主线程执行的太快,整个程序已经结束了,那个后台线程被C++运行时库回收了。
如果将thread1.detach()换成thread1.join(),test函数会在thread1线程执行结束之后,才会执行结束。
2. 带参数异步并占位
例子:
#include <iostream>
#include <thread>
#include <mutex>
#include<future>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
double t1(const double a, const double b)
{
double c = a + b;
sleep(3);
return c;
}
int main()
{
double a = 2.3;
double b = 6.7;
future<double> fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位;
cout << "compute" << endl;
cout << "please wait......" << endl;
cout << "result:" << fu.get() << endl;//阻塞主线程,直至异步线程return
//future对象的get()方法只能调用一次
return 0;
}
解释:
async与future:
async是一个函数模板,用来启动一个异步任务,它返回一个future类模板对象,future对象起到了占位的作用,刚实例化的future是没有储存值的,但在调用future对象的get()成员函数时,主线程会被阻塞直到异步线程执行结束,并把返回结果传递给future,即通过FutureObject.get()获取函数返回值。
3. 互斥量使用
使用 lock unlock
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
static std::mutex g_lock;
void lock_unlock()
{
//上锁
g_lock.lock();
cout << "in id: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "out id: " << this_thread::get_id() << endl;
//解锁
g_lock.unlock();
}
void f_lock_guard()
{
//lock_guard在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁.
lock_guard<std::mutex> lock(g_lock);
cout << "in id: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "out id: " << this_thread::get_id() << endl;
}
int main()
{
std::thread t1(lock_unlock);
std::thread t2(lock_unlock);
std::thread t3(lock_unlock);
t1.join();
t2.join();
t3.join();
std::thread t4(f_lock_guard);
std::thread t5(f_lock_guard);
std::thread t6(f_lock_guard);
t4.join();
t5.join();
t6.join();
return 0;
}
通常不直接使用 mutex,lock_guard更加安全, 更加方便。
lock_guard简化了 lock/unlock 的写法, lock_guard在构造时自动锁定互斥量, 而在退出作用域时会析构自动解锁, 保证了上锁解锁的正确操作, 正是典型的 RAII 机制。下面是生产者和消费者例子
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
deque<int> q;
mutex mu;
condition_variable cond;
int c = 0;//缓冲区的产品个数
void producer() {
int data1;
while (1) {//通过外层循环,能保证生成用不停止
if(c < 3) {//限流
{
data1 = rand();
unique_lock<mutex> locker(mu);//锁 有延迟锁定、时间锁定、手动锁定和解锁等 比 lock_guard复杂 功能差不多
q.push_front(data1); //开头插入元素
cout << "save: " << data1 << endl;
cond.notify_one(); // 通知取
++c;
}
sleep(1);
}
}
}
void consumer() {
int data2;//data用来覆盖存放取的数据
while (1) {
{
unique_lock<mutex> locker(mu);
while(q.empty())
cond.wait(locker); //wati()阻塞前先会解锁,解锁后生产者才能获得锁来放产品到缓冲区;生产者notify后,将不再阻塞,且自动又获得了锁。
data2 = q.back();//取的第一步 获得最后一个元素
q.pop_back();//取的第二步 删除最后一个元素
cout << "get: " << data2<<endl;
--c;
}
sleep(2);
}
}
int main() {
thread t1(producer);
thread t2(consumer);
t1.join();
t2.join();
return 0;
}
解释:生产者先产生 data1 并压入队列 q , 发出通知; 消费者线程先检测队列非空, 再等待通知和解锁, 从q中取出(分成2步:back 和 pop)放到data2
参考 : C++ 知识点汇总
4.sleep()/usleep()/this_thread::yield()/this_thread::sleep_for()作用
<1>.sleep()作用
功能: 将整个进程都休眠的
<2>.usleep()作用
功能: 将某个线程休眠
<3>.this_thread::yield()作用
功能: 线程调用该方法时,主动让出CPU,并且不参与CPU的本次调度,从而让其他线程有机会运行。在后续的调度周期里再参与CPU调度。这是主动放弃CPU的方法接口。
<4>.this_thread::sleep_for(chrono::nanoseconds(1))作用
功能: 线程调用该方法时,同样会让出CPU,并且休眠一段时间,从而让其他线程有机会运行。等到休眠结束时,才参与CPU调度。这也是主动放弃CPU的方法。
this_thread::yield()方法让出CPU的时间是不确定的,并且以CPU调度时间片为单位,yield()的实现依赖于操作系统CPU调度策略,在不同的操作系统或者同一个操作系统的不同调度策略下,表现也可能是不同的。
而sleep_for()让出CPU的时间是固定的。
示例
<1>.不添加延时函数
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(i < 1000)
i++;
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
int main(){
int i = 0, j = 0;
//1.任务线程.
thread t1(task_01, i);
t1.detach();//线程与进程分离,各自执行,顺序是混换的.
//t1.join();//主进程等待线程执行结束,然后主进程开始执行.
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
//this_thread::sleep_for(chrono::nanoseconds(1));
//this_thread::yield;
//sleep(1);
return 0;
}
结果:
root@LoongOS:test# ./test_thread
xxx------->main(), line = 70
<2>.使用this_thread::sleep_for(chrono::nanoseconds(1))延时
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(i < 1000)
i++;
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
int main(){
int i = 0, j = 0;
//1.任务线程.
thread t1(task_01, i);
t1.detach();//线程与进程分离,各自执行,顺序是混换的.
//t1.join();//主进程等待线程执行结束,然后主进程开始执行.
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
this_thread::sleep_for(chrono::nanoseconds(1));
//this_thread::yield;
//sleep(1);
return 0;
}
结果 :
root@LoongOS:test# ./test_thread
xxx------->main(), line = 70
xxx------->task_01(), line = 57
<3>.使用this_thread::yield()延时
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(i < 1000)
i++;
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
int main(){
int i = 0, j = 0;
//1.任务线程.
thread t1(task_01, i);
t1.detach();//线程与进程分离,各自执行,顺序是混换的.
//t1.join();//主进程等待线程执行结束,然后主进程开始执行.
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
//this_thread::sleep_for(chrono::nanoseconds(1));
this_thread::yield();
//sleep(1);
return 0;
}
结果:
root@LoongOS:test# ./test_thread
xxx------->main(), line = 70
<4>.使用usleep延时
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(i < 1000)
i++;
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
int main(){
int i = 0, j = 0;
//1.任务线程.
thread t1(task_01, i);
t1.detach();//线程与进程分离,各自执行,顺序是混换的.
//t1.join();//主进程等待线程执行结束,然后主进程开始执行.
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
//this_thread::sleep_for(chrono::nanoseconds(1));
//this_thread::yield();
//sleep(1);
usleep(100);//100us
return 0;
}
结果:
root@LoongOS:test# ./test_thread
xxx------->main(), line = 70
xxx------->task_01(), line = 57
<5>.使用sleep延时
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(i < 1000)
i++;
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
int main(){
int i = 0, j = 0;
//1.任务线程.
thread t1(task_01, i);
t1.detach();//线程与进程分离,各自执行,顺序是混换的.
//t1.join();//主进程等待线程执行结束,然后主进程开始执行.
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
//this_thread::sleep_for(chrono::nanoseconds(1));
//this_thread::yield();
sleep(1);
//usleep(100);
return 0;
}
结果:
root@LoongOS:test# ./test_thread
xxx------->main(), line = 70
xxx------->task_01(), line = 57
xxx------->task_01(), line = 60, i = 1000
void task_01(int i){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
if(i < 1000){
i++;
this_thread::sleep_for(chrono::nanoseconds(1));
}
printf("xxx------->%s(), line = %d, i = %d\n",__FUNCTION__,__LINE__,i);
}
void main_thread(int j){
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
while(j < 1000){
//this_thread::yield();
j++;
}
printf("xxx------->%s(), line = %d, j = %d\n",__FUNCTION__,__LINE__,j);
}
int main(){
int i = 0, j = 0;
thread t1(task_01, i);
main_thread(j);
t1.detach();
//t1.join();
usleep(100);
printf("xxx------->%s(), line = %d\n",__FUNCTION__,__LINE__);
return 0;
}
结果:
root@LoongOS:test# ./test_thread
xxx------->main_thread(), line = 90
xxx------->main_thread(), line = 95, j = 1000
xxx------->task_01(), line = 80
xxx------->main(), line = 105
总结
1.this_thread::sleep_for(chrono::nanoseconds(1))和this_thread::yield()是cpu自行调度,带有不确定性,跟系统有关.
2.sleep(1)和usleep(100)确实延时了,等待线程执行.