那今天呢?我们来学习一些现成的基础知识,为了更好的理解这部分内容啊,我们需要了解一点计算机的组成原理,这张图呢,就是计算机组成原理的架构图。从图中我们可以知道啊,整个计算机系统被南桥芯片和北桥芯片分成了两大部分。其中,北桥芯片呢,用于连接CPU内存以及显示器,对吧?
南桥芯片呢,用于连接。网卡声卡,鼠标键盘等这一些设备。我们都知道,在计算机刚出现的时候CPU是整个计算机系统的瓶颈,它的处理速度呢特别慢。一开始的CPU都是单核单进程的,一次只能做一件事儿,对吧?因为当时计算机普及的并不广泛。处理速度又慢,所以使用的人都要先排队,等一个人用完了,第二个人呢,才能开始这种工作方式呢,让大家非常的烦恼。所以人们一直希望可以将CPU的速度给它提上来,让它处理的越来越快,这样呢,我们的工作效率就会更高。OK,那个时候呢,计算机处理一次任务通常要花几个小时,那一些大一点的任务可能要好几天才能完成。那这种工作效率是大家不能忍受的,所以大家一直在想如何将计算机的处理速度提高上来,这样呢,能使我们完成更多的任务。那后来的CPU速度就逐渐上来了,
每次只处理一项任务就显得太浪费了,对吧?(提高主频、流水线等级)
于是就有了操作系统,那操作系统呢,可以将单核的CPU模拟成多核。这样CPU可以同时处理多个任务,其实在底层它是通过时间分片的机制来实现的,也就是说操作系统每隔20毫秒做一次切换。(单核CPU底层还是单次只执行一条指令 )
每个任务呢,执行20毫秒以后就切到下一个任务,按照这种方式依次执行,这就是多进程。就像我们这张图展示的这样对吧,先开始的20毫秒让任务一来执行,再接下来的20毫秒呢,让任务二执行再20毫秒任务三执行,当任务三执行完成之后没有任务了。它再切回到任务,一再执行20毫秒,这样依次类推。
那到了近代之后呢,就出现了真正的多核CPU,就像我这张图展示了同一时刻呢,可以有多个任务同时在进行。这样的一个效率就比我们前面的单核多进程的,这种方式要更高效了。(多核CPU)
那实际上呢,线程与进程是类似的。只是线程更轻量了,在Linux系统下呢,
每个线程就是一个进程,它们的区别是线程之间呢,可以共享资源。而进程之间呢,是完全独立的,谁也不碍谁的事,对吧?了解了上面这些内容之后呢,我们再来看看多线程的好处与坏处,当然好处显而易见的,对吧?可以充分的利用CPU的资源。
坏处呢,就是管理这件事儿是特别麻烦的,实际上我们大家在生活中啊,都有感受,如果我们是一个人去做事儿的话。那就不需要怎么管理,对吧?但是如果我们组成一个团队,是一个三四个人的团队,相对还好管理一些,如果是几十人几百人的团队。那管理呢,就是一个非常大的成本。
对于多线程也是类似的,那当我们出现多线程之后,管理就变成了一个麻烦的事,尤其是我们在访问共享资源的时候。大家都同时争夺一个资源,
这个时候我们就要把这个管理呢给做好,如果做不好的话,就会出现各种各样的问题,比如说有的县城在往磁盘中写数据。那另外一个线程呢?在磁盘中读数据,那如果我们不控制好它们前后的逻辑关系的话,就有可能读到的数据是一些脏数据。对吧,那这都是我们经常遇到的事儿好,那下面呢,我们再来看看多线程给我们带来了哪些问题,
那第一个问题呢,就是原子性的问题,我们都知道,在我们应用层执行的每一条语句。实际在底层呢,都是由多条指令来完成的,当我们访问某个共享资源的时候。在每条指令之间,都有可能由另外一个线程的指令去操作,对吧?那这个时候就会产生一个冲突,比如说我们要将内存中的。某个变量的值加一对于我们上层语言来说呢,我们可能写成了是a=a+1对吧,但是它翻译成底下的指令来说呢,就是先获取a的值。然后再加一之后呢,再将这个值写入这个变量,
同时呢,另外一个线程也做同样的事情,那这个时候呢,本身它的结果应该是加二了。但是呢,最终我们看到的结果却是a+1对,这样就产生了一个冲突,这是原子性问题,(原子性) 下
第二个问题呢,就是有序性问题,也就说我们在访问一个共享资源的时候呢。要达到一个有序,比如我们的顺序应该是先执行a线程,再执行b线程,如果我们控制不好呢,就有可能先执行b线程。再执行线程,这是一个有序性问题,(有序性,先后)上
好第三个是可见性问题。由于我们使用了多核,在每一个核中呢,都有自己的缓冲。所以他们在操作的时候呢,只是在自己的缓冲区中做了操作,而没有把它更新到主内存中。(cache)而这个时候呢,另外一个CPU它更新了内存中的这个值,但是对于第一个CPU来说,由于它只访问了自己的缓冲区。所以没有观察到主内存的变化,就造成了主内存的变化,并没有显示在第一个CPU中,对吧?(可见性 cache)中
这是可见性的问题。那对于多线程来说呢,这三大类问题是我们最常见的好,那我们如何来解决这些问题呢?
实际就是通过锁,通过加锁的方式,我们可以解决掉上面所说的所有的问题。那我们先来看一下,我们通常都使用哪些锁?那第一类呢?是读写锁,这是我们最常用的,对吧?
所谓读写锁呢,就是读锁和写锁当一个线程,加了读锁之后,所有持有写锁的线程都不能对加锁的资源再进行访问了。这是第一个方面,
那第二方面呢?是当线程对资源加了写锁之后,所有持有读锁写锁的线程都不能再访问这个资源了。这是读心锁。
第二个呢,是自选锁。自选锁呢,就是让它自转不停的尝试对某个资源进行加锁,如果加不上,它就一直死循环的在这儿尝试。直到将锁加上,所以对于这类锁来说呢,我们访问的资源一定是时间很短的,瞬间就用完,然后释放掉。
这样才能保证我们的执行效率,否则的话,如果我们需要很长时间访问这个资源,这个时候我们使用自选锁就会大量的浪费CPU,这是绝对不允许的。
好,第三类呢?就是可重入锁。当我们的线程对某个资源持有锁之后,它还可以继续对这个资源加锁,对吧?反复的加锁,只要是我同一个线程。那对于其他线程来说呢,它就不能再访问这个资源了,这就是可重入锁。当然,重入锁一般我们用的也比较少。
自选锁呢,一般是在操作系统底层使用最多的呢,我们都用的是读写锁,了解了锁的类型之后呢。啊,我们再来看看如何通过锁来解决资源共享的问题。
那首先呢,就是互斥通过锁呢,可以达到一个互斥,当某个线程要访问共享资源的时候,他先给它加一把锁,这个时候呢,其他的线程就没法再访问这个共享资源了。这样达到一个互斥。第二个呢,就是同步当多个线程要访问同一个资源的时候,如果它们是有前后关系的,这时候我们要安排好它们的同步性。哪个第一个访问哪个第二个访问哪个第三个访问,这是实现同步,不过锁呢,
我们就可以实现互斥与同步,对吧?当然,我们加锁的时候呢,还要注意一些点,那第一个呢,就不能出现 什么时候会出现死锁呢,就是a线程等待b线程,但是a线程呢,和b线程都要同时访问一个共享的资源。这时候他们都需要对这个资源加锁,但是由于a线程已经对这个资源加了锁b线程呢。一直获得不到这个资源。这样就产生了一个死锁,对吧?
这是我们要避免的。好,第二个呢,是为了提高效率,我们应该把这个资源呀,尽量划分的更小,这样使得我们锁这个资源的时间就更短。它的效率就更高,
对吧?第三点就是我们使用多线程的时候。设计好线程的使用,尽量呢,让线程之间减少资源的共享,从而达到少使用锁的目的,对吧?
这样呢,可以更高的。提高程序的执行效率,对这就是我们加锁时候需要注意的几个点,那以上呢,就是我们这节课所要介绍的内容。那在这节课中呢,我首先向你介绍了多进程,多线程,它是由何而来的?多线程解决了什么问题?又带来了什么问题?我们又该如何解决这些问题?那这就是我们这节课所介绍内容,当然在这节课中呢,
我主要向你介绍的都是一些理论的知识,那在后边我们再看外边代码的时候呢,我们再详细的看。外拔tc是如何使用线程的?又是如何利用锁来解决共享资源的?那我们本节课呢?就到这里。有任何的问题呢,你可以到讨论区或者是QQ群里去给我留言,我在那里呢,给你做详细解答好,谢谢。