计算机中的乱序执行
1. 一定会按正常顺序执行的情况
- 对同一块内存进行访问,此时访问的顺序不会被编译器修改
- 新定义的变量的值依赖于之前定义的变量,此时两个变量定义的顺序不会被编译器修改
2. 其他情况计算机会进行乱序执行
单线程情况下允许乱序进行,多线程情况下会出现问题
3. C++中的库中提供了6种内存模式用于多线程情况下 防止编译器的乱序进行
(1) memory_order_relaxed : 最放松的
(2)memory_order_consume:
当客户使用,搭配release使用,被release进行赋值的变量y,获取的时候如果写成consume,那么所有与y有关的变量的赋值,一定会按顺序进行。
(3)memory_order_acquire: 用于获取资源
(4)memory_order_release:
一般用于生产者,当给一个变量y进行赋值的时候,只有自己将这个变量释放了,别人才可以去读,读的时候如果使用acquire来读,编译器会保证y之前被赋值的变量都在y之前被执行,相当于设置了内存屏障。
(5)memory_order_acq_rel
(6)memory_order_deq_cst
**好处:**不需要编译器设置内存屏障,morden c++开始就会有底层汇编的能力
副作用
1. 无副作用编程
存在一个函数,传一个参数x进去,里面进行一系列的运算,返回一个y。中间所有的过程都是在栈中进行修改的。
2. 有副作用编程
例如:在一个函数运行过程中,对全局变量进行了修改,或者在屏幕上输出了一些东西。此函数还有可能是类的成员方法,在此方法中如果对成员变量进行了修改,类的状态就会发生改变
3. 在多线程的情况下有副作用编程
在线程1运行的时候对成员变量进行了修改,此时如果再继续执行运行线程2,此时线程2拥有的就不是这个类的初始状态,而会受到线程1的影响。
信号量
1. binary_semaphore
定义:
可以当做事件来用,只有有信号和无信号两种状态,一次只能被一个线程所拥有
使用步骤:
(1)初始创建信号量,并且一开始将其置位成无信号状态
std::binary_semaphore sem(0)
(2)线程使用acquire()方法等待被唤醒
(3)主线程中使用release()方法,将信号量变成有信号状态。
2. counting_semaphore
定义:
一次可以被很多线程所持有,线程的数量由自己指定
使用步骤:
(1)创建信号量
指定一次可以进入的线程的最大数量,并在最开始将其置位成无信号状态:
std::binary_semaphore<8> sem(0);
(2)主线程中创建10个线程
并且这些线程全部调用acquire()方法等待被唤醒。但是主线程使用release(6)方法就只能随机启用6个线程。
future库
用于任务链 (即:任务A的执行必须依赖于任务B的返回值)
1. 例子:生产者消费者问题
(1)子线程作为消费者
参数是一个future,用这个future等待一个int型产品:std::future & fut
(2)子线程中使用get()方法,等待一个未来的future,返回一个result
(3)主线程作为生产者,做出一个承诺:std::promise prom
(4)用此承诺中的get_future()
方法获取一个future
(5)主线程中将子线程创建出来,并将刚刚获取到的future作为参数传入
(6)主线程做出一些列的生产工作,最后生产完成后使用承诺中的set_value()
方法,参数为刚刚生产出的产品
(7)此时产品就会被传入到子线程中,子线程就可以使用此产品做出一系列动作
(8)最后使用join()
方法等待子线程静止。但是join只适用于等待没有返回值的线程的情况
2. 如果线程有返回值
(1)使用 async
方法可以进行异步执行
**参数一:**可以选择是马上执行还是等一会执行 (即当消费者线程调用get()
方法时,才开始执行)
**参数二:**执行的内容(可以放一个函数对象和lambda表达式
)
(2)生产者使用saync
方法做生产工作,并返回一个future
(3)消费者使用future
中的get()
方法可以获取产品