-
Notifications
You must be signed in to change notification settings - Fork 3
/
05.ass
543 lines (541 loc) · 76.6 KB
/
05.ass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
[Script Info]
Title: C++11开始的多线程编程
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
Original Script: woclass
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,微软雅黑,80,&H00FFFFFF,&H0000FFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2.0,1,2,10,10,10,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.87,0:00:03.92,Default,,0,0,0,,好,我们现在开始上课
Dialogue: 0,0:00:03.92,0:00:07.65,Default,,0,0,0,,今天的主题是多线程并发
Dialogue: 0,0:00:07.66,0:00:15.47,Default,,0,0,0,,然后首先我们是一个系列课,今天是第五第五节课了
Dialogue: 0,0:00:15.47,0:00:21.92,Default,,0,0,0,,然后我们需要有64位的,有多核的处理器
Dialogue: 0,0:00:21.92,0:00:33.13,Default,,0,0,0,,然后这是今天课的大纲,就可以看到我们这些都是C加加11新引入的特性
Dialogue: 0,0:00:33.13,0:00:39.93,Default,,0,0,0,,然后这每一个都是一个头文件,这是它里面的类
Dialogue: 0,0:00:39.93,0:00:47.74,Default,,0,0,0,,然后今天的课会用到刚才说的这个第二讲的很多知识
Dialogue: 0,0:00:47.74,0:00:51.47,Default,,0,0,0,,如果你没看,记得去看一下
Dialogue: 0,0:00:51.48,0:01:11.57,Default,,0,0,0,,然后今天会着重讲mute x嗯,首先在开始多线程之前,先要理解什么是时间在C语言中可以通过time括号now来获取当前的时间
Dialogue: 0,0:01:11.59,0:01:18.67,Default,,0,0,0,,它返回的是一个long类型,也就是一个整数,什么意思呢?
Dialogue: 0,0:01:18.67,0:01:32.84,Default,,0,0,0,,它代表从1970年1月1号到当前的时间用了多少秒,就是他是用一个固定的约定时辰时间点到现在
Dialogue: 0,0:01:32.84,0:01:40.25,Default,,0,0,0,,然后作为一个整数值,然后他要睡眠三秒,那就实力不伤
Dialogue: 0,0:01:40.25,0:01:47.01,Default,,0,0,0,,但如果我不想睡3秒,而是0.3秒呢,那就不行了
Dialogue: 0,0:01:47.01,0:01:52.81,Default,,0,0,0,,得用linux系统特有的u sleep,它才能睡零点几秒
Dialogue: 0,0:01:52.81,0:02:04.71,Default,,0,0,0,,所以说可以看到Z语言对时间的处理,它的弊端是类型都是整数,然后单位也不清楚,很容易混淆
Dialogue: 0,0:02:04.71,0:02:15.69,Default,,0,0,0,,甚至你可以把一个时间点乘以3,就是从1970年的这个时间段乘以3,这根本是无意义的计算
Dialogue: 0,0:02:15.70,0:02:20.34,Default,,0,0,0,,然后C语言却允许这样你在了吗?
Dialogue: 0,0:02:22.73,0:02:28.92,Default,,0,0,0,,所以说C加加10一开始就开始把时间标准化了
Dialogue: 0,0:02:28.93,0:02:44.12,Default,,0,0,0,,它利用了C加加具有强类型,可以区分不同的时间单位,比如用STD crown、冒号、冒号、milliseconds, 这个就代表用毫秒做单位
Dialogue: 0,0:02:44.13,0:02:51.56,Default,,0,0,0,,然后这个seconds呢就代表用秒做单位minutes就用分钟做单位
Dialogue: 0,0:02:51.57,0:02:56.53,Default,,0,0,0,,当然它的时间点和时间段也区分开来
Dialogue: 0,0:02:56.53,0:03:05.83,Default,,0,0,0,,时间段它是有一个明明确的时间单位,而时间点呢它又是这个time point类型
Dialogue: 0,0:03:05.84,0:03:24.68,Default,,0,0,0,,然后具体time point又分为三种,一种是steady clock,一种smaller sonic clock,一种是总之它是区分不同的时钟类型的,一种是CPU的计数器,一种是操作系统提供的
Dialogue: 0,0:03:24.68,0:03:30.01,Default,,0,0,0,,我们通常要精度高的话,就用steady clock就可以
Dialogue: 0,0:03:30.43,0:03:49.23,Default,,0,0,0,,然后它还具有运算符存在,比如它这个炭木炭,它是可以相减的,然后相减得到的就是一个时间段类型,嗯,然后时间段在不同的单位之间还可以转换
Dialogue: 0,0:03:49.23,0:04:08.37,Default,,0,0,0,,比如这个DT我们不知道它是什么单位,我们可以把它转换成秒的单位,通过duration cast,所以这个单位转换就变成类型转换就更加安全,明白吧?
Dialogue: 0,0:04:10.79,0:04:19.41,Default,,0,0,0,,然后就是可以看到这里面也有加法时间点加时间段还是时间点
Dialogue: 0,0:04:21.51,0:04:35.34,Default,,0,0,0,,然后最经典的案例就是上一期也用到了,就是它可以计算两个时间的差,从而就知道这个第七行执行用了多少时间
Dialogue: 0,0:04:35.34,0:04:43.41,Default,,0,0,0,,就一开始还没执行前,看看当前时间是多少,执行后当前时间是多少
Dialogue: 0,0:04:43.41,0:04:49.17,Default,,0,0,0,,然后两个时间的差值不就是时间段过了多少吗?
Dialogue: 0,0:04:49.18,0:05:01.28,Default,,0,0,0,,然后再把这个时间段转换为我们认识的微那个毫秒单位,然后返回的是一个64位整数,就是MS
Dialogue: 0,0:05:01.28,0:05:09.33,Default,,0,0,0,,然后他就能打印出啊,花了13毫秒有问题吗?
Dialogue: 0,0:05:10.79,0:05:13.51,Default,,0,0,0,,很高兴啊,老师
Dialogue: 0,0:05:21.79,0:05:27.86,Default,,0,0,0,,然后这里是只有返回了一个整数的毫秒数
Dialogue: 0,0:05:27.86,0:05:40.02,Default,,0,0,0,,如果我想要精度超过毫秒呢,就可以用double,然后double 的毫秒数可以用colonization double many
Dialogue: 0,0:05:41.05,0:05:54.48,Default,,0,0,0,,这样以后他就是就是刚才这个colonial seconds,其实是就是duration it,然后面对的简称
Dialogue: 0,0:05:58.49,0:06:09.81,Default,,0,0,0,,然后这里我们显示指明要用double,这样它输出就能够带小数点,是不是很方便
Dialogue: 0,0:06:11.56,0:06:22.87,Default,,0,0,0,,然后他还有一个好处,就是以前我们用睡眠一段时间要不同的操作系统,不同的API
Dialogue: 0,0:06:22.87,0:06:47.09,Default,,0,0,0,,现在他统一了用这个this实力的这个明明空间,它的意思就是当前县城要睡这么一段时间,然后这个时间是有类型的,它不是一个整数,它是有colonel milliseconds,代表要让它睡400毫秒,不是秒
Dialogue: 0,0:06:47.09,0:06:50.55,Default,,0,0,0,,这样它就能区分了嘛,对吧?
Dialogue: 0,0:06:51.67,0:06:59.48,Default,,0,0,0,,然后你也可以用micro表示,微淼seconds就是秒它的类型就就更安全
Dialogue: 0,0:06:59.49,0:07:11.81,Default,,0,0,0,,你知道它是什么单位,然后还有一个配合phone,phones是一个时间段,而是跳只是睡到一个时间点
Dialogue: 0,0:07:11.81,0:07:21.61,Default,,0,0,0,,就是比如我这里是当前时间加400毫秒,这和刚刚直接睡400毫秒是等价的
Dialogue: 0,0:07:22.92,0:07:27.09,Default,,0,0,0,,明白吧?
Dialogue: 0,0:07:29.26,0:07:48.00,Default,,0,0,0,,然后就是线程,就是首先什么是线程,就是有两种,一种是进程,进程就是一个EXE文件,一整AEXE文件就占据着一个进程
Dialogue: 0,0:07:48.00,0:08:05.56,Default,,0,0,0,,然后进程里面又可以创建很多个线程,嗯,就是进程它的体量是比较大的,每个进程都有独立的内存空间,而多个线程之间呢共享同一个地址空间
Dialogue: 0,0:08:05.56,0:08:09.95,Default,,0,0,0,,所以说它的效率更高,但它就不隔离
Dialogue: 0,0:08:09.95,0:08:13.17,Default,,0,0,0,,就有的时候是需要进程的
Dialogue: 0,0:08:14.29,0:08:16.16,Default,,0,0,0,,明白了吧?
Dialogue: 0,0:08:17.38,0:08:38.65,Default,,0,0,0,,所以说进程是大于这个线程的,嗯,才才来啊,然后就是我们高性能计算需要的是线程,因为他们需要访问同样的数据,如果进程的话就不行了
Dialogue: 0,0:08:38.65,0:08:46.35,Default,,0,0,0,,所以说拍摄这个东西是C加加20里面的
Dialogue: 0,0:08:46.94,0:08:54.19,Default,,0,0,0,,这个今天就是我刚才不是在动态里说有一个自由发挥课吗?
Dialogue: 0,0:08:54.19,0:09:02.07,Default,,0,0,0,,到时候等自由发挥课再说,现在不说了,然后为什么要多线程呢?
Dialogue: 0,0:09:02.07,0:09:09.32,Default,,0,0,0,,就是就是其实在多核处理器出现之前,就有多线程的概念了
Dialogue: 0,0:09:09.32,0:09:14.25,Default,,0,0,0,,为什么明明没有多个和我还要多线程呢?
Dialogue: 0,0:09:14.25,0:09:15.70,Default,,0,0,0,,不是浪费吗?
Dialogue: 0,0:09:15.70,0:09:24.98,Default,,0,0,0,,哎,有用多线程不一定是用来占据多个核的,它还可以用来让你程序变得异步
Dialogue: 0,0:09:24.98,0:09:37.17,Default,,0,0,0,,也就是比如就是比如迅雷下载,它不是有一个界面嘛,然后它可以一边下载一边那个你操作它的UI
Dialogue: 0,0:09:37.17,0:09:44.71,Default,,0,0,0,,但如果迅雷他们不用多线程的话,那就会变成你点了下载按钮
Dialogue: 0,0:09:44.71,0:09:53.12,Default,,0,0,0,,好,他的程序开始等待这个internet返回给他数据了,那他就陷入这个等待
Dialogue: 0,0:09:53.12,0:09:59.73,Default,,0,0,0,,他在等待的过程中,这个UI界面就卡死了,就不能动了
Dialogue: 0,0:09:59.73,0:10:11.33,Default,,0,0,0,,所以说就是如果你全是单线程的话,你的程序啊就很可能就你在做一件事,另一件事就不能同时执行
Dialogue: 0,0:10:11.33,0:10:19.06,Default,,0,0,0,,所以说多线程它就可以让你在注射的同时,另一个工作还能够继续
Dialogue: 0,0:10:19.07,0:10:30.94,Default,,0,0,0,,就比如这里我写了一个简易的例子,就是同时要下载,然后下载的同时还要和用户交互,就是interacting它
Dialogue: 0,0:10:30.95,0:10:44.66,Default,,0,0,0,,然后我们这个下载呢又是超级慢的一个动作,这就导致他必须全部下载完才能和用户打招呼
Dialogue: 0,0:10:44.66,0:10:57.08,Default,,0,0,0,,就比如这样下载,然后我这里输入他没反应了,他必须download complete以后,他才跟我说多进程
Dialogue: 0,0:10:57.08,0:11:08.79,Default,,0,0,0,,那有一个问题就是你在这个地方里面摁按钮,你怎么把这个消息传递给另一个下载进程呢?
Dialogue: 0,0:11:08.79,0:11:16.81,Default,,0,0,0,,就是进程它之间它之间那个传递信息的开销就更大了,知道吧?
Dialogue: 0,0:11:16.81,0:11:35.60,Default,,0,0,0,,啊,刚刚就说了,就是进程它有内存空间是隔离的,它们之间打交道要么通过文件,要么通过socket,那就没你们这个线程直接访问同一个内存,这么方便啊
Dialogue: 0,0:11:35.94,0:11:48.75,Default,,0,0,0,,所以说就是需要有多线程,然后就出现了C加加11,它从语言级别带来了STD实例的这个类
Dialogue: 0,0:11:48.75,0:11:56.43,Default,,0,0,0,,就是对呀对呀,进程弟子独立,所以沟通起来困难
Dialogue: 0,0:11:58.37,0:12:07.22,Default,,0,0,0,,然后就是以前就C语言其实有个叫P实力的的库,它就是操作系统提供的
Dialogue: 0,0:12:07.23,0:12:15.80,Default,,0,0,0,,然后你可以用它来操作线程,但它用起来是C语言的,不符合III思想
Dialogue: 0,0:12:15.80,0:12:26.37,Default,,0,0,0,,所以C加加分装一个STD实例的,而且它能够支持M的表达式哦,就这里STD适应的
Dialogue: 0,0:12:26.37,0:12:37.24,Default,,0,0,0,,然后我们这里弄一个参数,参数就是任何一个lambda表达式都可以,也可以是一个函数什么的
Dialogue: 0,0:12:39.23,0:12:45.77,Default,,0,0,0,,所以说这个已建成,一旦被创建之后,它就会在后台执行
Dialogue: 0,0:12:45.77,0:12:54.21,Default,,0,0,0,,然后可以看到这个它是在lambda里,所以它是在另一个线程里执行的,而interact
Dialogue: 0,0:12:54.22,0:12:56.40,Default,,0,0,0,,它直接在命里调用
Dialogue: 0,0:12:56.40,0:13:01.30,Default,,0,0,0,,所以它就是在我们的这个主线程里执行的
Dialogue: 0,0:13:01.99,0:13:13.87,Default,,0,0,0,,然后我们编译链接刚刚这个代码却发现出错了,出现哎PS里的create找不到,为啥呢?
Dialogue: 0,0:13:15.82,0:13:26.35,Default,,0,0,0,,这是因为STD实例的,它是基于P实例的,所以你必须把P实例的链接到这个程序上
Dialogue: 0,0:13:26.35,0:13:30.15,Default,,0,0,0,,而在windows上这个名字又不一样
Dialogue: 0,0:13:30.16,0:13:45.38,Default,,0,0,0,,谁为了跨平台c make自己推出了sleeves包,然后这个东西在windows上会变成windows的那个包,然后在linux上会变成P的,这样就跨平台了
Dialogue: 0,0:13:45.79,0:13:56.67,Default,,0,0,0,,然后有多线程以后可以看到我们这里在单位的同时输入彭于冰,然后enter他就及时的响应我了
Dialogue: 0,0:13:56.68,0:14:01.14,Default,,0,0,0,,然而却出现了这个错误,这是为什么呢?
Dialogue: 0,0:14:01.14,0:14:07.27,Default,,0,0,0,,这是因为刚刚我这个interact结束以后就直接return0了
Dialogue: 0,0:14:07.29,0:14:17.06,Default,,0,0,0,,而这个T可能还没下载完,但我mean return了以后,你知道它会退出当前进程,进程,退出那
Dialogue: 0,0:14:17.06,0:14:22.09,Default,,0,0,0,,这个直线程也就没了,所以他就崩溃了,咋办?
Dialogue: 0,0:14:22.57,0:14:32.53,Default,,0,0,0,,所以说就可以要让命等待一下刚刚创建这个T1,就用T一点胶印,胶印就是等待
Dialogue: 0,0:14:32.53,0:14:38.39,Default,,0,0,0,,比如这里我们输入单唠的钟的时候输入排位兵
Dialogue: 0,0:14:38.40,0:14:46.61,Default,,0,0,0,,然后安踏他跟我打了一声招呼以后,就陷入了等待,只进城这个状态
Dialogue: 0,0:14:46.61,0:14:57.45,Default,,0,0,0,,然后他等啊等啊等哦,终于下载完,下载完哦,还至县城退出了,那么我也可以退出了,知道吧?
Dialogue: 0,0:14:57.46,0:15:20.48,Default,,0,0,0,,的态度不一样,你不要打乱我的教学目标,什么单进程多线程啊,你那是排绳吧,排绳没有真正的多线程,就是排绳它的多个线程它是串行的,直行的,知道吗?
Dialogue: 0,0:15:20.48,0:15:28.63,Default,,0,0,0,,你那是排绳对,sleep for, 我没办法去搞一些互联网的东西出来
Dialogue: 0,0:15:28.63,0:15:38.31,Default,,0,0,0,,就是假设这是在下载吧,然后就是就是刚刚说这样可以就是胶印
Dialogue: 0,0:15:38.32,0:15:52.83,Default,,0,0,0,,然后但是如果我这个线程创建在另一个函数题里,那这个函数退出的时候,根据IIS想,那这个T一就会调用它的结构
Dialogue: 0,0:15:52.83,0:15:58.06,Default,,0,0,0,,函数结构了以后,这T一的资源就被销毁了
Dialogue: 0,0:15:58.06,0:16:07.34,Default,,0,0,0,,而资源一销毁,它这个单漏的,它指向的那个站,它也被销毁了,所以说就出错了
Dialogue: 0,0:16:07.34,0:16:17.46,Default,,0,0,0,,所以说就是嗯因为它这个实力的管理的资源嘛,所以说它定义了一个结构函数
Dialogue: 0,0:16:17.46,0:16:24.10,Default,,0,0,0,,因为定义了结构函数,它就把拷贝给删掉,但它提供了移动
Dialogue: 0,0:16:24.10,0:16:27.97,Default,,0,0,0,,所以说T结构以后T线程就没了
Dialogue: 0,0:16:27.97,0:16:40.13,Default,,0,0,0,,所以说为了让T这个T一被销毁的时候,这个T一线才能在后台运行,可以用TE data分离这个对象和线程
Dialogue: 0,0:16:40.14,0:16:47.88,Default,,0,0,0,,这样T一就在销毁的时候,它就不会实际销毁P16的那个线程了
Dialogue: 0,0:16:48.22,0:16:53.58,Default,,0,0,0,,老板不要乱叫,打乱我们的教学顺序了
Dialogue: 0,0:16:54.68,0:17:12.03,Default,,0,0,0,,然后这样你看,但是又出了个问题,就虽然没有出错,但是所述完成语音以后,然后我们think interact就是这里退出了以后,他虽然没出错,但下载也没有继续完成
Dialogue: 0,0:17:12.03,0:17:16.64,Default,,0,0,0,,就是detailed之后它不会自动胶印的,知道吧?
Dialogue: 0,0:17:16.64,0:17:31.82,Default,,0,0,0,,所以这个东西不一样,就是你要胶印,你还你想等他下完的话,你就得胶印,你得看看的话,你退出的时候,它就自动把你这个线程给杀死了
Dialogue: 0,0:17:31.82,0:17:33.17,Default,,0,0,0,,知道杀死吗?
Dialogue: 0,0:17:33.17,0:17:38.59,Default,,0,0,0,,就是不是杀人的意思,就是把这个县城结束掉了
Dialogue: 0,0:17:38.60,0:17:45.64,Default,,0,0,0,,所以说你要让他不结束,你可以这样就创建一个全局的线程表
Dialogue: 0,0:17:45.64,0:17:53.23,Default,,0,0,0,,然后你这这个函数题你创建了T1,你这退出不是T一就解构了吗?
Dialogue: 0,0:17:53.24,0:18:04.55,Default,,0,0,0,,你为了让T一能够活到命退出,你把它剖析到一个全局变量,全局变量的生命周期和命函数一样长
Dialogue: 0,0:18:04.56,0:18:20.29,Default,,0,0,0,,这样你在命函数最后一行上加上把铺的每一个线程都交应一下,这样的话就可以等下载完退出,而且也不需要在函数题里面就是胶印了
Dialogue: 0,0:18:22.17,0:18:29.02,Default,,0,0,0,,这样的话胶印就可以在退出的时候,而不是在函数体的时候
Dialogue: 0,0:18:32.69,0:18:44.00,Default,,0,0,0,,然后然后就是这样还是不方便,我们必须修改我的命函数,但是就可以用我们这个单例模式的特点
Dialogue: 0,0:18:44.01,0:18:56.16,Default,,0,0,0,,它的结构函数就是如果你有一个全局变量,TPTP的类型是sleep的,那么这个幂函数退出以后会自动执行
Dialogue: 0,0:18:56.17,0:18:59.20,Default,,0,0,0,,sleep的结构函数就是这个了
Dialogue: 0,0:19:00.07,0:19:13.17,Default,,0,0,0,,所以说这样就相当于直接在命之后调用了,这个,也就是把所有已经创建的县城全部等待,明白了吧?
Dialogue: 0,0:19:14.45,0:19:23.40,Default,,0,0,0,,知道单利模式吧,你看这样,我也是等他下完,这个时候其实已经命已经返回了
Dialogue: 0,0:19:23.40,0:19:30.18,Default,,0,0,0,,但是这个11个铺因为正好在解构嘛,所以他就等他下完了
Dialogue: 0,0:19:30.66,0:19:53.39,Default,,0,0,0,,然后还有就是这样,你还是得自定义结构函数,然后C加加20就引入这个结实力呢,它在结构函数里会自动调用胶印,就不需要你自定义一个,然后for each了,它就能够自动调用,就更安全了
Dialogue: 0,0:19:53.39,0:20:13.76,Default,,0,0,0,,但它是20里面才有的,十七里面还没有啊,到了吧,小鹰儿宝就是如果你的实力的是被移动过以后,就是这个这一行以后,你再去问他join AB他就会为force了
Dialogue: 0,0:20:13.76,0:20:21.54,Default,,0,0,0,,在这一行以前都是为Q就是join able,就是判断这个实例的里面是否为no
Dialogue: 0,0:20:21.55,0:20:22.36,Default,,0,0,0,,懂了吧
Dialogue: 0,0:20:22.36,0:20:32.54,Default,,0,0,0,,实例的你可以把它想象成一个指针,然后这个指针为note的时候,就是不join able怎么办?
Dialogue: 0,0:20:32.88,0:20:45.48,Default,,0,0,0,,这个木你也可以和就上第二期讲的那个指针相比,就是unique point,它不是不让拷贝嘛,实力的也不能拷贝,也要move
Dialogue: 0,0:20:45.48,0:20:57.24,Default,,0,0,0,,然后move了以后,T的那个对象不是没了嘛,它得表示这个是不可用的状态,这个状态就有胶印的来判断
Dialogue: 0,0:21:00.24,0:21:08.87,Default,,0,0,0,,然后就是小鹏老师今日吐槽,嗯,刚刚好像吐槽过,总之多线程很重要
Dialogue: 0,0:21:08.88,0:21:16.54,Default,,0,0,0,,然后如果你知道blend它在运行结算的时候,它的界面就会一卡一卡的
Dialogue: 0,0:21:16.54,0:21:22.01,Default,,0,0,0,,它意思你必须算完一针,它的界面才能刷新一遍
Dialogue: 0,0:21:22.02,0:21:29.41,Default,,0,0,0,,然后这个时候就导致他在解散的时候,你完全用不了这个界面了
Dialogue: 0,0:21:29.41,0:21:36.25,Default,,0,0,0,,就是你解散一针花多少时间,你就这段时间里它就会定格了
Dialogue: 0,0:21:36.25,0:21:40.63,Default,,0,0,0,,然后你想要取消都来不及取消,知道吧?
Dialogue: 0,0:21:40.63,0:22:02.54,Default,,0,0,0,,然后我们ZNO则是大量使用了多进程和多线程,它可以随时随地就在算的过程中,你也可以操作这个UI,甚至滑动到前几针,它同时后面还在不断的追加这个新的结算结果,这就很方便,对吧?
Dialogue: 0,0:22:02.54,0:22:16.60,Default,,0,0,0,,当然这个和open只要有一定的关系,但是因为zeno就是用了多进程来伺候open GR,从而可以实现并发,对吧?
Dialogue: 0,0:22:17.09,0:22:34.41,Default,,0,0,0,,就是啊就是啊这个open,只要这个事你太沙雕了,就是他搞的就是blender的,他的UI他不是用QT,他就是基于open GO直接渲染出来了
Dialogue: 0,0:22:34.42,0:22:53.27,Default,,0,0,0,,所以说就导致的很多都是单线程的设计啊,然后blend它又用了python,而python的单多线程它是假的,就是多个线程,它其实只能并发而不能并行
Dialogue: 0,0:22:53.27,0:23:00.32,Default,,0,0,0,,总之它这个blend用了python又用了open,就导致很容易卡住
Dialogue: 0,0:23:00.67,0:23:12.11,Default,,0,0,0,,然后就是第二章刚刚说到异步,多线程是好的,一方面它能够并行,一方面能够并发,也就是异步
Dialogue: 0,0:23:12.11,0:23:13.87,Default,,0,0,0,,什么是异步呢?
Dialogue: 0,0:23:13.87,0:23:20.03,Default,,0,0,0,,同步的话就是我必须下载完文件才能和用户交互
Dialogue: 0,0:23:20.03,0:23:36.18,Default,,0,0,0,,一步呢就是我下载文件当中哎呀,阻塞了,然后我在等待income店的请求,然后这时候就可以自动切换到我的这个和用户交互的线程上
Dialogue: 0,0:23:36.18,0:23:39.69,Default,,0,0,0,,这样用户的体验就不会下降
Dialogue: 0,0:23:39.70,0:23:54.07,Default,,0,0,0,,然后C在家里其实不光提供了sleep,还提供了一些帮手函数,比如STDA sink,它也是接受一个lambda对象,然后返回是啥呢?
Dialogue: 0,0:23:54.08,0:23:55.84,Default,,0,0,0,,神奇了叫future
Dialogue: 0,0:23:55.84,0:24:01.57,Default,,0,0,0,,然后你这个这个lambda它不是返回的int类型嘛
Dialogue: 0,0:24:01.58,0:24:14.57,Default,,0,0,0,,就这里比如我下载哦,下载完哎呀,下载到一个404,然后就作为internet类型返回了,然后这里就返回了一个future
Dialogue: 0,0:24:14.57,0:24:16.55,Default,,0,0,0,,in什么叫future呢?
Dialogue: 0,0:24:16.55,0:24:23.33,Default,,0,0,0,,就代表这in的现在还没有,但是我向你保证它在未来会有
Dialogue: 0,0:24:23.33,0:24:35.20,Default,,0,0,0,,所以说呃think这个调用以后,这个是没有实际被执行的,它是在后台挂起了一个线程,在那儿默默的执行
Dialogue: 0,0:24:35.20,0:24:39.70,Default,,0,0,0,,直到你调用f right点get就要获取这个right
Dialogue: 0,0:24:39.72,0:24:50.17,Default,,0,0,0,,这个future里面我要获取这个未来的值,也就是我要穿越到未来了,这个时候才会进行阻塞
Dialogue: 0,0:24:50.17,0:25:01.81,Default,,0,0,0,,即使你这个分别来get,就相当于刚刚说的胶印,不过他是有返回值的,就更方便,明白吧?
Dialogue: 0,0:25:03.78,0:25:16.60,Default,,0,0,0,,就是嗯我觉得python它好像也有呃think和await这种这个可以理解为wait吧
Dialogue: 0,0:25:16.61,0:25:28.70,Default,,0,0,0,,总之这样的话你就可以先创建一个future,然后你干别的事了,你等到你需要的时候再去get就行
Dialogue: 0,0:25:29.24,0:25:36.50,Default,,0,0,0,,然后除了可以get会陷入等待这个线程执行完,你也可以用
Dialogue: 0,0:25:36.50,0:25:42.54,Default,,0,0,0,,wait, 就是你不返回它的子弹,你只是等待一下这种
Dialogue: 0,0:25:42.55,0:25:55.07,Default,,0,0,0,,没错,就是你等他资金完,就比如我输入害彭于斌,然后这时候这个交互完成,我开始陷入等待,然后等待完成
Dialogue: 0,0:25:55.08,0:26:09.66,Default,,0,0,0,,他说哦等待返回了,我可以获取下载结果是404,懂了吧,然后等待也可以等待一段时间
Dialogue: 0,0:26:09.66,0:26:12.55,Default,,0,0,0,,如果超过这个时间,我就
Dialogue: 0,0:26:12.55,0:26:16.02,Default,,0,0,0,,代表等的不耐烦了就不等了
Dialogue: 0,0:26:16.02,0:26:25.27,Default,,0,0,0,,这个时间也是用我们刚刚说的,crowner可以指定是比如1000毫秒就让他等一秒
Dialogue: 0,0:26:25.28,0:26:30.77,Default,,0,0,0,,然后如果一秒之中等到了,那我就说future is ready
Dialogue: 0,0:26:30.77,0:26:41.01,Default,,0,0,0,,如果一秒之中他还是没有还是没有下载完,他就说future not ready to music, 看我输了
Dialogue: 0,0:26:41.01,0:26:42.14,Default,,0,0,0,,嗨,朋于冰
Dialogue: 0,0:26:42.14,0:26:50.91,Default,,0,0,0,,然后他说等待等待AR下载才下载到70,没有等到未来,然后再去等一遍
Dialogue: 0,0:26:50.91,0:26:57.69,Default,,0,0,0,,我有个Y2循环再等一遍,然后这个时候等A未来完成了
Dialogue: 0,0:26:57.69,0:27:08.43,Default,,0,0,0,,所以说这个时候就不look掉,然后继续来调用,get获取这个下载的结果了,明白了吧?
Dialogue: 0,0:27:08.43,0:27:14.18,Default,,0,0,0,,是不是很方便,比直接调用实例的方便多了吧
Dialogue: 0,0:27:15.33,0:27:24.93,Default,,0,0,0,,然后它的返回类型是个枚举类型,就可以是really表示完毕,time out就放弃了
Dialogue: 0,0:27:25.41,0:27:35.27,Default,,0,0,0,,然后当然他就刚刚不是有sleep for和sleep until嘛,我们也有sleep
Dialogue: 0,0:27:35.27,0:27:43.61,Default,,0,0,0,,wait for wait until它的参数就是时间点了,看看有什么问题吗?
Dialogue: 0,0:27:46.40,0:27:49.42,Default,,0,0,0,,那没问题继续啊
Dialogue: 0,0:27:50.75,0:27:55.98,Default,,0,0,0,,然后刚刚说呃think会创建一个线程在后台执行
Dialogue: 0,0:27:55.98,0:28:01.76,Default,,0,0,0,,如果你不想用线程的话,你也可以用definitely的参数
Dialogue: 0,0:28:01.76,0:28:05.62,Default,,0,0,0,,这个launch是一个animal,也就是枚举
Dialogue: 0,0:28:05.62,0:28:13.33,Default,,0,0,0,,如果你用这不是枚举,反正就是用了这个以后,他就不会创建线程了
Dialogue: 0,0:28:13.33,0:28:22.43,Default,,0,0,0,,你看比如还有彭玉斌,你看刚刚是这样异步的执行,它在下载过程中我可以交互
Dialogue: 0,0:28:22.43,0:28:30.69,Default,,0,0,0,,但这样的话呢,它就相当于就创建了一个lambda,然后在这里直接调用而已
Dialogue: 0,0:28:30.69,0:28:43.76,Default,,0,0,0,,也就是说它其实只是纯粹的函数是上的一步,而不是我们多线程的那个硬度,可以用这个来实现惰性求值啊
Dialogue: 0,0:28:47.42,0:28:58.76,Default,,0,0,0,,然后如果你这deferred 不给的话,就默认是创建现成的,或者你在写lunch,这个也是创建现成的
Dialogue: 0,0:28:58.76,0:29:10.24,Default,,0,0,0,,然后如果你就不想让他帮你创建线程,而是自己来管理线程的话,可以用这个promise
Dialogue: 0,0:29:10.24,0:29:19.69,Default,,0,0,0,,就是创建现成的时候把promise 放在前面,才能让这个lambda 可以捕获这个promise
Dialogue: 0,0:29:20.02,0:29:26.54,Default,,0,0,0,,然后你在实例的里面点promise,点set volume,然后你的返回值
Dialogue: 0,0:29:26.54,0:29:35.62,Default,,0,0,0,,然后这样的话在外面就可以用promise 点get the future 来获取这个promise 的未来对象
Dialogue: 0,0:29:35.62,0:29:46.11,Default,,0,0,0,,然后你再再去交互,然后你事情干完再去get,这个原理是一样的,只不过a think 帮你包装好了
Dialogue: 0,0:29:46.11,0:29:52.59,Default,,0,0,0,,如果你要自己创建线程,你才需要用promise,知道了吧?
Dialogue: 0,0:29:56.24,0:30:02.68,Default,,0,0,0,,然后还有一些小贴士,就是future里面的类型可以是word
Dialogue: 0,0:30:02.69,0:30:10.01,Default,,0,0,0,,如果你这个就是没有返回值的话,这个lambda不是推断为wide吗?
Dialogue: 0,0:30:10.02,0:30:12.65,Default,,0,0,0,,那这个future也是wide了
Dialogue: 0,0:30:12.66,0:30:21.16,Default,,0,0,0,,然后future因为RAII的那个什么三无法则,导致它是没法被拷贝的
Dialogue: 0,0:30:21.16,0:30:30.55,Default,,0,0,0,,如果你想要前拷贝的话,你可以用shared future,这和shared point是一样的,都是前拷贝
Dialogue: 0,0:30:32.43,0:30:53.47,Default,,0,0,0,,同理,promise也可以作为word,它的set volume就没有参数类型了啊,看不清啊,541080P呀,怎么会看不清?
Dialogue: 0,0:30:57.27,0:31:06.23,Default,,0,0,0,,就挺怪,然后就是互斥量,就是怎么是互斥量呢?
Dialogue: 0,0:31:06.23,0:31:17.13,Default,,0,0,0,,相信很多同学都听说过这种经典案例,就是两个线程,再往一个victor里面推数据,这个就出问题了
Dialogue: 0,0:31:17.13,0:31:17.95,Default,,0,0,0,,为什么?
Dialogue: 0,0:31:17.95,0:31:27.20,Default,,0,0,0,,因为victor它里面不是有个指针嘛,然后这两个两个对象同时看到这个指针的长度
Dialogue: 0,0:31:27.21,0:31:38.10,Default,,0,0,0,,就比如我现在victor的长度为四,我现在一看到哦这个长度为四,而我现在需要额外追加一个数据
Dialogue: 0,0:31:38.11,0:31:46.00,Default,,0,0,0,,好,那我得把这个四的长度扩展到6,这样我才可以往里面写一个数据
Dialogue: 0,0:31:46.01,0:31:54.44,Default,,0,0,0,,然后他就把这个四的地址给废掉了,然后他又重新分配了一个长度为六的
Dialogue: 0,0:31:54.45,0:32:03.72,Default,,0,0,0,,然后先从二月同时看到了是,然后他说哦知道我要去free,然后这个时候什么事情发生了呢?
Dialogue: 0,0:32:03.72,0:32:10.38,Default,,0,0,0,,就是T一和T2同时看到这个长度为四,然后他们都去flink了一遍
Dialogue: 0,0:32:10.38,0:32:16.04,Default,,0,0,0,,然后这个时候他们就flink了两遍,从而就出现double free错误
Dialogue: 0,0:32:16.05,0:32:20.97,Default,,0,0,0,,所以说这个victor它在多线程环境下是不安全的
Dialogue: 0,0:32:21.48,0:32:26.43,Default,,0,0,0,,1080呀,我怎么了?
Dialogue: 0,0:32:26.43,0:32:34.23,Default,,0,0,0,,就是这个现象又叫做数据竞争data race就是两个人在抢一份数据
Dialogue: 0,0:32:34.24,0:32:48.97,Default,,0,0,0,,然后为了避免这个数据,我们需要让两个县城,要么县城一在push,要么县城二的push就不要两个人同时一起push,这怎么办呢?
Dialogue: 0,0:32:48.97,0:32:54.98,Default,,0,0,0,,就可以用我们的mute XZ,然后mute x就像厕所一样
Dialogue: 0,0:32:54.98,0:33:02.20,Default,,0,0,0,,你一旦把这个门上锁了以后,就不会有第二个人再进来了
Dialogue: 0,0:33:02.20,0:33:11.51,Default,,0,0,0,,然后当然你要记得及时解锁,然后你解锁以后,第二个才可以进来,知道了吧?
Dialogue: 0,0:33:14.00,0:33:18.21,Default,,0,0,0,,所以说mute x如果没锁定就可以上锁
Dialogue: 0,0:33:18.21,0:33:40.96,Default,,0,0,0,,如果就比如我线程一锁上了这个门,然后线程二也去锁门的话,他看到门已经锁了,他就知道啊,里面有同学在上厕所,然后他就等啊等啊等,直到县城一去解锁了以后,然后县城二就是说唉门开了吗?
Dialogue: 0,0:33:40.97,0:33:56.14,Default,,0,0,0,,我好进去了,所以说X作用就是保证lock和along a lock之间的这一段代码是同时只有一个人执行的,从而就不会出现两个人同时看到
Dialogue: 0,0:33:56.14,0:34:02.60,Default,,0,0,0,,哦,这个我是我把它飞了,我又把它飞了,就不会做错了吧
Dialogue: 0,0:34:07.57,0:34:13.11,Default,,0,0,0,,然后刚才看到这个有lock on lock,这个是不是很眼熟呢?
Dialogue: 0,0:34:13.11,0:34:16.00,Default,,0,0,0,,就有点像我们的门lock和free
Dialogue: 0,0:34:16.01,0:34:20.23,Default,,0,0,0,,然后如果你忘记free了,不是就出错了吗?
Dialogue: 0,0:34:20.23,0:34:23.14,Default,,0,0,0,,这里也一样,你忘记解锁了
Dialogue: 0,0:34:23.14,0:34:41.34,Default,,0,0,0,,那第二个同学就进一步来了,他还以为你还在里面,所以说这个时候也可以用我们的结构函数来做这个unlock的操作,然后将所视为一种资源,就可以变成我们的lock guard类
Dialogue: 0,0:34:41.34,0:34:49.52,Default,,0,0,0,,它的结构函数里会自动调用on lock,而它的构造函数里则是调用M7X点lock
Dialogue: 0,0:34:49.53,0:35:04.24,Default,,0,0,0,,这样的话你就算你忘记后面写什么东西了,只要离开这个花括号的范围,它就能够自动解锁,就不会粗心犯错了,对吧?
Dialogue: 0,0:35:04.80,0:35:12.14,Default,,0,0,0,,哎呀,就你知道对呀,就是就是比如这样
Dialogue: 0,0:35:16.51,0:35:22.26,Default,,0,0,0,,那么到这里为止,就是这里已经引用不了嘎的变量了
Dialogue: 0,0:35:22.26,0:35:31.09,Default,,0,0,0,,所以说到这里就解锁了,就相当于在这里a lot,但是这里就已经在解锁Y了
Dialogue: 0,0:35:31.09,0:35:38.76,Default,,0,0,0,,所以他看的是最外面一个花括号的大小,就是由这里花括号延伸到这里
Dialogue: 0,0:35:38.76,0:35:43.31,Default,,0,0,0,,所以说只有这一段是被尬的做了,知道吧?
Dialogue: 0,0:35:43.31,0:35:47.95,Default,,0,0,0,,有的时候可以弄一个空的花括号知道了吗?
Dialogue: 0,0:35:49.88,0:35:58.17,Default,,0,0,0,,对,如果你就是在这里引用这个变量引用不到了,那它就说明这里是解锁了
Dialogue: 0,0:35:59.06,0:36:05.30,Default,,0,0,0,,然后除了loggan 呢,还有一个unique lock,这有啥区别呢?
Dialogue: 0,0:36:05.30,0:36:08.74,Default,,0,0,0,,就是logger 的,有时候很死板
Dialogue: 0,0:36:08.74,0:36:13.62,Default,,0,0,0,,就比如说就比如说哎呦,操操操
Dialogue: 0,0:36:16.71,0:36:29.95,Default,,0,0,0,,然后比如这里是locker guard,然后家庭,然后我这时候做了什么事情,但是我这个地方其实括号延伸到这里
Dialogue: 0,0:36:29.95,0:36:35.86,Default,,0,0,0,,但是我在这里就想on lock,然后我这里又做了什么事情
Dialogue: 0,0:36:35.86,0:36:42.64,Default,,0,0,0,,那如果我想要让他提前释放,那这时候又得打一个括号就很烦
Dialogue: 0,0:36:42.64,0:36:57.48,Default,,0,0,0,,而且如果你这里面声明了,比如这里是S等于S S S的话,那你这里打一个括号,那那那这个外面就访问不了这个S变量了,就很烦
Dialogue: 0,0:36:57.48,0:37:07.61,Default,,0,0,0,,所以这个时候我们可以用unique unique lock,然后这个lock 就可以让你动态的去unlock 它
Dialogue: 0,0:37:07.61,0:37:15.73,Default,,0,0,0,,就比如这样你创建了一个以后,它可以提前unlock 这样这个print
Dialogue: 0,0:37:15.73,0:37:18.55,Default,,0,0,0,,f 就在之外了,知道吧?
Dialogue: 0,0:37:19.89,0:37:35.39,Default,,0,0,0,,李飞大哥哥秀是秀的呀,就他知道呀,就他知道性能有问题,两个地方代码可以一样
Dialogue: 0,0:37:35.39,0:37:43.97,Default,,0,0,0,,就是比如A同学进去是上厕所的,然后B同学进去是洗手的,但是他们都锁了门
Dialogue: 0,0:37:43.97,0:37:54.90,Default,,0,0,0,,那这个B同学进来看,他也不不管你是洗手还是在大大,他他他不会重复进入,就是哪怕你这里不一样
Dialogue: 0,0:37:54.90,0:38:09.74,Default,,0,0,0,,比如我是剖析一剖析二,它只能保证就是没有多个人同时在厕所里,你在厕所里磨阳工,他也不会管你,他只能保证厕所里同时只有一个人
Dialogue: 0,0:38:10.24,0:38:29.25,Default,,0,0,0,,然后unique lock 就是可以提前解锁,就不需要等这个括号完了了,就括号完了之前我想要解锁,或者我做完一件事临时解锁一下,我出去一下,然后又回来又上锁,这样也是可以的
Dialogue: 0,0:38:29.25,0:38:50.22,Default,,0,0,0,,但是它再上锁以后,它也能够保证在这个括号离开之后能够自动解锁,所以它又安全又自由度是不是很好啊,对呀,他只看是不是上锁了,他管理里面是在推A还是磨阳工呢?
Dialogue: 0,0:38:52.04,0:39:14.21,Default,,0,0,0,,然后就是unique lock,它有一个参数这种就是比如我在括号M T X后面逗号S T D default 就是就是有人说就是叫什么这构造函数没有名字,构造函数没有名字,但它其实可以有
Dialogue: 0,0:39:14.21,0:39:32.95,Default,,0,0,0,,就是你只需要给构造函数后面加一个任意的tag 类型,这样就能区分多个构造函数的不同重载,以重载的形式实现了具有多个构造函数的效果啊
Dialogue: 0,0:39:36.58,0:39:41.33,Default,,0,0,0,,一般都是一个锁,对应一个全局变量吧
Dialogue: 0,0:39:42.97,0:39:51.12,Default,,0,0,0,,然后default就是这里不是因为构造函数里面会调用M7x lock嘛
Dialogue: 0,0:39:51.12,0:40:00.80,Default,,0,0,0,,而如果的话,这时候其实是没有上锁的,只有你显示在调用garden lock它才能上锁
Dialogue: 0,0:40:01.37,0:40:04.25,Default,,0,0,0,,可以看到这里的default
Dialogue: 0,0:40:04.25,0:40:15.79,Default,,0,0,0,,其实default的一个变量,而这个类里面其实是空的,它唯一的价值就在于这个和别人不同
Dialogue: 0,0:40:16.20,0:40:25.43,Default,,0,0,0,,包括你可以看到这样的构造函数里也有个P4Y是constraint作为参数
Dialogue: 0,0:40:25.45,0:40:31.62,Default,,0,0,0,,这其实也是利用了tag类来区分不同的构造函数的方法
Dialogue: 0,0:40:33.03,0:40:50.74,Default,,0,0,0,,啊,hello啊,what然后如果你有多个对象,刚刚一个对象用一个mute x锁,如果你有两个,那就一个人配一把锁,就有两个厕所嘛,那就配两把锁呗
Dialogue: 0,0:40:50.74,0:40:56.27,Default,,0,0,0,,我推一的时候就上一的时候,推2的时候,上二的时候
Dialogue: 0,0:40:57.28,0:41:15.59,Default,,0,0,0,,当然你这里因为如果你这样写的话,两个盖子就同时上两个锁了,所以你可以用一个额外的空的花括号来限制作用域,才让他确定的时间能够解锁
Dialogue: 0,0:41:15.59,0:41:37.38,Default,,0,0,0,,然后还有一种嗯然后还有一种就是除了能够lock,lock是就是lock,不是说如果你看到卫生间上锁了,那你就站在门口等啊等啊,等等到他解锁之后,你再进去,然后胖手上
Dialogue: 0,0:41:37.38,0:41:50.53,Default,,0,0,0,,但是现在还有一种就叫try lock,就是我去看一下,我其实不急的,我不是很急,我去看一下厕所间,如果锁上了,我不等我直接回去
Dialogue: 0,0:41:50.53,0:41:55.18,Default,,0,0,0,,然后如果厕所间没锁,那我也进去把它锁上
Dialogue: 0,0:41:55.18,0:42:00.85,Default,,0,0,0,,所以这时候就是track的功能,他看看厕所间是不是锁了
Dialogue: 0,0:42:00.85,0:42:04.72,Default,,0,0,0,,如果锁我不等我不等他,我直接回来
Dialogue: 0,0:42:04.72,0:42:08.33,Default,,0,0,0,,然后并且告诉这个程序,我返回force
Dialogue: 0,0:42:08.33,0:42:18.13,Default,,0,0,0,,就比如这里第一次上锁,他先进去,然后说锁,因为初始化的mute x是不上锁的,所以第一次成功
Dialogue: 0,0:42:18.13,0:42:27.01,Default,,0,0,0,,然后第二次他锁了以后,他再去看一遍,是不是锁了,已经锁了,那他就再也受不了了
Dialogue: 0,0:42:27.01,0:42:29.71,Default,,0,0,0,,所以第二次就失败了嗯
Dialogue: 0,0:42:32.91,0:42:38.27,Default,,0,0,0,,然后还有一个try log,是看一眼卫生间锁了立马回去
Dialogue: 0,0:42:38.27,0:42:44.14,Default,,0,0,0,,现在我们还有一个try log for,就是它是介于try log和log之间
Dialogue: 0,0:42:44.14,0:42:51.54,Default,,0,0,0,,然后可以看到这个房门锁它会无限制等下去,而try lock form,它会去看一眼
Dialogue: 0,0:42:51.55,0:42:54.61,Default,,0,0,0,,如果锁了,我最多等500毫秒
Dialogue: 0,0:42:54.61,0:43:01.25,Default,,0,0,0,,如果超过500毫秒,这里面同学一点反应都没有,那我就不等了
Dialogue: 0,0:43:01.25,0:43:03.55,Default,,0,0,0,,所以这里还是失败
Dialogue: 0,0:43:03.55,0:43:11.97,Default,,0,0,0,,但是这个成功和失败之间其实有一段时间,因为他在等这个锁是不是释放了呀?
Dialogue: 0,0:43:12.46,0:43:18.33,Default,,0,0,0,,桶里它也有for for的版本和until的版本,until是时间点
Dialogue: 0,0:43:18.33,0:43:22.24,Default,,0,0,0,,他等到这个时间点还没结束失败
Dialogue: 0,0:43:22.25,0:43:27.29,Default,,0,0,0,,哦,那我跑了,我跑了以后他会返回force,知道吧?
Dialogue: 0,0:43:28.70,0:43:43.42,Default,,0,0,0,,然后其实这个unique lock也可以用,try to lock作为参数,它的功能就不是去调用在构造的时候调用lock它构造时会调用try lock
Dialogue: 0,0:43:43.42,0:43:58.29,Default,,0,0,0,,然后你可以就是这个try log,不是返回的chocolate false嘛,那它这里可以用one's log来判断,try lock的调用是不是成功了,是不是锁上了
Dialogue: 0,0:43:58.29,0:44:10.08,Default,,0,0,0,,然后还可以用garden的a dop log参数,就代表你之前MPX已经上锁了
Dialogue: 0,0:44:10.08,0:44:20.77,Default,,0,0,0,,然后这个和defer lock是相反的,是我之后再上锁,然后adopt是之前已经上锁,告诉他嗯
Dialogue: 0,0:44:24.08,0:44:30.78,Default,,0,0,0,,然后他其实就是unique lock本身其实也可以再套一层lock gun
Dialogue: 0,0:44:30.80,0:44:36.35,Default,,0,0,0,,也就是说,unique lock它本身不是也有lock和unlock吗?
Dialogue: 0,0:44:36.35,0:44:48.32,Default,,0,0,0,,mute x有lock and lock呀,所以说lock guard不就是调用一个对象的lock这个名字的函数和unlock这个名字的函数嘛
Dialogue: 0,0:44:48.33,0:45:05.57,Default,,0,0,0,,所以说任何具有这两个这两个函数名字的类都可以作为local garden的garden参数,你甚至可以自定义一个类,其具有lock和unlock也可以作为的参数
Dialogue: 0,0:45:05.91,0:45:12.82,Default,,0,0,0,,很神奇吧,这就是C加加中的鸭子类型,或者说叫概念
Dialogue: 0,0:45:12.82,0:45:24.45,Default,,0,0,0,,也就是只要具有这个名字的对象,呃,只要这就有这个名类函数名的对象,他就能够支持lock
Dialogue: 0,0:45:24.45,0:45:46.65,Default,,0,0,0,,and它就比那个虚函数更高效,而且它是解耦合的,也就是不需要先事先声明一个lock ball LOCKABLE的抽象接口,然后让你的类去继承这个接口,这样你只需要弄一个约定俗成
Dialogue: 0,0:45:46.65,0:45:52.09,Default,,0,0,0,,这两个名字你只要名字一样,它就能够接受上
Dialogue: 0,0:45:52.09,0:45:59.73,Default,,0,0,0,,但是这样也有问题,就是比如两个名字正好冲突,那就麻烦了
Dialogue: 0,0:45:59.74,0:46:22.82,Default,,0,0,0,,所以说这有利有弊,我推荐用unique lock上锁,我推荐用unique lock,就是这种这种呃上面这种就是什么参数,也没有unique log,直接mute x这种
Dialogue: 0,0:46:24.33,0:46:36.44,Default,,0,0,0,,然后这种今天的自由那个自由发挥课也会稍微讲一讲这嗯然后就是mute x老大难问题死锁
Dialogue: 0,0:46:36.46,0:46:51.81,Default,,0,0,0,,可以看到这里有两台购物车,然后一台购物车锁住了,另一台,另一台又锁住了这一台,他们互相锁住,这就导致他们永远也解不开了
Dialogue: 0,0:46:52.15,0:46:59.01,Default,,0,0,0,,就比如这个情况,然后我这里很特意的把它锁的顺序换了换
Dialogue: 0,0:46:59.01,0:47:09.04,Default,,0,0,0,,首先是第一个线程是先锁,一再锁2,然后第二个线程是先锁二再锁一,这会造成什么问题呢?
Dialogue: 0,0:47:09.04,0:47:26.73,Default,,0,0,0,,就是因为两个线程不是并行执行的嘛,然后他们这两个的执行顺序就不一定是一样的,就不一定我这个全执行完了才执行,你就这两个可能是交错在一起执行的
Dialogue: 0,0:47:26.73,0:47:37.82,Default,,0,0,0,,比如你考虑这种情况,由T执行的这一步,然后T2又执行了这一步,这是有可能的吧,他们同步执行了
Dialogue: 0,0:47:37.82,0:47:45.21,Default,,0,0,0,,然后T一先上了一的锁,然后T2上了2的锁,然后T又去上二的锁
Dialogue: 0,0:47:45.21,0:47:49.43,Default,,0,0,0,,这锁二的锁已经被二号线程给锁上了
Dialogue: 0,0:47:49.44,0:47:56.21,Default,,0,0,0,,那么T再去看二号线程哦,二号线程啊,不是二号那个厕所被锁住了
Dialogue: 0,0:47:56.21,0:48:04.45,Default,,0,0,0,,然后他又在这个二号厕所门口等啊等啊等,然后二号线程又去看了一眼一号厕所
Dialogue: 0,0:48:04.45,0:48:12.19,Default,,0,0,0,,然后他看着一号厕所,他想哎怎么锁上啊,里面肯定有人,然后他也等啊等啊等
Dialogue: 0,0:48:12.20,0:48:19.22,Default,,0,0,0,,没想到一号厕所里其实没有人,一号厕所里的人跑出来,在等二号厕所
Dialogue: 0,0:48:19.22,0:48:32.53,Default,,0,0,0,,然后二号厕所里的人跑出来,又在等一号厕所,然后他们就会无限自信的等下去,然后就导致这个资源永远释放不了,这就是死锁现象
Dialogue: 0,0:48:34.72,0:48:42.67,Default,,0,0,0,,然后可以看到运行之后这东西无限制卡死了,我只有用control c才能中断糖
Dialogue: 0,0:48:43.09,0:48:52.84,Default,,0,0,0,,所以说要解决死锁问题,就是你不要上了一个厕所,又去上另一个厕所嘛
Dialogue: 0,0:48:52.84,0:49:03.77,Default,,0,0,0,,所以就是你用完以后就先unlock再去饬另一个锁,就永远不要同时持有两个锁,这样就不会出现死锁了
Dialogue: 0,0:49:05.38,0:49:10.68,Default,,0,0,0,,当然有时候是需要用到多个锁的,这时候就可以用
Dialogue: 0,0:49:10.68,0:49:16.50,Default,,0,0,0,,刚才我其实是故意把这个顺序调一下,才会出现失所的
Dialogue: 0,0:49:16.50,0:49:25.85,Default,,0,0,0,,如果你这个顺序两边的顺序都是先所一再所按的话,那它就保证不会再出现死锁是问题
Dialogue: 0,0:49:26.21,0:49:31.45,Default,,0,0,0,,对呀,不就是厕所吗?
Dialogue: 0,0:49:32.01,0:49:34.55,Default,,0,0,0,,那还能咋比喻法?
Dialogue: 0,0:49:36.28,0:49:41.37,Default,,0,0,0,,就你保证这个每个线程中它的顺序一样
Dialogue: 0,0:49:41.38,0:49:58.38,Default,,0,0,0,,然后还有一个标准库提供的就用STD lock它保证不管你这两个顺序如何,它能够保证不会发生死锁,也就是保证它俩的顺序能够自动被排序
Dialogue: 0,0:49:58.38,0:49:59.73,Default,,0,0,0,,怎么排序呢?
Dialogue: 0,0:49:59.74,0:50:07.32,Default,,0,0,0,,有可能是根据内存中的地址吧,总之我不清楚,反正它能够避免死锁
Dialogue: 0,0:50:07.32,0:50:21.27,Default,,0,0,0,,然后你lock是用统一的一个函数去lock而解锁呢就正常的按照它的成员函数就可以,明白吧,就一次上两个数
Dialogue: 0,0:50:21.28,0:50:30.24,Default,,0,0,0,,然后就是STD lock on lock,这个不是就普通的,没有INI容易犯错
Dialogue: 0,0:50:30.24,0:50:36.90,Default,,0,0,0,,所以他们又发明了scope的lock,这个和lock一样,也是接受多个参数
Dialogue: 0,0:50:36.91,0:50:42.77,Default,,0,0,0,,这里我只用了两个,当然可以有三个四个多个都可以
Dialogue: 0,0:50:43.88,0:50:58.00,Default,,0,0,0,,然后score the log,它就能够自动在这个结构的时候调用所有的他们的这个unlock s嗯,明白吧?
Dialogue: 0,0:51:04.22,0:51:09.44,Default,,0,0,0,,看一下时间多少啊,45分钟啊
Dialogue: 0,0:51:10.61,0:51:17.73,Default,,0,0,0,,然后还有就是不要以为只有多个线程这种情况会死锁同一个线程
Dialogue: 0,0:51:17.73,0:51:26.64,Default,,0,0,0,,就比如我这里进了funk funk锁了MPX1,然后MPX1锁了以后又去调用另一个函数
Dialogue: 0,0:51:26.65,0:51:33.01,Default,,0,0,0,,这个函数也锁了,MPX1,也就是他锁了一遍,他又锁了一遍
Dialogue: 0,0:51:33.01,0:51:44.47,Default,,0,0,0,,那第二遍锁他其实看到的是自己的锁,也就是他还以为这里面是别的同学在上厕所,那他就呆乖乖的在那等
Dialogue: 0,0:51:44.47,0:51:57.84,Default,,0,0,0,,其实他等的是他自己,那也造成了咋办,所以说就发明了,就是说不要在这个锁里面再调用锁这个锁的函数
Dialogue: 0,0:51:57.85,0:52:09.37,Default,,0,0,0,,然后在这个在这个文档里面说明这个函数是需要对M7S1上锁的,你外面帮助我上锁,不要让我再来上了
Dialogue: 0,0:52:09.37,0:52:16.79,Default,,0,0,0,,当然还有另一种,如果你改变不了这个代码,你的code base太烂了,你改不了
Dialogue: 0,0:52:16.79,0:52:30.36,Default,,0,0,0,,所以你可以用the cosmic x这样你上锁了以后,如果是在同一个县城,你再电用上锁的话,它不会再去等待,而是会去修改一个计数器
Dialogue: 0,0:52:30.36,0:52:40.60,Default,,0,0,0,,当log的时候计数器会加1,然后按log的时候减1,只有这个计数器减到0的时候,才会真正的解锁
Dialogue: 0,0:52:40.60,0:52:43.41,Default,,0,0,0,,这就是the cos of mute x的功能
Dialogue: 0,0:52:44.10,0:52:59.15,Default,,0,0,0,,然后你需要等待一段时间的话,就刚才try lock form,不是有一个需要用time的mute x嘛,就是try log for是只有这个才有的
Dialogue: 0,0:52:59.15,0:53:09.76,Default,,0,0,0,,当然我们也提供了recall save time,the mute x就又递归又能够计明白了吧?
Dialogue: 0,0:53:13.55,0:53:21.12,Default,,0,0,0,,然后就是数据结构,我们要利用这个锁来实现一些多线程安全的结构
Dialogue: 0,0:53:22.02,0:53:33.52,Default,,0,0,0,,就刚刚不是说了,同时往一个数组里推会出错,所以我们可以分装一个多线程安全的M T victor 吧
Dialogue: 0,0:53:33.52,0:53:44.16,Default,,0,0,0,,就是也就是在他的pos 里面,先对他同样的这个锁先锁一下,然后再往里推推,完了再解锁
Dialogue: 0,0:53:44.16,0:53:54.22,Default,,0,0,0,,然后我们size 就是计算这个M T V它的大小,当然也是去调用M I这种叫做代理模式
Dialogue: 0,0:53:54.22,0:54:09.18,Default,,0,0,0,,就是不是有很多什么几十种设计模式嘛,这个代理模式是其中一种,它外面就先包裹一层上锁和解锁,然后这里就出了个问题
Dialogue: 0,0:54:09.18,0:54:17.81,Default,,0,0,0,,因为赛日它就是在原来victor 的定义里,它是个cos book,修改这个victor 内容的
Dialogue: 0,0:54:17.81,0:54:27.55,Default,,0,0,0,,而这里因为mute x 的上锁和解锁是要修改mute x 里的内容的,从而它必须必须不能scarce
Dialogue: 0,0:54:27.55,0:54:33.39,Default,,0,0,0,,而我的size 又为了兼容以前的代码,必须是count,对不对?
Dialogue: 0,0:54:33.39,0:54:35.34,Default,,0,0,0,,这时候怎么办呢?
Dialogue: 0,0:54:35.34,0:54:41.47,Default,,0,0,0,,就可以给mute x 上一个multiple,也就是mute x 是可以改变的
Dialogue: 0,0:54:41.47,0:54:57.06,Default,,0,0,0,,即使这里为const 他这个上面也不会加上,你就是给他开一个后门,这样的话就可以在cos 的函数里调用成员的非cos 的函数是不是很方便呢?
Dialogue: 0,0:54:59.06,0:55:05.93,Default,,0,0,0,,行呀行呀行呀,今天不是有那个自由讨论课吗?
Dialogue: 0,0:55:05.93,0:55:14.98,Default,,0,0,0,,到时候你不要走,等这节课上完不要走,然后就可以看到他这就能够正常编译了
Dialogue: 0,0:55:14.98,0:55:20.74,Default,,0,0,0,,然后然后我们可能以为啊mute x 可能就万事大吉了
Dialogue: 0,0:55:20.74,0:55:29.79,Default,,0,0,0,,唉,还有一个性能上可以提升的点,就刚刚不是说mute x 是厕所,不能多个人进入嘛
Dialogue: 0,0:55:29.79,0:55:54.71,Default,,0,0,0,,但是有时候有一个策略,就是如果你这个厕所不光是用来拉的,就是比如你这个厕所是世界上最后一个水源啊,就假如是这样,就假如全世界到处都没有水,也没有马桶,然后这是世界上最后一个马桶和世界上最后的一个水源
Dialogue: 0,0:55:54.71,0:56:03.14,Default,,0,0,0,,然后所有人都要来你这里喝水,然后他们喝水的时候其实是可以多个人一起喝的呢
Dialogue: 0,0:56:03.14,0:56:09.65,Default,,0,0,0,,通过卖完插进去,这样他们可以多个人同时并行的喝水就喝的快
Dialogue: 0,0:56:09.65,0:56:15.19,Default,,0,0,0,,但是拉的时候,因为人要坐在上面,船不能多个人一起拉
Dialogue: 0,0:56:15.19,0:56:25.30,Default,,0,0,0,,所以说而且还有一个额外的权利,就是在拉的同时,别人不能再喝水,因为这样就喝到他拉的水了,对吧?
Dialogue: 0,0:56:25.30,0:56:32.78,Default,,0,0,0,,就是我们说的张书记,张书记就是说啊可是那怎么迷人呢?
Dialogue: 0,0:56:33.11,0:56:40.98,Default,,0,0,0,,总之就是嗯总之就是有人在拉的时候,就不能有人在喝了然后
Dialogue: 0,0:56:40.98,0:56:45.78,Default,,0,0,0,,而如果有人在喝,是可以有多个人一起喝的
Dialogue: 0,0:56:45.78,0:56:50.06,Default,,0,0,0,,这其实就是和计算机里的读写相类似
Dialogue: 0,0:56:50.06,0:56:56.73,Default,,0,0,0,,如果我去多个人同时读一段带那个数据,这个其实可以共享
Dialogue: 0,0:56:56.73,0:57:11.42,Default,,0,0,0,,而如果我要改写这个数据,那么我就得独占了,不能多个人一起改写,也不能在我写的同时让别人读到我正在修改中的脏数据,知道吧?
Dialogue: 0,0:57:12.05,0:57:23.99,Default,,0,0,0,,所以说是一家家又发明了读写所,也就是N个人可以读没问题,或者只有一个人写没问题,或者没人读,也没人写
Dialogue: 0,0:57:23.99,0:57:29.19,Default,,0,0,0,,可以,但是不要有人读,有人写,也不要有多个人写
Dialogue: 0,0:57:29.19,0:57:49.15,Default,,0,0,0,,所以读写所就叫做写的mute X X然后如果我们只需要读的时候,我就告诉他我的锁是一个读锁,也就是我进厕所之前有一个管理员,我告诉他啊,管理员同学我是要进去喝的,不是进去拉的
Dialogue: 0,0:57:49.15,0:58:02.64,Default,,0,0,0,,然后管理员看了一下厕所,说看到里面已经有很多人在喝,但是没问题,因为喝是可以共享的,所以他又把这个同学说没问题进去吧
Dialogue: 0,0:58:02.64,0:58:08.88,Default,,0,0,0,,然后这时候又来了一个同学说管理员同学我是来进来拉的
Dialogue: 0,0:58:08.88,0:58:14.63,Default,,0,0,0,,然后管理员一看,唉,这个旁边这个里面全是在喝的人嘛
Dialogue: 0,0:58:14.63,0:58:23.13,Default,,0,0,0,,然后管理员说,哎,不行,人家正在喝呢,你等他们所有人都喝完了以后,你才能进去啦
Dialogue: 0,0:58:23.13,0:58:24.88,Default,,0,0,0,,是不是很神奇?
Dialogue: 0,0:58:25.65,0:58:31.50,Default,,0,0,0,,所以我们size 啊作为一个cost 的那个函数,它其实是只读
Dialogue: 0,0:58:31.50,0:58:41.89,Default,,0,0,0,,也就是只要读一下这个数据,这时候就可以用共享的那个lock 线的线的,就代表是读的那种方式
Dialogue: 0,0:58:41.89,0:58:49.61,Default,,0,0,0,,而直接的唠嗑则是写的那种方式,所以现在就可以很多个人一起上锁
Dialogue: 0,0:58:49.61,0:58:58.00,Default,,0,0,0,,然后我们可以看到这里也是正常运行出答案了,明白了吧?
Dialogue: 0,0:58:59.80,0:59:05.83,Default,,0,0,0,,然后就是刚才这样lock 不是有一个unique lock 吗?
Dialogue: 0,0:59:05.83,0:59:18.15,Default,,0,0,0,,我们共享首页具有shared lock,它会调用的是local shared 和unlocker 啊,有那个指挥调研,无后缀的lot
Dialogue: 0,0:59:18.48,0:59:27.77,Default,,0,0,0,,这样你只要指定这个类型为shared lock,这个就会变成一个R A I I的lock 和unlock shared
Dialogue: 0,0:59:28.12,0:59:34.23,Default,,0,0,0,,他同样支持death lock on lock 和unique log 差不多
Dialogue: 0,0:59:34.79,0:59:43.19,Default,,0,0,0,,同学们可以自己研究哦,可以理解,难以接受吗?
Dialogue: 0,0:59:43.80,0:59:48.77,Default,,0,0,0,,那你觉得应该找什么来类比,没办法嘛
Dialogue: 0,0:59:51.06,0:59:57.32,Default,,0,0,0,,然后就是我们的设计模式用来一种设计模式叫访问者
Dialogue: 0,0:59:57.32,0:59:58.96,Default,,0,0,0,,访问者是啥呢?
Dialogue: 0,0:59:58.96,1:00:06.04,Default,,0,0,0,,就是在我们数据面向数据的编程中,这个是用来存储数据的类
Dialogue: 0,1:00:06.04,1:00:11.76,Default,,0,0,0,,而这个X S则是用来访问数据类,他们会区分开来
Dialogue: 0,1:00:11.76,1:00:15.57,Default,,0,0,0,,这一种原因就是为了伺候这个锁
Dialogue: 0,1:00:15.57,1:00:24.84,Default,,0,0,0,,也就是刚才我们这样,就相当于每一个循环都调用一次上锁和解锁,这样非常低效
Dialogue: 0,1:00:24.84,1:00:30.28,Default,,0,0,0,,还有访问者呢,我们可以把它一次性上锁,为什么?
Dialogue: 0,1:00:30.28,1:00:50.11,Default,,0,0,0,,一次性上锁的呢,因为我们这个访问者里面持有的一个unique lock 类型,就是这访问者初始化的时候会先锁住这个mute x 然后再访问者这个X R结构的时候,才会去把这个锁解锁掉
Dialogue: 0,1:00:50.11,1:01:05.13,Default,,0,0,0,,也就是说它这里面只进行一次上锁一次解锁,从而它就合并了多次上锁,对性能有帮助,嗯,而且也能够分离这个实际对象的存储和访问
Dialogue: 0,1:01:05.13,1:01:20.44,Default,,0,0,0,,就是存储是外面一个类,然后访问是里面额外的一个类,可以通过X S函数来获取一个存储对象的访问者类的怎么办吧?
Dialogue: 0,1:01:21.80,1:01:26.92,Default,,0,0,0,,特别是在G P U上,这种访问者模式就很重要
Dialogue: 0,1:01:26.92,1:01:32.32,Default,,0,0,0,,因为你没办法把一个victor 给拷到这个G P U上
Dialogue: 0,1:01:32.32,1:01:45.18,Default,,0,0,0,,还有一个问题,就是比如我只想victor 中的一段,就比如string 的server S T R函数这种其实就是需要一个起止地址
Dialogue: 0,1:01:45.18,1:01:54.17,Default,,0,0,0,,而这个起始和结束的地址,它只是指针,它只是一个引用,而不实际存储类型
Dialogue: 0,1:01:54.17,1:01:57.94,Default,,0,0,0,,所以这个引用是可以被拷贝的
Dialogue: 0,1:01:57.94,1:02:08.67,Default,,0,0,0,,而那个存储类型的存呃不是存储类型里面,它的拷贝构造和移动构造都得遵循三无法则
Dialogue: 0,1:02:11.85,1:02:25.29,Default,,0,0,0,,所以说就是如果你知道open V D B,它其实也用了这种access 的模式,从而就可以减轻反复调用access 所造成的overhead
Dialogue: 0,1:02:25.29,1:02:28.79,Default,,0,0,0,,而且它也区分constant 和不constant
Dialogue: 0,1:02:28.79,1:02:38.10,Default,,0,0,0,,我们这里只用了非常,如果你感兴趣,也可以想办法怎么实现一个cost access
Dialogue: 0,1:02:38.10,1:02:46.47,Default,,0,0,0,,然后它这里是count 这个同学们自己研究,然后他的上锁可能就需要用读写锁
Dialogue: 0,1:02:47.04,1:03:03.93,Default,,0,0,0,,然后就是刚才说到互斥量,还互斥量适用于防止多个线程同时放一个数据,而条件变量它更像是一种信号量之类的东西
Dialogue: 0,1:03:03.93,1:03:11.35,Default,,0,0,0,,就是只有某个事件发生了之后,这个县城才能继续执行
Dialogue: 0,1:03:11.74,1:03:22.51,Default,,0,0,0,,就是比如这个首先是condition volleyball 类型,他他必须和mute x 一起用
Dialogue: 0,1:03:22.51,1:03:29.15,Default,,0,0,0,,就在这里,我们先嗯就是先向C V点wait lock
Dialogue: 0,1:03:29.15,1:03:33.76,Default,,0,0,0,,为什么要这个作为一个参数传进去呢?
Dialogue: 0,1:03:33.76,1:03:36.36,Default,,0,0,0,,这是到时候我们再说
Dialogue: 0,1:03:36.36,1:03:51.65,Default,,0,0,0,,总之剩余一点wait 会等待C V上发生一个事件,然后C V点notify ,one 则会通知这个有一个事件发生,可以认为它就是一个任务队列
Dialogue: 0,1:03:51.65,1:03:59.33,Default,,0,0,0,,我这里发生一个事件,然后这里就可以继续执行了,明白吗?
Dialogue: 0,1:04:02.99,1:04:06.96,Default,,0,0,0,,能不能明白老师先问好
Dialogue: 0,1:04:21.51,1:04:32.17,Default,,0,0,0,,有点像信号量,嗯,然后除了可以直接位置之外,它还可以选择性的被唤醒
Dialogue: 0,1:04:32.17,1:04:40.77,Default,,0,0,0,,就是比如第一次去唤醒的时候,ready 这变量为force,那他唤醒了之后wait 会检查
Dialogue: 0,1:04:40.77,1:04:53.58,Default,,0,0,0,,第二个,作为一个lambda 表达式传进去,它会求职这个lambda 表达式,然后它求出来这个表达式为false 的话,那它就继续等待
Dialogue: 0,1:04:53.58,1:04:57.05,Default,,0,0,0,,如果为真的话,它才会真正唤醒
Dialogue: 0,1:04:57.72,1:05:04.45,Default,,0,0,0,,所以说第一次去notify 它的时候,它不会被响应
Dialogue: 0,1:05:04.45,1:05:08.98,Default,,0,0,0,,然后第二次去notify 的时候,它才被唤醒了
Dialogue: 0,1:05:09.31,1:05:14.57,Default,,0,0,0,,可以看出它是进行了一个条件的求值
Dialogue: 0,1:05:14.57,1:05:24.99,Default,,0,0,0,,welcome, 我不知道呀,不懂,你们welcome welcome,应该和信号量蛮像的
Dialogue: 0,1:05:28.03,1:05:36.65,Default,,0,0,0,,然后条件变量刚才这是notify one就可以看出他可以多个人一起等一起通知
Dialogue: 0,1:05:36.67,1:05:40.84,Default,,0,0,0,,就比如我这里有三个县城都在等他
Dialogue: 0,1:05:40.85,1:05:46.70,Default,,0,0,0,,首先第一次我们是notify one,所以只唤醒了T一个人
Dialogue: 0,1:05:46.70,1:05:54.22,Default,,0,0,0,,然后我们又写notify,这个时候会把等这个条件变量所有人都唤醒
Dialogue: 0,1:05:55.45,1:05:57.71,Default,,0,0,0,,你就是可以看到这里
Dialogue: 0,1:05:57.71,1:05:59.72,Default,,0,0,0,,T3和T2都醒了
Dialogue: 0,1:06:06.80,1:06:20.43,Default,,0,0,0,,明白了吧,然后这就是为什么weight必须和一个lock连用之前不是说CVUC为啥要配一个锁呢?
Dialogue: 0,1:06:20.44,1:06:28.33,Default,,0,0,0,,这是因为我们有多个等待者的时候,这几个等待者可能是同时出现
Dialogue: 0,1:06:28.66,1:06:31.84,Default,,0,0,0,,教师就是队列嘛
Dialogue: 0,1:06:31.84,1:06:41.95,Default,,0,0,0,,然后为了保证七一执行以后,千万不会同时在执行必须T一解锁之后,T2才能够继续响应
Dialogue: 0,1:06:41.95,1:06:46.21,Default,,0,0,0,,所以就推出了mute x作为参数的想法了
Dialogue: 0,1:06:48.30,1:06:56.30,Default,,0,0,0,,然后所以就是如果你这里啊lock之后他才能够去执行其他的那个等待着
Dialogue: 0,1:06:56.32,1:07:05.94,Default,,0,0,0,,然后这个condition variable的一个用途就是实现生产者和消费者的这个这个队列的功能啊
Dialogue: 0,1:07:07.06,1:07:15.85,Default,,0,0,0,,就我们刚才这里不是能指定一个条件变量嘛,那这个条件变量就设为负值的大小
Dialogue: 0,1:07:15.85,1:07:23.61,Default,,0,0,0,,也就是如果这个负值队列不为空,就我们T一和T2是2个来吃饭的人
Dialogue: 0,1:07:23.61,1:07:31.61,Default,,0,0,0,,然后主线程是一个厨师,厨师往这个菜的队列里面推了一个数
Dialogue: 0,1:07:31.61,1:07:40.64,Default,,0,0,0,,然后他就得通知一下啊,客人我们来菜了,然后他就会唤醒其中一个在等的客人
Dialogue: 0,1:07:40.64,1:07:49.66,Default,,0,0,0,,然后这个客人正好是T1T1被唤醒了,然后七就开始从复制队列里取出食物
Dialogue: 0,1:07:49.66,1:08:03.41,Default,,0,0,0,,然后因为取出这个操作不是不安全嘛,所以必须在锁的环境里,然后是这个实物取完以后就可以解锁它
Dialogue: 0,1:08:03.41,1:08:09.47,Default,,0,0,0,,刚刚wait的这个锁呢,嗯是不是很方便?
Dialogue: 0,1:08:19.15,1:08:27.78,Default,,0,0,0,,太冷了,总之这里就是可以把它分装一下,变成一个通用的队列类
Dialogue: 0,1:08:27.78,1:08:37.99,Default,,0,0,0,,然后我们这里就只有一个类,然后这里是小鹏老师分装的,大家也可以在自己的项目里用哦
Dialogue: 0,1:08:37.99,1:08:46.59,Default,,0,0,0,,然后这里就直接push 进去,然后在push 里它能够自动自动等待这个条件变量成真
Dialogue: 0,1:08:46.59,1:09:13.80,Default,,0,0,0,,然后在破碎的时候也能够自动去通知这个消费者是不是很方便啊,然后就是condition variable,它只支持unique lock mute x 如果你需要支持其他类型的milk x 的话,可以用condition variable any 它能够支持female 采样甚至是自定义类型
Dialogue: 0,1:09:13.80,1:09:19.12,Default,,0,0,0,,这个其实用了一种和any 横向的类型擦除技术
Dialogue: 0,1:09:19.12,1:09:23.04,Default,,0,0,0,,待会儿我们自由课里面会讲一下
Dialogue: 0,1:09:23.04,1:09:32.00,Default,,0,0,0,,当然它也有wait for wait until 它是进行一个等待一段时间,你可以自己去看这个文档
Dialogue: 0,1:09:33.05,1:09:39.95,Default,,0,0,0,,然后这个我们是比较接近P实力的里面的这些这些对象
Dialogue: 0,1:09:39.95,1:09:45.43,Default,,0,0,0,,当然我们也可以从直接从硬件层面上去操作
Dialogue: 0,1:09:45.43,1:09:54.06,Default,,0,0,0,,就是硬件层面它提供就就是condition variable 和mute x 这都是操作系统提供的
Dialogue: 0,1:09:54.06,1:10:08.75,Default,,0,0,0,,还有一种硬件提供的,它是更高效,而且操作系统的这些root x 也是基于它来实现的,也就是我们的原子原子会这个原子指令
Dialogue: 0,1:10:08.75,1:10:11.63,Default,,0,0,0,,就比如就这个案例来说
Dialogue: 0,1:10:14.92,1:10:22.36,Default,,0,0,0,,总之就是有两个线程,他们同时在给count进行一个加一操作
Dialogue: 0,1:10:22.85,1:10:35.79,Default,,0,0,0,,然后你以为可能这个counter只有一个操作,不像刚才victor要释放啊,要重新分配啊,可能就不会冲突了嘛,其实会冲突
Dialogue: 0,1:10:37.26,1:10:42.44,Default,,0,0,0,,因为加一在编译了以后,其实变成了三条指令
Dialogue: 0,1:10:42.44,1:10:57.44,Default,,0,0,0,,它首先是读出content的值,就是一开始读出为零,然后读出这个值是先读到寄存器里的,然后寄存器加一以后加一以后再写入到count变量
Dialogue: 0,1:10:57.45,1:11:03.83,Default,,0,0,0,,所以一个C加加那个语言的句子变成三个汇编指令
Dialogue: 0,1:11:03.84,1:11:13.85,Default,,0,0,0,,然后你可能认为哎,那是不是我开了优化以后,编译器就能优化成这样只有一条指令呢?
Dialogue: 0,1:11:13.85,1:11:23.31,Default,,0,0,0,,哎想多了,你以为CPU这么笨,它其实就是这某86汇编是比较CISC的设计
Dialogue: 0,1:11:23.31,1:11:32.76,Default,,0,0,0,,而实际上C用内部它会把一条CISC指令给解码成好多好多条RNSA指令
Dialogue: 0,1:11:32.77,1:11:42.50,Default,,0,0,0,,然后它在里面就是可以对这些指令进行重排啊,甚至是乱序执行,甚至是并行执行啊
Dialogue: 0,1:11:42.50,1:11:57.52,Default,,0,0,0,,然后这样以后他就不可能还是原来这一条指令了,就有可能他实际被拆成三条,甚至更多条,就是没办法保证它是一个原子的操作
Dialogue: 0,1:11:59.73,1:12:10.89,Default,,0,0,0,,所以说为了解决这个呢,就可呃总之再说一遍,就是如果他不是原子操作,就会出现刚才说的这样同时执行
Dialogue: 0,1:12:10.90,1:12:14.20,Default,,0,0,0,,我T先读取了,然后T2也读取
Dialogue: 0,1:12:14.20,1:12:31.97,Default,,0,0,0,,他们读的都是零,然后这时候T一加上一变成一,T2,加上一又变成一,然后T一把加了之后的一写回康坦,然后T2又把加了之后的一写回康坦,就导致这两个都执行了
Dialogue: 0,1:12:31.97,1:12:37.05,Default,,0,0,0,,但最后content却只变成一,而没有变成我想要的2
Dialogue: 0,1:12:37.05,1:12:43.14,Default,,0,0,0,,所以就导致最后加起来,结果没有达到2万还是少了一点
Dialogue: 0,1:12:44.73,1:13:04.47,Default,,0,0,0,,唉,没人说话啦,还开着呀,怪吧,没人哦,所以说就可以用我们刚刚说的mute x这是最暴力的解决方法
Dialogue: 0,1:13:06.30,1:13:15.77,Default,,0,0,0,,然后他锁上以后就不会有多个人同时修改content,这样的确能够正确的变成2万
Dialogue: 0,1:13:15.77,1:13:21.81,Default,,0,0,0,,然后但是这个问题就是note x是操作系统来维护的
Dialogue: 0,1:13:22.14,1:13:34.97,Default,,0,0,0,,而操作系统你要让他去上锁,而会进入一下内核态,然后再回到用户态,甚至要切换到另一个县城,这个开销是很大的
Dialogue: 0,1:13:34.97,1:13:46.73,Default,,0,0,0,,如果你只是针对一个小小的硬的,这样做会严重影响效率,所以就可以用更加直观也更加高效的呃透明卡
Dialogue: 0,1:13:47.07,1:13:50.97,Default,,0,0,0,,它是有专门的硬件来支持的
Dialogue: 0,1:13:50.97,1:13:57.81,Default,,0,0,0,,就比如lock x and lock就是锁定总线,然后执行这个操作
Dialogue: 0,1:13:59.56,1:14:12.02,Default,,0,0,0,,CPU在识别到log指令的时候就会锁住内存总线,然后放弃他们那种很骚的优化方案,然后将该指令视为一个同步点
Dialogue: 0,1:14:12.03,1:14:25.01,Default,,0,0,0,,就是我前面如果就是前面的执行的指令都要开始进行一个commit,就把它同步到这个这个实际的内存中去,就不要再乱续了
Dialogue: 0,1:14:25.01,1:14:33.92,Default,,0,0,0,,就是前面执行了,不要再放到后面了,才能够保证该操作一定是原子的,也就是不可能
Dialogue: 0,1:14:33.92,1:14:39.26,Default,,0,0,0,,我加了当中,别人又去修改它,所以为什么要叫原子?
Dialogue: 0,1:14:39.26,1:14:40.79,Default,,0,0,0,,就是这个原因
Dialogue: 0,1:14:40.79,1:14:52.24,Default,,0,0,0,,因为原子一开始认为它是不可分割的,所以automatic又有不可分割的意思,所以原子操作就是不可分割的操作
Dialogue: 0,1:14:52.24,1:14:57.91,Default,,0,0,0,,就比如加等于不会被分割成等于和加两步操作哦
Dialogue: 0,1:15:01.09,1:15:14.45,Default,,0,0,0,,有这些有这些就是他有加等于减等于乘等于还有还有不是乘等于乘等于0
Dialogue: 0,1:15:14.45,1:15:27.89,Default,,0,0,0,,还有这个就是何等也就是安慰,还有或等安慰,或还有安慰,还有加加,还有减减,这些能够保证是那个原子的
Dialogue: 0,1:15:27.89,1:15:33.39,Default,,0,0,0,,当然你必须对atomic变量这样做才能是原子
Dialogue: 0,1:15:37.11,1:15:53.05,Default,,0,0,0,,明白了吧,待会儿会说全部的运算列表,然后可以要注意一下,就是你必须用加等于才是原子的,你用count等于count加1,这样就不行了
Dialogue: 0,1:15:53.06,1:16:01.58,Default,,0,0,0,,你看刚才是正常的2万,现在这样分离来加就加不满,从而可以看出它不原子了
Dialogue: 0,1:16:05.87,1:16:17.30,Default,,0,0,0,,就你看这种是不原子的这种能够原子这种也能原子同伴不要在这样分离,写了他怎么知道两个是同一对象呢?
Dialogue: 0,1:16:17.30,1:16:21.53,Default,,0,0,0,,编译器看不出呀,你必须这样写才能保证
Dialogue: 0,1:16:25.93,1:16:27.91,Default,,0,0,0,,好冷呀
Dialogue: 0,1:16:32.11,1:16:38.99,Default,,0,0,0,,然后除了能够用运算符重载之外,还可以直接调用函数名
Dialogue: 0,1:16:38.99,1:16:46.74,Default,,0,0,0,,比如store就代表是往atomic里赋值一个0,为什么负值还需要原子呢?
Dialogue: 0,1:16:46.74,1:16:52.76,Default,,0,0,0,,哎,需要的反正就是需要的,然后log也是需要原子的
Dialogue: 0,1:16:53.78,1:17:04.73,Default,,0,0,0,,然后fetch add呢就代表我们刚刚的这个加等于这样也能够保证他加出来的结果是正确的
Dialogue: 0,1:17:11.00,1:17:27.60,Default,,0,0,0,,哎呦,你又来了,你又来秀english 了,can you speak chinese? 然后非H I呢还有一个特点,就是它能够返回旧值,这个是加等于做不到的
Dialogue: 0,1:17:27.60,1:17:29.73,Default,,0,0,0,,H A的能返回旧值
Dialogue: 0,1:17:30.83,1:17:41.18,Default,,0,0,0,,所以这就是为什么它叫fetch ,and 它首先是fetch ,fetch 就是取先取出来旧址,然后再去增加
Dialogue: 0,1:17:41.18,1:17:42.37,Default,,0,0,0,,这个操作
Dialogue: 0,1:17:42.37,1:17:43.85,Default,,0,0,0,,是和二为一
Dialogue: 0,1:17:51.58,1:18:08.43,Default,,0,0,0,,当然这里和CONTA加加是等价的,所以HI的加1,然后它返回的是就此我们这个佳佳是在后面啊,佳佳在后面你学过C语言,他是返回家之前的值
Dialogue: 0,1:18:08.43,1:18:11.95,Default,,0,0,0,,然后我们这里就可以做什么事呢?
Dialogue: 0,1:18:11.96,1:18:17.25,Default,,0,0,0,,就两个线程并行的同时往一个数组里面累加数据
Dialogue: 0,1:18:18.69,1:18:28.11,Default,,0,0,0,,然后哪家数据你就得知道它加到哪里,然后加到哪里,就可以通过我们原子的FHI来实现
Dialogue: 0,1:18:28.71,1:18:39.71,Default,,0,0,0,,你看这种人啊欢笑english,总之这样加出来以后就可以看到它里面是有数据
Dialogue: 0,1:18:41.62,1:18:50.90,Default,,0,0,0,,然后除了CHI的会返回旧址,还有X千,它和store不一样,就在于它有返回值
Dialogue: 0,1:18:50.90,1:18:57.95,Default,,0,0,0,,它首先会把三写入content,然后把content你原来具有的词给取出来
Dialogue: 0,1:18:57.96,1:19:09.36,Default,,0,0,0,,就比如这样,我们context初始为零的,然后零我们往里面写入3,它返回的是旧值,旧值它就会说old等于0
Dialogue: 0,1:19:09.36,1:19:14.51,Default,,0,0,0,,就是我写入之后,它原来的值会返回到你的站
Dialogue: 0,1:19:20.68,1:19:30.69,Default,,0,0,0,,哎呦,这老哥哥老哥哥,总之X差距和store 的区别在于它能返回
Dialogue: 0,1:19:31.76,1:19:36.21,Default,,0,0,0,,然后你可能认为X差距已经很神奇了
Dialogue: 0,1:19:36.21,1:19:47.50,Default,,0,0,0,,更神奇的是compare x 前提是john 这么长的一个名字啊,而且参数还这么复杂,它是什么作用呢?
Dialogue: 0,1:19:47.50,1:19:54.32,Default,,0,0,0,,它就是比较比较你这个原子变量里的值是否等于O的值
Dialogue: 0,1:19:54.65,1:19:57.90,Default,,0,0,0,,如果等于的话,那就把三写入
Dialogue: 0,1:19:57.90,1:20:11.64,Default,,0,0,0,,如果不等于的话,那就不写入,同时还把原子变量值写入到old,就是它的第一个参数是个引用,它是引用了old
Dialogue: 0,1:20:11.64,1:20:15.00,Default,,0,0,0,,所以这里必须是一个左呃左值
Dialogue: 0,1:20:15.00,1:20:25.86,Default,,0,0,0,,然后它修改了以后可以看到第一次因为counter 变量为二嘛,而我的old 为一二不等于一,所以它不会写入
Dialogue: 0,1:20:30.27,1:20:41.44,Default,,0,0,0,,种子,然后再去读取一下的话,可以看到这里count也是没变还是R然后再再去比较一下,它是否等于二呢?
Dialogue: 0,1:20:41.44,1:20:47.41,Default,,0,0,0,,如果等于2的话,那就写入可以看到这次相等就是真了
Dialogue: 0,1:20:47.42,1:20:53.91,Default,,0,0,0,,然后真的是相等,然后相等就输出为Q这时候O的也是没变
Dialogue: 0,1:20:53.92,1:21:05.63,Default,,0,0,0,,因为它本来就是二嘛,然后这时候就可以看到它写入成功变成三了不等效
Dialogue: 0,1:21:07.23,1:21:11.36,Default,,0,0,0,,哎呀,错了就是
Dialogue: 0,1:21:17.13,1:21:21.24,Default,,0,0,0,,这个是可以的这个是可以的,知道吧?
Dialogue: 0,1:21:21.65,1:21:27.79,Default,,0,0,0,,这个就不可以错了这个
Dialogue: 0,1:21:32.51,1:21:38.72,Default,,0,0,0,,ok 的这个也ok 的,这个就不对了,因为它会返回修改后的词
Dialogue: 0,1:21:44.06,1:21:57.96,Default,,0,0,0,,所以你学过太极的话,就知道经常有这种automatic 的加一个变量,然后把加之前的值作为这个写入的索引
Dialogue: 0,1:22:01.25,1:22:19.32,Default,,0,0,0,,总之总之嗯然后如果你不理解C O M P X是啥,就可以看一下这个类,它是没有现成安全,但是它是方便你理解的伟大吗?
Dialogue: 0,1:22:19.32,1:22:38.05,Default,,0,0,0,,就你可以认为atomic 你是长这样的,可以看到X嵌入式先保存一下旧值,然后把新值写进去并返回旧,然后比较并替换,也是先比较
Dialogue: 0,1:22:38.05,1:22:45.06,Default,,0,0,0,,然后如果比较成功就写入比较不成功呢,就把原来的纸取出来
Dialogue: 0,1:22:45.41,1:22:56.60,Default,,0,0,0,,好,你学过太极的话,就知道它里面有一个atomic add,能够支持浮点数,很神奇吧
Dialogue: 0,1:22:56.60,1:23:06.32,Default,,0,0,0,,其实它不是直接调用了硬件的浮点加指令去lock,而是利用了complex 欠据配合浮点加
Dialogue: 0,1:23:06.66,1:23:15.04,Default,,0,0,0,,就是说C A S这个东西简称C A S,它可以实现基本任何的atomic 类型
Dialogue: 0,1:23:15.04,1:23:27.45,Default,,0,0,0,,就比如我我这个假如我automatic 支持加,我想要支持乘法,甚至支持指数幂运算,都可以基于C A S来实现
Dialogue: 0,1:23:27.45,1:23:29.76,Default,,0,0,0,,到时候我们再说
Dialogue: 0,1:23:34.02,1:23:40.19,Default,,0,0,0,,总之这个看就是正式课应该算是结束了吗?
Dialogue: 0,1:23:41.04,1:23:43.01,Default,,0,0,0,,有问题吗?
Dialogue: 0,1:23:44.48,1:23:47.43,Default,,0,0,0,,然后是今天的作业
Dialogue: 0,1:23:52.38,1:23:56.36,Default,,0,0,0,,今天的作业我看看已经发布
Dialogue: 0,1:24:06.89,1:24:11.76,Default,,0,0,0,,好到这里,那正式课就结束了,拜拜