只有县城我们可以并发的在各个 cpu 核心上执行任务,最大化 cpu 的利用率,但县城也可能导致各种奇怪的资源竞争问题。 相信大家一定都看过这个经典案例,用不同的线程去更新同一段内存数据。比如这里,我们总共创建十个线程,每个线程累加这个数字一百万次运行程序,你会发现得到的结果并不是预期的一千万,实际显示的数字可能会比他小很多,并且每次输出的结果还都不一样。 这视频呢,我会向大家解释为什么会出现这种情况,以及各种常见的线程同步机制。我们先来回顾一下刚才的这段程序,其实问题都出在数字累加的这行代码上。 虽然我们在程序中简单的写作恩加加,但是当程序被翻译成机器代码时,恩加加其实被翻译成三条不同的机器指明,他们分别是将 内存中的数据加载到 cpu 的计算器 ex 中,然后将 ex 中的数据加一,最后再将计算结果写回内存。换句话说,我们这里登加加并不是原子操作,原子操作指的是不能被继续拆分或者被其他操作打断的指令。 这里顺带解释一下,计算器是 cpu 内部的小型存储器,用来临时存放计算数据。 cpu 中的运算都离不开计算器,他们的容量非常有限,但读写速度会比内存快很多。 回到刚才的代码,如果不同的线程按照顺序一字执行,比如线程 a, 先将数据五读入计算器,然后加一得到六并写回内存。使用线程 b, 再将数据六读入计算器, 加一得到七并写回内存,这样没有任何问题。但问题在于线程是并发执行的,可能线程 a 还未将累加后的数据写回内存, 县城币就已经开始读取数据到计算器,这样县城币会读到修改之前的旧数据,最后的结果是数据只被累加了一次。这个就是我们平时说的县城资源竞争而导致的数据不一致问题。 要解决这个问题,我们需要对线程进行同步,也就是让原线翼步的操作依次有序的执行。而锁是我们接下来要讲的第一种,也是最最基本的线程同步机制。它的概念非常简单,在同一时间,只有一个线程可以获得锁的拥有权, 此时其他的线程只能干等着,直到这个锁被持有者释放掉。锁的获取和释放有时候也被叫做上锁和解锁。在不同的语言或者操作系统中,通常会用到不同的锁的实现,比如 cr 加或者构装的 mudex 互制锁加法则允许使用 sinknix 关键字 来对某个函数对象或者代码块上做单根底层一切。我们甚至可以调用操作系统的 api 来实现锁的功能。其他的你可能听过的还包括自选锁、读写锁等等。 总而言之,锁的核心概念是非常简单的,我们只需要记住在访问共享资源之前上锁,并在结束之后解锁即可。修改变形程序可以看到这里输出了我们预期的结果,一千万。 虽然这是一个样例程序,但还是要强调一下,频繁进行枷锁会解锁的操作是非常低效的,这样会完全打破现成的并发执行。 其实我们完全可以在县城中创建一个临时变量做计算,然后再将最终的结果累加到全球的共享变量中,这样只有最后一个操作需要同步,而县城的主体依然能够并发的执行。另外,在使用锁的时候需要格外 小心,多个锁的欠套使用很可能导致现存的死锁现象。比如这有两个现存和两把锁,现存一,先获取锁一,然后再获取锁二。现存二刚好相反,先获取锁二,再获取锁一。 如果这两个县城同时运行,恰好县城一先获取了锁一,然后县城二获取了锁二,然后县城一继续执行。由于锁一被县城二占用,所以县城一会被阻塞。同时由于锁二被县城一占用,所以县城二也会被阻塞,于是县城一和县城二同时被阻塞,也就造成了县城的思索。 这里关键的问题在于上锁的顺序,如果我们让所有的线程都按照同样的顺序上锁,其实是可以避免这种情况的。不过实际程序可能远比这个复杂,每个线程会用到不同的锁,并且枷锁和解锁的操作分散在代码的各个角落。所以 一种做法是干脆就使用单个锁来保护所有的共享资源,并且仅仅在访问资源的时候再去上锁。虽然这么做会损失掉一部分现成的并发性,但好处在于承修的逻辑会更加清晰,更容易维护。 讲到这里,我们就顺带提一下部分语言支持的 atomic 语法修饰。这是一种不使用锁,但依然能够解决资源竞争的方法。比如像之前简单的加点操作,在机器内部会直接翻译成硬件支持的原子操作。 也就说指令已经是不可拆分的最小步骤,因此不需要同步,他的效率通常比使用锁更高。 另外,建立在锁制上,县城中还有其他更复杂、更高级的同步机制,比如信号量、条件变量等等。虽然他们也可以用来保护共享资源,但是更主要的用途是在县城中传递信号。比如使用条件变量,你可以让县 进入等待,直到某个条件成立后再继续执行。这个条件可能是网络资源被成功加载,或者某项数据准备完毕等等。而信号量则更加灵活一些,你可以先让所有的县城进行等待,但在同一时间内,只让特定数量的县城被唤醒。 给大家举个例子,比如车间工人需要对零件进行加工,零件有传送带一一送来。如果当前没有零件时,所有工人都需要进行等待。如果当前有三个零件被送达,那么就有三个工人可以进行加工,其他的工人则需要继续等待。 这里的工人就像是县城,而零件就像是他们正在等待的信号量。考虑到篇幅长度,我想在后期视频中再向大家详细解释信号量和条件变量的工作原理,因为他们很容易和锁混淆,但实际用途却完全不一样。
粉丝21.9万获赞105.9万


如果面试官问你现成间的同步方式有哪些?你会怎么回答?看完这个视频你就知道了。 先说一下县城同步解决了什么问题。在多县城的环境下,如果多个县城要同时访问共享资源,那么就可能导致数据不一致,程序行为异常等问题。 那县城同步主要是通过限制了对共享资源的访问,来确保一定程度上的有序执行,从而防止这些问题的发生。那么常见的县城同步的方式有哪些呢?今天告诉你三个方法你绝对够用, 分别是护赤锁、毒邪锁和信号量。首先来讲一下护赤锁,护赤锁是一种保证在同一时刻只有一个县城可以访问共享资源的同步方法。就好比你进入了商场的洗手间,我们会使用锁来确保同一时间只有 一个人可以进入,而在加法当中可以使用 secret nine s 关键字来实现不制作。在这个视频当中,我们声明了一个 lock 对象来作为锁。方法中使用 secret nine s 关键字加上 lock 对象来作为参数,这样就可以确保同一时刻只有一个线程可以进入这个大板块。 而当其他县城是不进入的时候呢,他们就会被阻塞,直到当前县城释放了这把锁。第二个是毒蝎锁, 读写所允许多个县城同时来进行读操作,但是在进行写操作的时候只允许一个县城,那就好比现在有一篇在线的文章,我们会允许多个读者来同时进行读操作,但是如果当你要发表这篇文章的时候,就只有一个人可以进行操作。 在这个例子当中,我们实力化的一个 re entrance read right log 对象,他们有两个锁,一个是 read log 读锁,另外一个是 red log 写作。那我们在方法当中可以通过使用读索来确保多个线权可以同时进行读操作,而使用写索来确保在进行写操作的时候,就只有一个线权可以访问共享资源。第三个方法是信号量, 信号量用于控制同时访问共享资源的信号数量。就像停车场里面的车位其实是有限的,那只有拿到车位的车辆才可以进去停车。那在这个例子当中,信号量就好比我们车位的计数器, 可以看一下。这个例子当中,我们创建了一个三个符信号量的对象,然后初始化它的数值是三。那么在方法当中,我们首先尝试来获取信号量,如果成功了,也就是信号量技术是大于零的,就可以访问共享资源。但是如果失败了,也就是说信号量的技术是小于等于零的,那么县城就 需要等待其他的县城来释放许可。在访问了共享资源之后,我们也会释放信号量来允许其他的县城继续获取许可。 现在你已经了解了县城间同步的概念和方法,那么我想考你道题。假设你现在需要实现一个县城安全的计数器, 要求这个计数器可以被多个线程并发访问和更新,同时又要保证计数器的正确性。那么请你使用刚刚我们所学会的线程头部的方法,来说一下你的设计。把你的想法留言在评论区,我们下个视频见。
