多线程编程
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;
}

浙公网安备 33010602011771号