多线程编程

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;
}

 

posted @ 2021-12-20 21:31  dsfsadfdgd  阅读(106)  评论(0)    收藏  举报