0 00:00:00,000 --> 00:00:07,080 1 00:00:07,120 --> 00:00:08,760 那接下来我们讲一下 2 00:00:08,800 --> 00:00:12,680 管程和条件变量的设计与实现 3 00:00:12,720 --> 00:00:15,320 首先我们再回顾一下管程的原理 4 00:00:15,360 --> 00:00:16,720 管程的定义是什么呢 5 00:00:16,760 --> 00:00:18,320 管程是一个特殊的结构 6 00:00:18,360 --> 00:00:20,000 在这里面包含了几部分内容 7 00:00:20,040 --> 00:00:22,680 第一部分是共享变量shared data 8 00:00:22,720 --> 00:00:24,240 这是需要互斥访问的 9 00:00:24,280 --> 00:00:26,320 10 00:00:26,360 --> 00:00:28,560 第二部分呢是条件变量 11 00:00:28,600 --> 00:00:31,120 那是说当在里面某些操作中 12 00:00:31,160 --> 00:00:33,480 有可能某些条件满足或者不满足 13 00:00:33,520 --> 00:00:35,520 它会做相应一个等待 14 00:00:35,560 --> 00:00:37,080 或者唤醒的一个操作 15 00:00:37,120 --> 00:00:39,280 16 00:00:39,320 --> 00:00:41,480 第三个呢是并发执行的进程 17 00:00:41,520 --> 00:00:43,400 在这里面有一系列操作呢 18 00:00:43,440 --> 00:00:45,080 它需要互斥去完成 19 00:00:45,120 --> 00:00:47,160 然后有一些进程可以进来 20 00:00:47,200 --> 00:00:49,240 来完成相应的一些操作 21 00:00:49,280 --> 00:00:51,400 而这些操作可以并发来执行 22 00:00:51,440 --> 00:00:52,440 但需要保证 23 00:00:52,480 --> 00:00:54,720 对共享变量的访问是互斥的 24 00:00:54,760 --> 00:00:57,240 25 00:00:57,280 --> 00:01:01,200 第四个在管程做相应的启动之前呢 26 00:01:01,240 --> 00:01:03,080 它需要有一系列初始化过程 27 00:01:03,120 --> 00:01:05,160 这有一些共享变量初始化值 28 00:01:05,200 --> 00:01:06,200 以及对条件变量 29 00:01:06,240 --> 00:01:08,440 也有一定的初始化的一些要求 30 00:01:08,480 --> 00:01:11,000 31 00:01:11,040 --> 00:01:13,560 那我们既然讲了大致原理之后呢 32 00:01:13,600 --> 00:01:15,960 我们看看怎么来定义这个管程 33 00:01:16,000 --> 00:01:17,520 那么管程其实主要用在 34 00:01:17,560 --> 00:01:18,880 一些高级语言里面 35 00:01:18,920 --> 00:01:20,840 会有管程的一个设计与实现 36 00:01:20,880 --> 00:01:22,080 包括对它的使用 37 00:01:22,120 --> 00:01:22,920 在操作系统里面 38 00:01:22,960 --> 00:01:24,400 这上面的使用稍微少一些 39 00:01:24,440 --> 00:01:25,960 我们看看在ucore lab里面 40 00:01:26,000 --> 00:01:28,720 怎么来完成这个管程的设计与实现 41 00:01:28,760 --> 00:01:30,520 首先我们对它要定义一个数据结构 42 00:01:30,560 --> 00:01:33,360 这是一个管程数据结构的定义 43 00:01:33,400 --> 00:01:34,880 那它有一个mutex 44 00:01:34,920 --> 00:01:37,720 来保证对这些操作互斥的执行 45 00:01:37,760 --> 00:01:38,960 需要注意这些操作里面 46 00:01:39,000 --> 00:01:40,920 是包含了对共享变量的一些访问 47 00:01:40,960 --> 00:01:41,960 所以需要互斥 48 00:01:42,000 --> 00:01:42,800 49 00:01:42,840 --> 00:01:45,560 第二个呢有一个next和next_count 50 00:01:45,600 --> 00:01:46,800 next是一个semaphore(信号量) 51 00:01:46,840 --> 00:01:48,240 next_count是一个int(整型) 52 00:01:48,280 --> 00:01:50,280 这一块的具体解释 53 00:01:50,320 --> 00:01:52,080 我们在后面随着进一步的展开 54 00:01:52,120 --> 00:01:54,640 会给大家做进一步的讲解 55 00:01:54,680 --> 00:01:57,000 第三个cv就是条件变量 56 00:01:57,040 --> 00:01:59,520 对某些条件是否满足的 57 00:01:59,560 --> 00:02:00,640 所需要条件变量 58 00:02:00,680 --> 00:02:02,680 它类比我们这个信号量 59 00:02:02,720 --> 00:02:04,800 但和信号量的具体实现是有不同的 60 00:02:04,840 --> 00:02:07,760 这就构成了一个所谓的管程 61 00:02:07,800 --> 00:02:09,440 62 00:02:09,480 --> 00:02:11,320 好 讲了数据结构之后 63 00:02:11,360 --> 00:02:13,080 我们看它大致的实现 64 00:02:13,120 --> 00:02:14,520 这个管程里面各个操作呢 65 00:02:14,560 --> 00:02:16,360 是以函数形式存在的 66 00:02:16,400 --> 00:02:18,360 function_in_monitor 对应这里面 67 00:02:18,400 --> 00:02:19,840 一个一个的操作 68 00:02:19,880 --> 00:02:21,120 好在这个操作里面 69 00:02:21,160 --> 00:02:21,840 我们可以看到 70 00:02:21,880 --> 00:02:24,080 首先在操作开始和结束 71 00:02:24,120 --> 00:02:28,360 它有一个sem.wait 72 00:02:28,400 --> 00:02:29,880 和sem_signal 73 00:02:29,920 --> 00:02:31,240 类似于信号量的P操作 74 00:02:31,280 --> 00:02:33,040 和信号量的V操作 75 00:02:33,080 --> 00:02:35,320 从而可以确保中间的一些 76 00:02:35,360 --> 00:02:37,200 对共享变量的访问 77 00:02:37,240 --> 00:02:39,440 是一种互斥的操作 78 00:02:39,480 --> 00:02:42,320 在这里面如果对某一个条件的判断 79 00:02:42,360 --> 00:02:44,040 发觉条件能够满足 80 00:02:44,080 --> 00:02:46,240 我们就会做cond_signal 81 00:02:46,280 --> 00:02:48,240 或者做进一步的操作 82 00:02:48,280 --> 00:02:49,360 如果条件不能满足 83 00:02:49,400 --> 00:02:50,880 那我们就需要做cond_wait 84 00:02:50,920 --> 00:02:52,480 来完成一个等待 85 00:02:52,520 --> 00:02:54,960 这个是用到了条件变量 86 00:02:55,000 --> 00:02:57,320 这是通常位于管程中的一个操作 87 00:02:57,360 --> 00:02:59,600 所要完成的一个流程 88 00:02:59,640 --> 00:03:01,560 89 00:03:01,600 --> 00:03:03,960 另外呢我们也可以看到 90 00:03:04,000 --> 00:03:06,960 在管程里面有两个特殊的信号量 91 00:03:07,000 --> 00:03:09,160 和跟信号量相匹配的一个 92 00:03:09,200 --> 00:03:10,800 next_count这么一个记录 93 00:03:10,840 --> 00:03:13,320 这个记录和这里面的操作 94 00:03:13,360 --> 00:03:14,480 是有对应关系的 95 00:03:14,520 --> 00:03:15,480 它的具体含义 96 00:03:15,520 --> 00:03:17,000 我们会在后面进一步展开讲解 97 00:03:17,040 --> 00:03:18,440 这里面就不展开了 98 00:03:18,480 --> 00:03:20,760 99 00:03:20,800 --> 00:03:23,880 好 那我们来看一下 100 00:03:23,920 --> 00:03:25,720 关于条件变量的一个定义 101 00:03:25,760 --> 00:03:28,040 那我们知道条件变量和管程 102 00:03:28,080 --> 00:03:29,160 是有一个紧密联系的 103 00:03:29,200 --> 00:03:30,400 应该说条件变量 104 00:03:30,440 --> 00:03:32,640 是管程重要的组成部分 105 00:03:32,680 --> 00:03:34,000 根据我们原理课的介绍呢 106 00:03:34,040 --> 00:03:35,480 我们可以知道条件变量 107 00:03:35,520 --> 00:03:37,520 这个是它的一个数据结构定义 108 00:03:37,560 --> 00:03:39,800 包含了等待在条件变量上的 109 00:03:39,840 --> 00:03:41,080 进程的个数 110 00:03:41,120 --> 00:03:42,320 第二是等待队列 111 00:03:42,360 --> 00:03:44,040 这是它的一个数据结构 112 00:03:44,080 --> 00:03:45,640 同时它也有一个wait操作 113 00:03:45,680 --> 00:03:47,160 和一个signal操作 114 00:03:47,200 --> 00:03:48,400 wait操作是什么意思呢 115 00:03:48,440 --> 00:03:50,800 就说当一个条件得不到满足之后 116 00:03:50,840 --> 00:03:53,120 我们会让当前线程睡眠 117 00:03:53,160 --> 00:03:53,840 这个所谓睡眠 118 00:03:53,880 --> 00:03:56,040 让它执行一个condition: :wait(条件变量等待) 119 00:03:56,080 --> 00:03:57,600 如果说这个条件得到满足 120 00:03:57,640 --> 00:03:59,520 那么另一个进程或者另一个线程 121 00:03:59,560 --> 00:04:01,000 它会执行一个condition: :signal(条件变量唤醒) 122 00:04:01,040 --> 00:04:03,840 来把这个睡眠的线程给唤醒 123 00:04:03,880 --> 00:04:06,080 这就是它大致一个执行过程 124 00:04:06,120 --> 00:04:08,160 看起来和我们前面讲的 125 00:04:08,200 --> 00:04:09,360 信号量呢很类似 126 00:04:09,400 --> 00:04:10,640 但其实它们在具体实现上 127 00:04:10,680 --> 00:04:11,840 是有比较大的不同 128 00:04:11,880 --> 00:04:14,840 129 00:04:14,880 --> 00:04:17,920 那我们看看原理上这个条件变量 130 00:04:17,960 --> 00:04:20,400 和我们具体的实现之间 131 00:04:20,440 --> 00:04:22,040 是什么样对应关系 132 00:04:22,080 --> 00:04:23,560 可以看出来 133 00:04:23,600 --> 00:04:25,400 这个等待的这个线程个数呢 134 00:04:25,440 --> 00:04:26,280 这里面有个count 135 00:04:26,320 --> 00:04:28,840 和这个numWaiting是对应的 136 00:04:28,880 --> 00:04:30,360 这个等待队列需要注意 137 00:04:30,400 --> 00:04:32,080 在这里面具体的实验上面呢 138 00:04:32,120 --> 00:04:33,120 用的是一个信号量 139 00:04:33,160 --> 00:04:35,320 来代替了这个等待队列 140 00:04:35,360 --> 00:04:36,440 因为大家都知道 141 00:04:36,480 --> 00:04:37,880 前面讲的信号量实验中呢 142 00:04:37,920 --> 00:04:39,320 也用到了等待队列 143 00:04:39,360 --> 00:04:40,440 通过这个方式呢 144 00:04:40,480 --> 00:04:42,400 可以更加灵活来完成 145 00:04:42,440 --> 00:04:44,920 对线程的唤醒 146 00:04:44,960 --> 00:04:48,440 和让它睡眠这么一个操作 147 00:04:48,480 --> 00:04:51,880 另外条件变量和管程之间的关系 148 00:04:51,920 --> 00:04:53,280 通过另外一个monitor 149 00:04:53,320 --> 00:04:55,920 这么一个变量来建立好了 150 00:04:55,960 --> 00:04:57,920 这个条件变量是属于这个管程的 151 00:04:57,960 --> 00:04:59,080 这也是我们说 152 00:04:59,120 --> 00:05:00,600 条件变量是管程重要组成部分 153 00:05:00,640 --> 00:05:01,680 的一个体现 154 00:05:01,720 --> 00:05:04,000 这就是我们说关于条件变量的定义 155 00:05:04,040 --> 00:05:04,920 大致就这么多了 156 00:05:04,960 --> 00:05:08,120 157 00:05:08,160 --> 00:05:10,080 第二个是关于它的两个操作 158 00:05:10,120 --> 00:05:11,040 一个是wait操作 159 00:05:11,080 --> 00:05:12,960 一个是signal操作 160 00:05:13,000 --> 00:05:14,000 对于wait操作而言 161 00:05:14,040 --> 00:05:16,440 我们可以看到它是它的原理部分 162 00:05:16,480 --> 00:05:18,240 它其实对应红色部分呢 163 00:05:18,280 --> 00:05:19,840 就是它的具体实现 164 00:05:19,880 --> 00:05:22,240 比如它对它的numWaiting ++ 165 00:05:22,280 --> 00:05:25,000 我们cv.count++是和它对应的 166 00:05:25,040 --> 00:05:27,120 另外呢它会有一个 167 00:05:27,160 --> 00:05:29,400 信号量的signal机制 168 00:05:29,440 --> 00:05:31,920 来完成互斥一个通知 169 00:05:31,960 --> 00:05:35,000 与release(lock)是对应的 170 00:05:35,040 --> 00:05:38,840 最后呢sem_wait和schedule是对应的 171 00:05:38,880 --> 00:05:39,840 可以看出来 172 00:05:39,880 --> 00:05:41,800 对原理部分一些描述呢 173 00:05:41,840 --> 00:05:44,120 基本上在我们具体实验中有涉及 174 00:05:44,160 --> 00:05:47,080 但是需要注意还有很多地方 175 00:05:47,120 --> 00:05:48,480 是没有涉及的 176 00:05:48,520 --> 00:05:53,200 那这个需要结合我们signal一起来看 177 00:05:53,240 --> 00:05:57,160 比如说前面讲到numWaiting--这个操作 178 00:05:57,200 --> 00:05:59,560 其实在前面的waiting这个地方 179 00:05:59,600 --> 00:06:01,400 做了一个相应的实现 180 00:06:01,440 --> 00:06:02,960 而对于其它部分 181 00:06:03,000 --> 00:06:04,240 比如说要判断 182 00:06:04,280 --> 00:06:05,600 numWaiting是否大于0 183 00:06:05,640 --> 00:06:06,880 这里面是判断了什么 184 00:06:06,920 --> 00:06:08,840 cv.count是否大于0 185 00:06:08,880 --> 00:06:12,560 这里面wakeup唤醒操作呢 186 00:06:12,600 --> 00:06:13,440 在我们这里面是 187 00:06:13,480 --> 00:06:14,960 信号量一个signal操作 188 00:06:15,000 --> 00:06:17,000 起到一个唤醒一个作用 189 00:06:17,040 --> 00:06:19,520 但是也一样可以看到 190 00:06:19,560 --> 00:06:21,200 非红色部分呢 191 00:06:21,240 --> 00:06:22,440 这些操作在这里面 192 00:06:22,480 --> 00:06:24,000 没有直接的对应关系 193 00:06:24,040 --> 00:06:24,960 那这意味着什么呢 194 00:06:25,000 --> 00:06:26,920 其实这说明了我们原理 195 00:06:26,960 --> 00:06:28,280 和我们具体实现 196 00:06:28,320 --> 00:06:30,400 还是有比较大一个区别 197 00:06:30,440 --> 00:06:32,480 这个区别呢我们在后面具体实验中 198 00:06:32,520 --> 00:06:34,240 给大家逐一展开讲解 199 00:06:34,280 --> 00:06:36,960 200 00:06:37,000 --> 00:06:40,360 那我们以两个线程来做一个表述 201 00:06:40,400 --> 00:06:41,600 假设我们存在两个线程 202 00:06:41,640 --> 00:06:43,080 都想对一个管程 203 00:06:43,120 --> 00:06:44,400 进行一个相应的操作 204 00:06:44,440 --> 00:06:47,240 首先线程A先进入管程 205 00:06:47,280 --> 00:06:48,520 然后在进入之后呢 206 00:06:48,560 --> 00:06:50,960 通过对共享变量的访问 207 00:06:51,000 --> 00:06:52,600 发现某个条件不满足 208 00:06:52,640 --> 00:06:54,480 那么它会执行一个条件等待 209 00:06:54,520 --> 00:06:56,120 我们说条件变量的等待 210 00:06:56,160 --> 00:06:58,360 这是线程A的一个大致工作 211 00:06:58,400 --> 00:07:01,280 那么线程B呢它后进入管程 212 00:07:01,320 --> 00:07:02,720 因为前面等待之后呢 213 00:07:02,760 --> 00:07:03,960 很显然另一个进程 214 00:07:04,000 --> 00:07:04,720 它有机会可以进入到 215 00:07:04,760 --> 00:07:06,320 管程里面去执行 216 00:07:06,360 --> 00:07:07,240 进入管程之后 217 00:07:07,280 --> 00:07:08,840 它会设置条件满足 218 00:07:08,880 --> 00:07:10,400 因为它会查询 219 00:07:10,440 --> 00:07:12,480 或者修改共享的变量 220 00:07:12,520 --> 00:07:14,680 然后把条件设成满足 221 00:07:14,720 --> 00:07:15,760 设成满足之后 222 00:07:15,800 --> 00:07:18,000 它就需要把线程A给唤醒 223 00:07:18,040 --> 00:07:20,320 这是线程A和线程B 224 00:07:20,360 --> 00:07:21,600 它大致的执行过程 225 00:07:21,640 --> 00:07:23,520 在这个过程中我们来体会一下 226 00:07:23,560 --> 00:07:26,800 想看一下条件变量是如何有效地 227 00:07:26,840 --> 00:07:29,680 来让进程A和进程B 228 00:07:29,720 --> 00:07:32,040 既互斥 又能够同步地 229 00:07:32,080 --> 00:07:33,960 完成它们各自的工作 230 00:07:34,000 --> 00:07:36,360 这里面可以看出来我们设置是 231 00:07:36,400 --> 00:07:37,920 线程A是在这儿 线程B在这儿 232 00:07:37,960 --> 00:07:40,080 线程A先执行 线程B后执行 233 00:07:40,120 --> 00:07:42,960 它们分别进入到管程里面去 234 00:07:43,000 --> 00:07:47,440 完成各自工作一个过程 235 00:07:47,480 --> 00:07:49,600 这是线程A在管程中 236 00:07:49,640 --> 00:07:51,800 大致执行过程的一个描述 237 00:07:51,840 --> 00:07:54,720 它首先会有一个互斥的一个操作 238 00:07:54,760 --> 00:07:56,880 比如说信号量的mutex 239 00:07:56,920 --> 00:07:59,440 以及对它一个释放操作 240 00:07:59,480 --> 00:08:01,080 这是wait 这是释放 241 00:08:01,120 --> 00:08:02,720 这是保证了中间的 242 00:08:02,760 --> 00:08:05,120 处理过程是互斥的 243 00:08:05,160 --> 00:08:06,000 然后呢它会完成 244 00:08:06,040 --> 00:08:07,720 对共享变量的一些访问 245 00:08:07,760 --> 00:08:09,520 然后判断条件是否满足 246 00:08:09,560 --> 00:08:11,000 如果条件不满足 247 00:08:11,040 --> 00:08:12,920 它会做cond_wait 248 00:08:12,960 --> 00:08:14,960 就是条件变量的等待 249 00:08:15,000 --> 00:08:18,240 这就是关于线程A的一个操作 250 00:08:18,280 --> 00:08:20,040 如果等待满足了它会被唤醒 251 00:08:20,080 --> 00:08:24,520 然后它会退出整个操作执行过程 252 00:08:24,560 --> 00:08:26,440 对进程B而言类似 253 00:08:26,480 --> 00:08:28,320 它有一个互斥信号量的 254 00:08:28,360 --> 00:08:31,640 一个等待和唤醒的一个操作 255 00:08:31,680 --> 00:08:34,760 确保了后续的中间这部分工作呢 256 00:08:34,800 --> 00:08:37,800 是能够互斥完成 257 00:08:37,840 --> 00:08:40,520 如果它发现它对某些共享变量 258 00:08:40,560 --> 00:08:41,800 形成的一个条件 259 00:08:41,840 --> 00:08:42,920 能够让它满足之后呢 260 00:08:42,960 --> 00:08:44,840 它会发出一个cond_signal 261 00:08:44,880 --> 00:08:48,000 这个操作呢会使得前面的线程A 262 00:08:48,040 --> 00:08:50,080 能够被唤醒并继续执行 263 00:08:50,120 --> 00:08:51,760 大家需要注意什么呢 264 00:08:51,800 --> 00:08:53,000 线程A和线程B 265 00:08:53,040 --> 00:08:55,000 它们在进入管程之后呢 266 00:08:55,040 --> 00:08:57,240 只有一个可以去互斥地 267 00:08:57,280 --> 00:08:59,160 访问那些共享变量 268 00:08:59,200 --> 00:09:01,800 所以说当它唤醒完线程A之后呢 269 00:09:01,840 --> 00:09:05,080 其实怎么确保这个互斥性 270 00:09:05,120 --> 00:09:06,360 是有一定讲究的 271 00:09:06,400 --> 00:09:08,000 这也是我们说在具体实验上 272 00:09:08,040 --> 00:09:10,720 和原理上有一些很细微的差别 273 00:09:10,760 --> 00:09:11,960 在我们具体实验中 274 00:09:12,000 --> 00:09:16,200 需要去仔细地去体会 275 00:09:16,240 --> 00:09:18,960 我们来看看一个大致的执行过程 276 00:09:19,000 --> 00:09:21,040 这是线程A的这个wait的 277 00:09:21,080 --> 00:09:23,560 条件变量的一个操作 278 00:09:23,600 --> 00:09:25,600 这是线程B的signal的 279 00:09:25,640 --> 00:09:27,080 一个条件变量的一个操作 280 00:09:27,120 --> 00:09:28,920 这是原理部分的内容 281 00:09:28,960 --> 00:09:31,280 那我们可以跟具体实现对照一下 282 00:09:31,320 --> 00:09:33,120 看看我们的实现是否能够 283 00:09:33,160 --> 00:09:34,480 满足原理的功能 284 00:09:34,520 --> 00:09:35,840 同时呢它和原理 285 00:09:35,880 --> 00:09:38,600 在实现上具体有哪些不同 286 00:09:38,640 --> 00:09:39,960 我们可以第一个看着 287 00:09:40,000 --> 00:09:41,480 既然要等待所以说 288 00:09:41,520 --> 00:09:43,800 对应的线程个数会做一个加操作 289 00:09:43,840 --> 00:09:45,800 那么唤醒的时候 290 00:09:45,840 --> 00:09:48,000 会把这个线程个数做减操作 291 00:09:48,040 --> 00:09:49,600 对应我们具体的 292 00:09:49,640 --> 00:09:51,640 条件变量实现可以看到 293 00:09:51,680 --> 00:09:55,520 这有一个cv.count++ cv.count-- 294 00:09:55,560 --> 00:09:57,160 和这两个是对应起来的 295 00:09:57,200 --> 00:10:01,560 这一步是有直接的对应关系 296 00:10:01,600 --> 00:10:05,120 第二个对于条件不满足之后 297 00:10:05,160 --> 00:10:06,720 当前线程它会需要去 298 00:10:06,760 --> 00:10:09,000 做一个等待操作 299 00:10:09,040 --> 00:10:10,200 同时把自己挂到 300 00:10:10,240 --> 00:10:11,560 这个等待队列里面去 301 00:10:11,600 --> 00:10:14,200 这是线程A一个工作 302 00:10:14,240 --> 00:10:16,640 对于线程B而言它后进入 303 00:10:16,680 --> 00:10:19,120 它会发现当前有线程在等待 304 00:10:19,160 --> 00:10:20,520 但它条件也满足了 305 00:10:20,560 --> 00:10:22,240 所以它会把线程A呢 306 00:10:22,280 --> 00:10:24,120 从等待队列里面移出来 307 00:10:24,160 --> 00:10:25,360 同时唤醒线程A 308 00:10:25,400 --> 00:10:27,160 这是它的一个工作过程 309 00:10:27,200 --> 00:10:30,120 那么这个呢也是原理上一个实现 310 00:10:30,160 --> 00:10:32,560 我们看看如果落到具体实现上面 311 00:10:32,600 --> 00:10:34,400 怎么来完成 312 00:10:34,440 --> 00:10:37,800 OK 可以看着先执行线程A 313 00:10:37,840 --> 00:10:39,200 它会做一个等待的时候 314 00:10:39,240 --> 00:10:42,280 会有cv.count++ 315 00:10:42,320 --> 00:10:45,000 然后接着它会由于条件不满足 316 00:10:45,040 --> 00:10:49,160 它会做一个sem_wait来完成睡眠 317 00:10:49,200 --> 00:10:49,840 需要注意的是它这里面 318 00:10:49,880 --> 00:10:52,760 用到的信号量是cv.sem 319 00:10:52,800 --> 00:10:58,000 不是管程里面那个信号量 320 00:10:58,040 --> 00:10:59,600 它这儿就睡着了 321 00:10:59,640 --> 00:11:03,520 紧接着呢我们的线程B会继续执行 322 00:11:03,560 --> 00:11:06,280 线程B执行到一定程度之后会发现 323 00:11:06,320 --> 00:11:09,920 条件满足同时有线程正在等待 324 00:11:09,960 --> 00:11:12,320 为什么呢因为cv.count是大于0的 325 00:11:12,360 --> 00:11:14,160 和这个是一致的 326 00:11:14,200 --> 00:11:18,000 如果cv.count>0它会去把这个线程A 327 00:11:18,040 --> 00:11:19,720 从等待队列里面移出来 328 00:11:19,760 --> 00:11:21,680 同时唤醒线程A 329 00:11:21,720 --> 00:11:24,480 那么这里面是用一个sem_signal 330 00:11:24,520 --> 00:11:26,880 就是信号量的一个V操作 331 00:11:26,920 --> 00:11:29,720 来完成对cv.sem的一个处理 332 00:11:29,760 --> 00:11:31,880 那么这两个是匹配的 333 00:11:31,920 --> 00:11:33,320 可以看出来 334 00:11:33,360 --> 00:11:35,640 这一步也是可以和我们原理 335 00:11:35,680 --> 00:11:37,160 是一一对应的 336 00:11:37,200 --> 00:11:38,760 337 00:11:38,800 --> 00:11:40,320 再接下来我们看一下 338 00:11:40,360 --> 00:11:42,960 会发现线程A当它要睡眠的时候 339 00:11:43,000 --> 00:11:45,760 它会把刚才进入管程里面 340 00:11:45,800 --> 00:11:49,520 那个操作那个mutex要给释放掉 341 00:11:49,560 --> 00:11:52,280 这样才能使得其它的进程或者线程 342 00:11:52,320 --> 00:11:53,760 才有机会进入到管程中 343 00:11:53,800 --> 00:11:55,240 去执行对应的一些操作 344 00:11:55,280 --> 00:11:56,640 所以这里面有release(lock) 345 00:11:56,680 --> 00:11:57,720 这么一个操作 346 00:11:57,760 --> 00:12:00,720 347 00:12:00,760 --> 00:12:02,960 在具体实现这上面也存在一个 348 00:12:03,000 --> 00:12:05,800 信号量的一个signal就是V操作 349 00:12:05,840 --> 00:12:09,680 来完成对关于管程那个mutex 350 00:12:09,720 --> 00:12:11,040 的一个释放操作 351 00:12:11,080 --> 00:12:12,320 但是这个和这边 352 00:12:12,360 --> 00:12:14,080 好像没有对应的关系 353 00:12:14,120 --> 00:12:15,680 大家觉得在哪呢 354 00:12:15,720 --> 00:12:20,680 它对应进入P操作在哪呢 355 00:12:20,720 --> 00:12:22,800 其实我们可以看到 356 00:12:22,840 --> 00:12:25,040 在wait具体实现中呢 357 00:12:25,080 --> 00:12:26,920 就是条件变量的具体实现中呢 358 00:12:26,960 --> 00:12:29,040 有一个sem_signal 359 00:12:29,080 --> 00:12:30,120 那么这个signal 360 00:12:30,160 --> 00:12:32,200 其实和我们另一个进程 361 00:12:32,240 --> 00:12:34,400 它的sem_wait是对应起来的 362 00:12:34,440 --> 00:12:36,000 这使得我们线程A 363 00:12:36,040 --> 00:12:37,520 通过执行sem_wait 364 00:12:37,560 --> 00:12:39,120 处于等待状态之后呢 365 00:12:39,160 --> 00:12:43,160 且释放了它所占用的互斥的信号量 366 00:12:43,200 --> 00:12:46,200 就是monitor.mutex的时候 367 00:12:46,240 --> 00:12:47,720 使得其它另外一个线程 368 00:12:47,760 --> 00:12:49,440 比如线程B可以进一步执行 369 00:12:49,480 --> 00:12:50,320 因为线程B这时候 370 00:12:50,360 --> 00:12:52,120 在执行sem_wait的时候 371 00:12:52,160 --> 00:12:53,560 它就可以继续往下走了 372 00:12:53,600 --> 00:12:55,000 这已经被释放了 373 00:12:55,040 --> 00:12:56,960 然后线程B在执行这个 374 00:12:57,000 --> 00:12:58,600 sem_wait之后 375 00:12:58,640 --> 00:13:01,040 它会去查询共享变量 376 00:13:01,080 --> 00:13:03,040 看这个变量所对应条件是否满足 377 00:13:03,080 --> 00:13:06,680 如果满足它会执行cond_signal 378 00:13:06,720 --> 00:13:09,360 就是关于条件变量一个唤醒操作 379 00:13:09,400 --> 00:13:11,560 那么执行这个条件变量唤醒操作之后呢 380 00:13:11,600 --> 00:13:14,320 它可以使得我们的线程A 381 00:13:14,360 --> 00:13:15,480 能够进一步执行 382 00:13:15,520 --> 00:13:16,680 但是这里面有一些 383 00:13:16,720 --> 00:13:19,320 微妙一些处理过程大家需要注意 384 00:13:19,360 --> 00:13:22,120 385 00:13:22,160 --> 00:13:24,600 那我们看一下关于条件变量的signal 386 00:13:24,640 --> 00:13:27,080 它到底做哪些具体的实现 387 00:13:27,120 --> 00:13:29,160 前面我们说到的monitor中 388 00:13:29,200 --> 00:13:30,240 关于我们管程 389 00:13:30,280 --> 00:13:31,800 它有两个特殊的成员变量 390 00:13:31,840 --> 00:13:32,960 一个是next_count(在monitor中) 391 00:13:33,000 --> 00:13:34,360 一个是next信号量(在monitor中) 392 00:13:34,400 --> 00:13:35,400 那么这两者呢 393 00:13:35,440 --> 00:13:36,840 其实我们没有做进一步解释 394 00:13:36,880 --> 00:13:38,880 这里面我们可以展开看一看 395 00:13:38,920 --> 00:13:42,120 当我们发现有线程处于 396 00:13:42,160 --> 00:13:44,280 等待在某个条件变量的时候 397 00:13:44,320 --> 00:13:46,480 它的cv.count是大于0的 398 00:13:46,520 --> 00:13:48,880 一旦大于0它会做进一步的一些操作 399 00:13:48,920 --> 00:13:49,520 比如说 400 00:13:49,560 --> 00:13:52,040 把monitor.next_count做一个加操作 401 00:13:52,080 --> 00:13:54,960 发出signal信号 402 00:13:55,000 --> 00:13:56,560 这是cv.sem 403 00:13:56,600 --> 00:13:57,920 对应的就是wakeup 404 00:13:57,960 --> 00:14:00,840 那它会把另一个我们thread A给唤醒 405 00:14:00,880 --> 00:14:02,440 这是一个正常操作 406 00:14:02,480 --> 00:14:03,760 但是接下来 又做了一个 407 00:14:03,800 --> 00:14:04,920 sem_wait 408 00:14:04,960 --> 00:14:06,800 wait什么呢 是monitor.next 409 00:14:06,840 --> 00:14:10,080 就是在管程中那个next信号量 410 00:14:10,120 --> 00:14:11,280 那做了这个操作 411 00:14:11,320 --> 00:14:14,840 使得自身会陷入睡眠状态 412 00:14:14,880 --> 00:14:17,480 这也就意味着对于原理课上来说的 413 00:14:17,520 --> 00:14:20,160 wakeup(t)这一步操作呢 414 00:14:20,200 --> 00:14:23,680 除了唤醒我们的其他线程之外呢 415 00:14:23,720 --> 00:14:26,400 还把自身给处于一个睡眠状态了 416 00:14:26,440 --> 00:14:28,360 这是sem_wait干的事情 417 00:14:28,400 --> 00:14:30,240 OK 那这个wait之后 418 00:14:30,280 --> 00:14:32,160 谁来唤醒它呢 419 00:14:32,200 --> 00:14:33,600 我们再来看一下 420 00:14:33,640 --> 00:14:35,320 线程A中的一部分实现 421 00:14:35,360 --> 00:14:37,920 线程A中其实有一块判断 422 00:14:37,960 --> 00:14:41,000 如果monitor.next_count>0 423 00:14:41,040 --> 00:14:43,680 那么它就会做一个semaphore的唤醒 424 00:14:43,720 --> 00:14:46,000 唤醒的正好是这边所等待的 425 00:14:46,040 --> 00:14:47,920 monitor.next 426 00:14:47,960 --> 00:14:51,280 貌似thread A可以用来唤醒thread B 427 00:14:51,320 --> 00:14:53,040 但这里面有个时间差的问题 428 00:14:53,080 --> 00:14:54,120 我们先说了 429 00:14:54,160 --> 00:14:55,560 thread A是先执行的 430 00:14:55,600 --> 00:14:56,880 thread B是后执行的 431 00:14:56,920 --> 00:14:58,840 当thread A执行到 432 00:14:58,880 --> 00:15:01,720 sem_wait(cv.sem)的时候 433 00:15:01,760 --> 00:15:03,480 它已经处于睡眠状态了 434 00:15:03,520 --> 00:15:04,760 在这个时候 435 00:15:04,800 --> 00:15:08,200 这个if操作根本没有执行 436 00:15:08,240 --> 00:15:10,120 而我们的thread B 437 00:15:10,160 --> 00:15:12,360 当它执行到sem_wait的时候 438 00:15:12,400 --> 00:15:16,160 它唤醒的thread A在这个地方 439 00:15:16,200 --> 00:15:17,560 thread A可以继续往下走 440 00:15:17,600 --> 00:15:19,240 但是无法再回去执行 441 00:15:19,280 --> 00:15:23,600 所以sem_wait(monitor.next) 442 00:15:23,640 --> 00:15:27,080 不能靠这部分语句来帮它唤醒 443 00:15:27,120 --> 00:15:28,880 那是靠哪来唤醒呢 444 00:15:28,920 --> 00:15:32,240 大家想一想 445 00:15:32,280 --> 00:15:33,720 其实这个唤醒操作 446 00:15:33,760 --> 00:15:37,320 还要看thread A的后续的执行 447 00:15:37,360 --> 00:15:41,080 我们看看当thread A在等待cv 448 00:15:41,120 --> 00:15:42,800 就是等待条件变量 449 00:15:42,840 --> 00:15:44,560 满足了之后呢它会被唤醒 450 00:15:44,600 --> 00:15:45,880 进一步去执行 451 00:15:45,920 --> 00:15:47,920 执行到最后 会注意 452 00:15:47,960 --> 00:15:50,920 在thread A的某一个function中呢 453 00:15:50,960 --> 00:15:51,560 会执行一个 454 00:15:51,600 --> 00:15:53,360 sem_signal的一个操作 455 00:15:53,400 --> 00:15:55,040 而这个操作正好是 456 00:15:55,080 --> 00:15:56,640 执行的是monitor.next 457 00:15:56,680 --> 00:15:57,760 但需要注意的是 458 00:15:57,800 --> 00:15:59,240 执行这个操作有个前提 459 00:15:59,280 --> 00:16:02,000 是monitor.next_count>0 460 00:16:02,040 --> 00:16:03,200 也就意味着这个next_count 461 00:16:03,240 --> 00:16:04,600 其实它的含义是什么呢 462 00:16:04,640 --> 00:16:08,080 含义是发出条件变量signal(操作)的 463 00:16:08,120 --> 00:16:09,840 线程的个数 464 00:16:09,880 --> 00:16:11,480 当某一个线程 465 00:16:11,520 --> 00:16:12,920 比如在这里面我们是thread B 466 00:16:12,960 --> 00:16:14,400 它这个线程 467 00:16:14,440 --> 00:16:17,040 由于发出了条件变量的signal操作 468 00:16:17,080 --> 00:16:19,800 且把自身置于一个睡眠状态 469 00:16:19,840 --> 00:16:23,320 使得被唤醒的线程A呢 470 00:16:23,360 --> 00:16:25,320 有机会在它退出的时候 471 00:16:25,360 --> 00:16:27,560 把线程B给唤醒 472 00:16:27,600 --> 00:16:29,400 唤醒是monitor.next 473 00:16:29,440 --> 00:16:30,320 为什么要这么做 474 00:16:30,360 --> 00:16:32,080 好像把这个问题给复杂化了 475 00:16:32,120 --> 00:16:33,560 其实这是有一定的原因 476 00:16:33,600 --> 00:16:34,640 原因在哪呢 477 00:16:34,680 --> 00:16:39,880 就在于这是管程里面的一个函数 478 00:16:39,920 --> 00:16:41,760 这是管程里面的另一个函数 479 00:16:41,800 --> 00:16:43,640 这是线程B在执行 480 00:16:43,680 --> 00:16:45,040 这是线程A在执行 481 00:16:45,080 --> 00:16:47,760 这两个函数都会涉及到 482 00:16:47,800 --> 00:16:49,640 对共享变量的访问 483 00:16:49,680 --> 00:16:50,840 既然都会涉及到 484 00:16:50,880 --> 00:16:51,600 对动共享变量的访问 485 00:16:51,640 --> 00:16:53,880 那其实根据我们管程的定义呢 486 00:16:53,920 --> 00:16:56,320 只允许一个进程 487 00:16:56,360 --> 00:16:57,920 对这个共享变量进行操作 488 00:16:57,960 --> 00:17:00,360 也就意味着这两个函数中 489 00:17:00,400 --> 00:17:02,360 只有一个可以去执行 490 00:17:02,400 --> 00:17:03,920 另一个只能等待 491 00:17:03,960 --> 00:17:06,520 所以当我们的线程B 492 00:17:06,560 --> 00:17:08,240 把线程A唤醒之后 493 00:17:08,280 --> 00:17:09,600 它自身必须要睡眠 494 00:17:09,640 --> 00:17:11,760 这使得线程A的 495 00:17:11,800 --> 00:17:13,440 对应的函数才能继续执行 496 00:17:13,480 --> 00:17:15,480 才能保证对共享变量的 497 00:17:15,520 --> 00:17:16,960 一个互斥的操作 498 00:17:17,000 --> 00:17:19,520 但是线程A执行完之后 499 00:17:19,560 --> 00:17:20,800 在最后的阶段 500 00:17:20,840 --> 00:17:23,920 它需要把我们的线程B给唤醒 501 00:17:23,960 --> 00:17:27,640 使得线程B能够持续往下走 502 00:17:27,680 --> 00:17:31,080 这样就确保了两个线程 503 00:17:31,120 --> 00:17:33,400 可以对两个不同的操作 504 00:17:33,440 --> 00:17:35,200 互斥地访问 505 00:17:35,240 --> 00:17:37,280 这是它们的一个执行过程 506 00:17:37,320 --> 00:17:40,440 507 00:17:40,480 --> 00:17:42,760 另外我们还要注意一点 508 00:17:42,800 --> 00:17:45,560 在这里面这个mutex出现了多次 509 00:17:45,600 --> 00:17:47,040 那么这个多次呢 510 00:17:47,080 --> 00:17:48,680 其实它有时间顺序 511 00:17:48,720 --> 00:17:50,240 它们之间不会出现 512 00:17:50,280 --> 00:17:52,960 我们说死锁或者说重复的现象 513 00:17:53,000 --> 00:17:55,120 为什么 大家注意一下 514 00:17:55,160 --> 00:17:57,760 以线程A开始执行为例 515 00:17:57,800 --> 00:17:58,640 一开始它要执行 516 00:17:58,680 --> 00:18:00,240 一个mutex的一个wait 517 00:18:00,280 --> 00:18:01,680 就是信号量的一个wait 518 00:18:01,720 --> 00:18:03,960 在0时刻在执行这个地方 519 00:18:04,000 --> 00:18:06,360 执行到wait_cv的时候 520 00:18:06,400 --> 00:18:10,200 它会再次发出一个sem_signal 521 00:18:10,240 --> 00:18:11,600 那么这个sem_signal呢 522 00:18:11,640 --> 00:18:13,680 实际上是把这个信号给释放掉了 523 00:18:13,720 --> 00:18:15,320 这个释放使得我们的 524 00:18:15,360 --> 00:18:16,520 线程B可以执行 525 00:18:16,560 --> 00:18:18,280 那么第三个时刻 526 00:18:18,320 --> 00:18:19,760 就是2标记的时刻呢 527 00:18:19,800 --> 00:18:21,920 会做一个sem_wait 528 00:18:21,960 --> 00:18:23,760 当线程B最后离开的时候呢 529 00:18:23,800 --> 00:18:26,440 又会做一个sem_signal 530 00:18:26,480 --> 00:18:29,080 来完成对这个互斥信号量的 531 00:18:29,120 --> 00:18:30,840 一个释放操作 532 00:18:30,880 --> 00:18:33,840 可以看出来 0 1 2 3 533 00:18:33,880 --> 00:18:35,400 这个呢是确保了 534 00:18:35,440 --> 00:18:37,000 我们的线程A和线程B 535 00:18:37,040 --> 00:18:40,240 互斥地来访问相应的共享变量 536 00:18:40,280 --> 00:18:42,520 或者是访问相应的操作 537 00:18:42,560 --> 00:18:45,400 且不会引起冲突 538 00:18:45,440 --> 00:18:47,720 另外大家还需要注意一点 539 00:18:47,760 --> 00:18:49,640 当线程A被唤醒之后呢 540 00:18:49,680 --> 00:18:51,400 需要注意它这时候位置位于 541 00:18:51,440 --> 00:18:54,040 sem_wait之后这一点 542 00:18:54,080 --> 00:18:56,040 根据我们的原理课的讲解 543 00:18:56,080 --> 00:18:58,760 在这里我们需要重新得互斥锁(monitor.mutex) 544 00:18:58,800 --> 00:18:59,960 但我们在具体实验中 545 00:19:00,000 --> 00:19:02,160 (线程A)并没有重新获得这个互斥锁(monitor.mutex) 546 00:19:02,200 --> 00:19:03,040 那怎么能确保 547 00:19:03,080 --> 00:19:05,400 这个互斥的正确执行呢 548 00:19:05,440 --> 00:19:07,920 那其实是在于线程A这时候 549 00:19:07,960 --> 00:19:10,160 它的互斥是通过线程B的 550 00:19:10,200 --> 00:19:11,680 这个操作来完成的 551 00:19:11,720 --> 00:19:14,880 所以说大家要注意 0 1 2 3 552 00:19:14,920 --> 00:19:16,240 2这一步呢 553 00:19:16,280 --> 00:19:17,760 实际上是帮助我们线程A 554 00:19:17,800 --> 00:19:21,560 继续保证了它后续的操作的互斥性 555 00:19:21,600 --> 00:19:25,200 大家这一点需要理解一下 556 00:19:25,240 --> 00:19:26,560 那我们来看一下 557 00:19:26,600 --> 00:19:29,000 想结合一下线程A 558 00:19:29,040 --> 00:19:30,840 和线程B的执行过程 559 00:19:30,880 --> 00:19:32,680 看看我们这个条件变量的 560 00:19:32,720 --> 00:19:35,080 等待操作和signal操作 561 00:19:35,120 --> 00:19:37,000 它们具体怎么来完成 562 00:19:37,040 --> 00:19:39,760 正确的同步互斥的执行过程 563 00:19:39,800 --> 00:19:43,480 我们这里面专门用0到17来标注了 564 00:19:43,520 --> 00:19:45,400 它的一个大致的执行的过程 565 00:19:45,440 --> 00:19:47,040 我们一步一步来分析一下 566 00:19:47,080 --> 00:19:49,120 首先线程A来先执行 567 00:19:49,160 --> 00:19:52,720 它首先通过monitor.mutex 568 00:19:52,760 --> 00:19:55,640 这个信号量进入到临界区执行 569 00:19:55,680 --> 00:19:57,680 它用到了sem_wait 570 00:19:57,720 --> 00:19:59,400 OK 这是第一步 571 00:19:59,440 --> 00:20:01,840 进去之后它发现 572 00:20:01,880 --> 00:20:04,320 它对共享变量所需要的那些条件 573 00:20:04,360 --> 00:20:05,400 得不到满足 574 00:20:05,440 --> 00:20:09,360 因此它会调用条件变量的wait操作(wait_cv) 575 00:20:09,400 --> 00:20:11,040 这个条件变量是在哪呢 576 00:20:11,080 --> 00:20:14,280 是在这里面来实现的wait操作 577 00:20:14,320 --> 00:20:15,640 所以这是第一步 578 00:20:15,680 --> 00:20:17,760 然后呢第二个时刻 579 00:20:17,800 --> 00:20:19,720 是进入了条件变量的wait操作的 580 00:20:19,760 --> 00:20:21,680 具体的一个函数里面去 581 00:20:21,720 --> 00:20:23,560 当前等待的个数 582 00:20:23,600 --> 00:20:24,960 条件变量的等待个数 583 00:20:25,000 --> 00:20:27,000 会做一个加一的操作 584 00:20:27,040 --> 00:20:30,160 在第三步因为在这个时刻 585 00:20:30,200 --> 00:20:32,120 monitor.next_count还是为0的 586 00:20:32,160 --> 00:20:34,000 所以说它是执行else操作 587 00:20:34,040 --> 00:20:36,160 第三步有一个sem_signal 588 00:20:36,200 --> 00:20:39,400 这个signal是把临界区的 589 00:20:39,440 --> 00:20:41,560 wait操作给退出出去 590 00:20:41,600 --> 00:20:44,440 它做了一个monitor.mutex的 591 00:20:44,480 --> 00:20:46,080 一个signal操作 592 00:20:46,120 --> 00:20:47,520 这是第三步 593 00:20:47,560 --> 00:20:50,640 那么第四步它自身要进入睡眠 594 00:20:50,680 --> 00:20:52,320 所以说有个sem_wait 595 00:20:52,360 --> 00:20:54,320 睡在哪呢 睡在条件变量的 596 00:20:54,360 --> 00:20:55,280 那个信号量里面 597 00:20:55,320 --> 00:20:56,560 就是cv.sem里面 598 00:20:56,600 --> 00:20:57,840 这是第四步 599 00:20:57,880 --> 00:20:59,080 当执行到这一步的时候呢 600 00:20:59,120 --> 00:21:00,320 我们可以发现 601 00:21:00,360 --> 00:21:03,120 线程A处于等待状态 602 00:21:03,160 --> 00:21:06,360 它已经不能再继续往下执行了 603 00:21:06,400 --> 00:21:08,440 那我们线程B能够继续执行吗 604 00:21:08,480 --> 00:21:09,880 可以看一下 605 00:21:09,920 --> 00:21:11,280 线程B一开始执行的 606 00:21:11,320 --> 00:21:14,160 也是一个信号量的wait操作 607 00:21:14,200 --> 00:21:15,200 这个信号量是什么呢 608 00:21:15,240 --> 00:21:16,960 是monitor.mutex 609 00:21:17,000 --> 00:21:20,520 由于前面在这第三步的时候呢 610 00:21:20,560 --> 00:21:22,120 线程A已经释放了 611 00:21:22,160 --> 00:21:23,120 这个monitor.mutex 612 00:21:23,160 --> 00:21:25,480 所以这一步可以进去 613 00:21:25,520 --> 00:21:26,880 这是第五个时刻 614 00:21:26,920 --> 00:21:29,160 进去之后也进入了一个临界区 615 00:21:29,200 --> 00:21:32,120 在这里面呢线程B会判断一下 616 00:21:32,160 --> 00:21:34,360 跟共享变量相关的一些条件 617 00:21:34,400 --> 00:21:35,920 发现这些条件可以满足 618 00:21:35,960 --> 00:21:38,840 因此发出一个signal_cv操作 619 00:21:38,880 --> 00:21:41,240 而这个操作会唤醒线程A 620 00:21:41,280 --> 00:21:42,720 具体怎么做呢我们看一下 621 00:21:42,760 --> 00:21:45,080 这是第时刻六完成的造作 622 00:21:45,120 --> 00:21:47,120 在这它完成对条件变量的 623 00:21:47,160 --> 00:21:50,840 signal的一个函数调用 624 00:21:50,880 --> 00:21:51,960 首先它的判断 625 00:21:52,000 --> 00:21:53,640 cv.count是否大于0 626 00:21:53,680 --> 00:21:54,880 这里面可以看着 627 00:21:54,920 --> 00:21:56,120 这做了++操作 628 00:21:56,160 --> 00:21:58,840 所以说在这里面count是大于0的 629 00:21:58,880 --> 00:22:00,200 会进一步去执行 630 00:22:00,240 --> 00:22:01,600 执行到哪呢 631 00:22:01,640 --> 00:22:04,400 sem_signal(cv.sem) 632 00:22:04,440 --> 00:22:06,280 这个是有什么用呢 633 00:22:06,320 --> 00:22:09,400 这一步会完成对线程A的唤醒 634 00:22:09,440 --> 00:22:11,320 这里面可以看着 635 00:22:11,360 --> 00:22:14,160 在线程A的条件变量的等待操作中 636 00:22:14,200 --> 00:22:17,200 它有一个对信号量cv.sem的 637 00:22:17,240 --> 00:22:19,600 一个等待 638 00:22:19,640 --> 00:22:22,800 通过线程B的 639 00:22:22,840 --> 00:22:25,440 sem_signal(cv.sem) 640 00:22:25,480 --> 00:22:28,160 可以完成对它的一个唤醒操作 641 00:22:28,200 --> 00:22:30,080 然后紧接着线程B 642 00:22:30,120 --> 00:22:32,520 自身再执行一个sem_wait 在哪呢 643 00:22:32,560 --> 00:22:35,560 wait在monitor.next条件变量上面 644 00:22:35,600 --> 00:22:38,680 使得线程B又睡眠了 645 00:22:38,720 --> 00:22:40,200 这是第九步 646 00:22:40,240 --> 00:22:43,240 那线程A会被唤醒 647 00:22:43,280 --> 00:22:46,400 唤醒之后会执行一个cv.count--操作 648 00:22:46,440 --> 00:22:49,200 从而使得cv.count从1又变成了0 649 00:22:49,240 --> 00:22:51,920 这是第十个时刻 650 00:22:51,960 --> 00:22:52,960 执行完之后呢 651 00:22:53,000 --> 00:22:54,320 这个函数就执行完毕 652 00:22:54,360 --> 00:22:56,720 它会跳出wait_cv这么一个函数 653 00:22:56,760 --> 00:22:59,200 这是第十一步 这一个过程 654 00:22:59,240 --> 00:23:01,080 并进一步判断 655 00:23:01,120 --> 00:23:03,600 当前的monitor.next_count是否大于0 656 00:23:03,640 --> 00:23:04,840 大家注意一下 657 00:23:04,880 --> 00:23:06,160 在第七步之后呢 658 00:23:06,200 --> 00:23:08,120 有一个monitor.next_count++ 659 00:23:08,160 --> 00:23:09,280 这么一个操作 660 00:23:09,320 --> 00:23:12,760 所以这个条件是成立的 661 00:23:12,800 --> 00:23:15,880 所以它会执行sem_signal 662 00:23:15,920 --> 00:23:18,240 monitor.next信号量 663 00:23:18,280 --> 00:23:19,360 那么这个信号量呢 664 00:23:19,400 --> 00:23:23,480 会唤醒刚才线程B睡眠的 665 00:23:23,520 --> 00:23:24,960 那个信号量 666 00:23:25,000 --> 00:23:27,080 monitor.next在这sem_wait 667 00:23:27,120 --> 00:23:29,440 然后它自身就退出了 668 00:23:29,480 --> 00:23:31,400 退出了管程的一个操作 669 00:23:31,440 --> 00:23:33,280 那我们可以看着 670 00:23:33,320 --> 00:23:35,840 线程B这个时候它唤醒之后呢 671 00:23:35,880 --> 00:23:39,400 会在第14个时刻继续执行 672 00:23:39,440 --> 00:23:41,440 完成对next_count的一个减减操作 673 00:23:41,480 --> 00:23:43,320 使得count也变成了0 674 00:23:43,360 --> 00:23:45,400 然后这个函数会跳出 675 00:23:45,440 --> 00:23:48,200 跳出返回到15这个时刻 676 00:23:48,240 --> 00:23:49,560 然后继续往下走 677 00:23:49,600 --> 00:23:51,480 在15这个时刻它会进一步判断 678 00:23:51,520 --> 00:23:53,760 是否monitor.next_count大于0 679 00:23:53,800 --> 00:23:54,920 这里面做了一个减操作 680 00:23:54,960 --> 00:23:56,360 所以它已经是为0了 681 00:23:56,400 --> 00:23:57,200 这一步不会执行 682 00:23:57,240 --> 00:23:58,840 所以会执行第16和17 683 00:23:58,880 --> 00:24:00,680 就是else之后的这个语句 684 00:24:00,720 --> 00:24:03,480 完成对monitor.mutex 685 00:24:03,520 --> 00:24:05,000 这么一个互斥信号量的 686 00:24:05,040 --> 00:24:06,400 一个释放操作 687 00:24:06,440 --> 00:24:07,360 那么这个操作呢 688 00:24:07,400 --> 00:24:08,920 是和第五步的 689 00:24:08,960 --> 00:24:11,720 sem.wait monitor.mutex 690 00:24:11,760 --> 00:24:14,840 是对应的 那完成这一步之后呢 691 00:24:14,880 --> 00:24:17,160 使得线程A和线程B 692 00:24:17,200 --> 00:24:20,200 都完成了各自的对管程中的 693 00:24:20,240 --> 00:24:22,360 特定操作的一个执行过程 694 00:24:22,400 --> 00:24:25,160 且保证了正确的同步互斥关系 695 00:24:25,200 --> 00:24:25,240