00:00
好,那么同学们,咱们继续。下面我们看指令重排的第二个案例,注意,这块知识非常难。同学们。非计算机系的了解即可。但是呢,不是因为难老师就故意跟你说啊,同学们算了,这三个特性,大家呢,了解其中的两个就行了。那么这个。先听老师讲完一遍,至少你知道有这么一个东西。然后。接下来有时间的同学,学有余力的同学可以再进去呢,进一步的深入,但是先跟着老师走,先把这些东西整明白,那么首先。第二个指令重排,注意。由于编译器存在指令重排。顺序和数据是不是会被打乱,但编译器认为这是一种优化。而刚好我们的程序又在多线程环境下面。那么这个时候会导致执行的顺序不一样,将会导致的数据的最终一致性没有办法保证。所以说。
01:03
编译器的指令重排,有时候我们需要用v tell禁止它重排,听懂了吗?哥们儿,你不用来自作聪明来给我做优化,你不要重排那么好,那么接下来我们再看一个案例。现在这个东东刚才讲过了,我们再说一个案例。请同学们看一下这个,我还是呢打开这个API。给同学们呢遮盖一下。注意。现在是不是一个重排的DEMO?好,那么同学们,当然这个很难演示哈,因为它有时候什么这个重不重排,不是你加个参数,那它就重排,不加个参数就不重排,我们是什么禁止指令重排,对吧,我们过来请看。Int a等于零,布尔型的flag等于false,两个变两个数字型和布尔型变量的声明,同学们没问题吧?
02:06
接下来请看在多线程环境下面,比方说。A线产线产操纵资源赖。是不是都是同一个资源类,这个资源类里面请看同学们那有几个方法。两个吧,可不可以A线程干嘛同时来访问了这个资源类,这个资源类我干什么呢?正常情况顺序执行。一二。现程序反问。一号方法线程二反问二号方法,同学们,这个OK吧?那么请看初始值是零和负线程一干了一件事。一。错,同学们没问题吧?过来。下面。请看。线程这个资源类里面的方法二干嘛呢?Flag如果为错。
03:02
A等于A加五,同学们。第一个方法执行完了,我们是不是实现了一个事情,就是A等于一。那么这个时候flag等于衣服戳进不进来。一加上五语句三,这个A就应该等于六第一步,同学们这个case能不能跟上?好。99.99999999%的情况下,都挺和谐挺自然的,但是由于你在多线程。有可能我们极端一点,他们2万个线程来访问你这个资源类,来调用你资源类的这个方法。这个时候,那么系统底层由于在多线程的环境下面,请同学们跟着我再读一遍这句话。多线程环注意单线程环境里面确保了最终执行顺序与代码时间顺序一致,就是说单线程环境你不用去关心指令重排,听懂了吗?再来什么环境,多线环境中线程干嘛交替执行?
04:04
由于编译器优化重排的存在,这个是客观存在的,就好比北京有雾霾,北京会堵车,你今天去面试可能会考虑迟到的情况。他有编译器优化指令并行的重排,系统内存系统的重排,三步的重排,你这个源码写的是1234啊,那么可能到最后最终执行的指令是什么,3521这个时候干嘛。两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。杨哥把这句话拷贝过来。请看这个案例,那么出现一个一种什么坑爹的情况呢?这也就悲剧了。干嘛呢?兄弟们,由于出现指令重排,在多线程环境下面,我们出现什么情况?回答,我语句一和语句二只是两个变量的声明,现在由于重排以后顺序不再是一二。
05:02
却变成了二一。听懂了吗?这个时候。Flag。戳。等于。这句话语句二。变成了第一行。Flag等于出了,另外的线程马上就抢到,Flag等于出了,进不进来,进来但抱歉,这个时候注意啊,这句话的意思是这样。兄弟们。默认情况是这样,但是呢,我们呢,出现指令重排了以后,有可能变成我们讲过干嘛。这两个变量是不是不存在数据依赖性,那么这个时候就会导致什么呢?在多线程环境下面,有可能flag等于错先被执行了。一先被执行,但是抱歉线程太快。还没有走到A等于一,那么这个时候将会导致什么情况呢?一服错进不进来,进来这个时候A等于一,这一段还没走到A,现在默认值是多少?零就变成零加五,A等于多少?
06:08
五。第一种情况是六,第二种情况是五,听懂了吗?那么这个时候我们要干什么?最狠的是不是在这变量前面干嘛?甲克白的太啊哥们。不要给我重排,给老子保证每一句话都是一和二,一定是A等于一先执行,再等于flag等于错先执行,禁止你指令重排,也就是说,把这种情况给我彻底干掉。那么这种情况下,兄弟们。一戳进来,A等于一,一加五等于六,最终只要是六,听懂了吗?那么这个就是我们的什么指令进同拍。那么好,那么下面。禁止指令重排的这个总结干嘛呢?牵扯到底层的编译原理和汇编。非计算机专业的同学不要求掌握这块知识,那么请同学们干嘛?就是听杨哥了解一遍就行了,那么非常的底层不要求大家知道了解就行了,你指定重排,你就把我的这个案例说出去就OK,听懂再读一遍。
07:19
多线程环境中,那么单线程你不用管,听懂了吗?由于线程的交替执行,比方说什么4万个线程。编辑优化重排的存在。两个线程中使用的变能否保证一致性是无法确定,你看两个线程中使用的变量能否保证一致的,那第一次A1FLAG出是这个顺序,第二次flag先出了,对不对?这个时候就会导致A还没有加完,别的线程进来了,拿到的值A是零不是一,一个结果是五,一个结果是六。导导致的一致性无法确定,结果无法预测,那么这样是不是不好?不好怎么办?就要用vla修饰变量来禁止指令重排,让它的可能性只有一种,听懂了吗?
08:04
那么接下来我们来看指令重排的案例小结。那么这块比较烧脑,那么同学们不要着急,读一遍就成了,你读得懂。最好,读不懂就算了,但是老师不能降低难度。巴拉泰尔实现了禁止指令重排的优化,也就是说什么编译器做的优化,你不要自作聪明,给我禁了。听到。从而避免多线程环境下面程序出现乱序执行的现象。来吧。我们先了解的概念叫memory barrier又称内存屏障,是一个CPU的实例。它的作用两个,第一个保证特特定操作的什么执行顺序,第二个保证某些变量的内存可见性。什么意思呢?就说白了,Vallatile就只有两个特性。保证可见性。禁止指令重排,那么你这个变量只要是被完了太修饰,是不是这两个特性同时具备,那么过来看。
09:07
由于编译器和处理器都能执行指令重排的优化。如果在指令接插入一条内存屏障,就会告诉CPU,我们不管指什么指令,都不能和这条内存屏障的指令进行重排,也就是说通过内存屏障禁止在内存屏障的什么前后的指令执行重排序优化听懂。那么内存屏障另外一个作用就是强制刷出各种CPU的缓存数据。因此CPU上面的线程都能读取到这些数据的最新版本。说白了就是什么。我加了,大家看可见性是不是获得保障,兄弟们,杨哥的视频重新改过了,给大家重新发一下,上一个版本作废,用这个版本,那么这种就叫可见,就叫及时通知,那么过来。我们对外修饰的变量要进行什么操作?写操作的时候,我们会在写操作的后面加入一条store的屏干指令,这个是CPU的指令啊,牵扯到底层的汇编和硬件,不懂的同学算了,因为你们是非算专业。
10:11
将公众内存中的共享变量重新刷回回主内存。那么言下之意。如果现在你是外laile写,那么呢,禁止上面的普通写和下面的外laile是干嘛写重排,就说你只要外LA写完了才行。防止上面的写和下面可能有外读写进行重排序。那么你看。带什么内存屏障的什么鬼?前和后,那么这个时候写是什么?Store store屏障,Store load屏障,你发,写完了咱们再说。第二个对外来跳变量进行什么操作?那么读操作或在读操作加入一条什么指令,Load读是不是load读取?Loading是读取的意思吗?从主内存中读取共享变量,那么外跳读的话将会在这儿。
11:01
加两个屏障,禁止下面的所有普通。独操作和上面的。独操作干嘛重排你看就是这个。下面所有的普通读和前面的完读干嘛重排序,就是你先给我把完tell这个变量,这个值读完了,你才允许下面的这么说,听懂了吗?言言下之意,这个就是什么阴阳两隔,给我分开加屏障,是不是加了一种阻塞?好,那么这个时候干嘛禁止下面的所有写操作和上面不要跳读重排序,那么通过这种情况加store屏障和load load屏障就是我们所说的内存屏障。告诉你干嘛?禁止在内存屏障的前后执行重排序优化。编一线,请你不要自作聪明。按照主人要求的顺序来,你不要自作聪明了,我知道你天生自带编译器优化,但是现在不要你优化,因为你会打,打乱我的什么顺序,出现乱序执行的现象,那么现在我要把你禁止,怎么禁?
12:07
在。关键的变量前面加修饰,就这个意思,那么所以说。最终我们要明白明白。GMM。可见原子和有序,为了保证有序,有时候需要禁止指令重排,那么最终我们的线程GMM的安全性数据不乱一致和安全获得保证,那怎么做到的呢?第一个。工作类群与主动存同步延迟现象导致的可见性问题,我们可以使用synchize和vela解决。我们都可以使一个线程修改后的变量立刻对其他线程可见及时通知啊,听到。都能解决。SYNCH更能解决,因为为什么我synchize就是什么只允许一个线程来修改,对不对,改完了才能用。
13:00
第二个,对于指令重排导致的可见性问题和有趣性问题,我们用外关键字解决,因为它的另外作用就是禁止指令重排优化。那么言下之意,可见性和什么重排,那么外laile首单其冲,这个就是我们外laile的什么禁止指令重排。
我来说两句