多线程编程
1. :单线程vs多线程
线程共享:堆,文件描述符,信号处理函数,全局变量。
线程独占:栈空间,寄存器。
2. 线程的几种退出方式
1. 子线程使用return退出,主线程中使用pthread_join回收线程。如果是detach线程由系统自动回收。join线程有主线程进行回收。
2.子线程使用pthread_exit主动退出,主线程中使用pthread_join接收pthread_exit的返回值,并回收线程。join是让主线程等待子线程结束。
3.主线程中调用pthread_cancel,然后调用pthread_join回收线程,被cancel的线程遇到取消点时会被终止,如果没有取消点(阻塞函数read write sem_wait pthread_cond_wait)子线程不会被取消。
4.进程终止,线程也会随之结束。
2. volatile
volatile:当使用该变量的值的时候,系统总是重新从内存读取数据,而不是从线程的寄存器中。用于多线程中对全局变量的读写问题。
当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:
volatile bool stop_flag = false;
3. promise/future
https://www.cnblogs.com/guxuanqing/p/11360572.html
std::future可以从异步任务中获取结果,一般与std::async配合使用,
std::async用于创建异步任务,实际上就是创建一个线程执行相应任务。
https://www.cnblogs.com/taiyang-li/p/5914167.html
4. 单核CPU是否需要考虑线程安全问题
需要,单核CPU不能保证调度的顺序性和任务的原子性。
5. 线程/进程的运行状态
1:就绪状态 当进程已分配到除CPU以外的所有必要的资源。
2:执行状态
3:阻塞状态 未获得资源时进入等待队列(阻塞状态),主动让出CPU资源,获得资源时进入就绪队列,等待CPU。阻塞状态下只能进入就绪状态。
(1) 就绪→执行
处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态。
(2) 执行→就绪
处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态。
(3) 执行→阻塞
正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。
(4) 阻塞→就绪
处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。
不存在从阻塞状态到执行状态的切换。sleep(0)线程不进入等待队列,直接进入就绪队列。
6.多核CPU和单核CPU下的多线程
并发/并行区别
并发是多个事件在同一时间段执行,而并行是多个事件在同一时间点执行
双核处理器并行执行(硬件并发)对比单核处理器并发执行(任务上下文切换)
CPU常见的调度方式有:FIFO,时间片调度,优先级调度。
系统资源分配的最小单位是进程,调度的最小单位是线程。进程的优势是资源隔离和跨机器部署;线程的优势是数据共享,劣势是一个线程down会相应其他线程/线程同步。
多核CPU下的多线程同时使用多个核心,由系统自动调度。对程序透明。
单核cpu + 计算密集型 = 多线程切换导致速度更慢。
单核CPU + IO密集型 = 采用多线程
多核CPU + IO/计算密集型 = 多线程
8.自增操作过程
自增操作是不具备原子性的,它包含取数据、+1、数据写回操作。先读的线程后写,后读的线程先写就会出现覆盖。
第一步,先将 count所在内存的值加载到寄存器;
第二步,将寄存器的值自增1;
第三步,将寄存器中的值写回内存。
9. 线程让出CPU
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
1、线程体中调用了 sleep 方法使线程进入睡眠状态,与yield不同的是,sleep休眠时间固定。
2、线程由于 IO 操作受到阻塞
3、更高优先级线程出现
4)在支持时间片的系统中,该线程的时间片用完
目前能让线程阻塞方法有:join() ,wait(), sleep(),read write
10. pthread_join与pthread_detach
pthread_join()即是子线程合入主线程,主线程阻塞,等待子线程结束,然后回收子线程资源。如果主线程提前结束,子线程也会被强制结束。应该避免主线程出现异常导致子线程没有完成任务。
pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收。
pthread_detach(pthread_self());//也可以在主线程内设置
pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
注意:pthread_detach分离后的线程不会阻塞主线程,主线程销毁后释放资源,有可能导致子线程使用已经被释放的变量,但是主线程终止后子线程可以继续运行。
11.生产者 / 消费者
# include<iostream> # include<thread> # include<vector> # include<mutex> # include<condition_variable> # include<queue> #include <ctime> #include <windows.h> using namespace std; # define PRODUCT_SIZE 5//生产者数量 # define CUSTOMER_SIZE 20//消费者数量 #define MAX_SIZE 10//最大产品数量 mutex mut;//互斥锁 condition_variable con;//条件变量 queue<int> que;//队列,模拟缓冲区 void Producter() { while (true) { Sleep(10); srand(int (time(NULL))); std::unique_lock <std::mutex> lck(mut); /mut用来保护que共享资源互斥访问 while (que.size() > MAX_SIZE)//当队列已满时等待,不再生产 { con.wait(lck); //阻塞该线程,同时释放锁,在收到其他线程的notify后会自动加锁。 } int data = rand();//随机产生数字代表商品的生产 que.push(data);//将数据推入队列代表商品加入缓冲区 con.notify_all();//唤醒con上所有等待的进程 } } void Customer() { while (true) { std::unique_lock <std::mutex> lck(mut); while (que.empty())//当队列为空时等待,不再消费 { con.wait(lck); //阻塞该线程,同时释放锁,在收到其他线程的notify后会自动加锁。 } que.pop(); con.notify_all();//唤醒所有等待的进程 } } int main() { vector<thread> threadPoll; //创建生产者和消费者 for (int i = 0; i < PRODUCT_SIZE; ++i) { threadPoll.push_back(thread(Producter)); } for (int i = 0; i < PRODUCT_SIZE + CUSTOMER_SIZE; ++i) { threadPoll.push_back(thread(Customer)); } for (int i = 0; i < PRODUCT_SIZE + CUSTOMER_SIZE; ++i) { threadPoll[i].join();//原始线程等到新线程执行完毕后再销毁 } return 0; }