This repository has been archived by the owner on Apr 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
/
05-concurrency101.slide
434 lines (263 loc) · 14.9 KB
/
05-concurrency101.slide
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
Concurrency 101
07.11.2018
http://fmi.golang.bg/
@fmi_golang
* Но преди това...
* Въпрос за мъфин #1
Какво е интерфейс?
- Тип от езика
- Съвкупност от дефиниции на методи
* Въпрос за мъфин #2
Как се създават нови типове?
- С ключовата дума `type`
- Ситанксиса е `type Name <oldType>`
* Въпрос за мъфин #3
type A struct {
a int
}
type B struct {
a float64
}
type C struct {
A
B
}
var c C
Какво е `c.a`?
Отговор:
- Компилатора ще ни каже грешка защото не знае кое `a` имаме предвид.
- `c.A.a` е int
- `c.B.a` е float64
* Въпрос за мъфин #4
По какъв начин показваме, че тип имплементира интерфейс?
- Никак
- Типовете имплементират интерфейси неявно
* Какво ще говорим днес?
- Конкурентност с/у Паралелизъм
- Кратка история на многопроцесорното програмиране
- go рутини
- Споделяне чрез комуникация
- channels
* Що е то конкурентност?
* Магически паралелизъм?
- Go е конкурентен
- ... следователно всичко ще работи паралелно! Yey!
- Не е точно така, може би бъркате дефинициите си.
* Конкурентност
- Конкурентност е да се *занимаваш* с много неща "едновременно"
- Имаме набор от независими процеси/задачи
- Могат да започват, работят и приключват в припокриващи се периоди
- Програмите в една операционна система, работеща на 1 процесорно ядро, са конкурентни
- В JavaScript има concurrency, без да е необходим паралелизъм
* Паралелизъм
- *Изпълнение* на различни неща едновременно
- Едновременно пресмятане на една задача от няколко процеса
* Конкурентност с/у Паралелизъм
- Когато говорим за конкурентност става въпрос за структурата на програмата
- Когато говорим за паралелизъм става въпрос за изпълнението ѝ
* Обяснение с малко повече gophers
- Лекция на Rob Pike по въпроса:
.link http://blog.golang.org/concurrency-is-not-parallelism
.link https://talks.golang.org/2012/waza.slide
* CPU скорост vs. производителност
.image https://i.stack.imgur.com/z94Of.png 400 _
- Moore's law
- А какво става, когато имаме много ядра?
* IO-bound vs. CPU-bound
- CPU-bound са програми, които главно зависят от време, прекарано в процесора
- IO-bound са програми, които главно зависят от време, прекарано в чакане на мрежата, паметта или диска
* Подходи за конкурентност
- Процеси
- Нишки (native & green)
- Актьори
- Мега умни компилатори?
А как синхронизираме различните задачи?
* В C ползват вилици
.code code/concurrency101/c_fork.c
- `fork` създава ново копие на програмата, която изпълняваме
- Всички ресурси и променливи запазват стойността си в процеса-дете
- След създаването на новия процес, всички промени са локални
- Все едно клонираме хора, за да вършим повече работа едновременно
* Синхронизация на вилици
.code code/concurrency101/c_fork_sync.c
- `execl` спира изпълнението на текущия процес и зарежда друг
- `waitpid` позволява на родителя да чака свършването на конкретно дете
* Предимства и недостатъци на fork
Против:
- Само за UNIX
- Създаването на нов процес е бавно и паметоемко
- Комуникацията между процеси е трудна - нямат обща памет
- Копира се памета на процеса
За:
- Копира се памета на процеса
- Стабилност
- Детето е независимо - ако омаже нещо, родителят няма да пострада
* В Go се правим на модерни
- Fork не се препоръчва
- Имаме по - добър начин, за него след малко
- Ако все пак искате чрез библиотеката `syscall` можете да вдигнете нов процес
- Не го правете, ако нямате много сериозна причина
* Нишки
- Много нишки живеят в един и същи процес
- Следователно имат достъп до една и съща памет
- Глобалните променливи са общи за нишките
- Създават се бързо и лесно
- Това е концепция в операционните системи
- Някои езици ги поддържат директно
- Други ги скриват зад ниво на абстрактност
* Нишки в C
.code code/concurrency101/c_threads.c /ticker/,
* Нишки в Python
.code code/concurrency101/python_threads.py /ticker/,/thread.start/
или
.code code/concurrency101/python_threads.py /class/,/thread.join/
* Goroutines
* Скучно
За да се съсредоточим върху това, което се опитваме да кажем ще дадем скучен пример.
.play code/concurrency101/boring.go /^func main/,
За конкурентноста таймингът е важен. Нека е малко по - непредвидим.
* Малко по - малко скучно
Ще сложим случайно време за сън.
.play code/concurrency101/less-boring.go /^func main/,
Скучната ни програма ще продължи да работи така до безкрайност. Като много скучна лекция, от която ви е неудобно да си тръгнете.
* Да я игнорираме
Скучната програма не заслужава вниманието ни, нека не я чакаме.
С `go` пускаме функция нормално, но пускащия няма нужда чака приключването й.
Пускаме goroutine.
.play code/concurrency101/go-less-boring.go /^func main/,
Когато main приключи програмата спира.
* Да я игнорираме малко по - малко
.play code/concurrency101/go-less-boring-sleep.go /^func main/,
Изпълнявахме main и скучната функция едновременно.
С края на main дойде и края на скучната функция.
* Какво е Goroutine
- Независимо изпълняваща се функция
- Практически безплатни са за създаване от към памет и процесорно време. Може да имате стотици хиляди в един процес
- Не е thread
- Зелени нишки
- Има умен scheduler, който мапва горутини към OS нишки
- Но ако мислите за тях като за много евтини нишки, няма да сте далеч от истината
- Дизайнът на езика и особено go рутините са много повлияни от Communicating sequential processes на C. A. R. Hoare
.link http://usingcsp.com/cspbook.pdf
* Вдъхновено от
- Последните няколко примера са безсрамно присвоени от лекция на Rob Pike. Интересна е, препоръчваме я.
.link http://www.youtube.com/watch?v=f6kdp27TYZs
- А сега да се върнем към нишки и goroutines
* Проблеми, свързани с нишки
От това, че имат една и съща памет, следва, че могат да достъпват едни и същи променливи
int i = 0
thread1 { i++ }
thread2 { i++ }
wait { thread1 } { thread2 }
print i
Тук `i` накрая може да бъде 1 или 2.
* Критични секции
- Части от кода, които могат да бъдат изпълнени само от една нишка/процес в даден момент, се наричат критични секции
- Те са основна част от многозадачното програмиране
- Има много похвати за реализирането на критични секции
- STM, Semaphores & Co., Message passing, Actors
В Go имаме Semaphores и Message passing
* Communicate by sharing vs. Share by communicating
- Може експлицитно да използваме "ключалки", за да ограничаваме едновременния достъп до споделена памет
- В Go също може да го правим, подобно на повечето mainstream езици
- Но в много ситуации в Go би било по-добре да споделяме памет чрез комуникация
- Може да предаваме (референции към) данни между различни горутини с помощта на канали
* Communicate by sharing vs. Share by communicating
.image assets/gopher-02-door-communication.jpg
* Channels
- Вграден тип, който се използва за комуникация между различни горутини
- Може да се използва и за синхронизация
- За тях има специален синтаксис
* Употреба на канали
- Инстанцират се с `make`, като се подава типа, който ще се пренася
- Този е за пренасяне на цели числа:
intChannel := make(chan int)
- Могат да бъдат буферирани и небуферирани
- По подразбиране са небуферирани
- Ето буфериран канал за пренасяне на string slices:
ch := make(chan []string, 100)
- В канал може да се изпраща и от него може да се получава
ch <- 64
read := <-ch
- Изпращането и получаването може да блокират докато някой "отсреща" не извърши "противоположната" операция
* IO в канал
Операциите по изпращане и получаване се изпълняват с оператора `<-`
- `chan`<-`someValue` изпраща по канала
- `someVar,`ok` = `<-chan` получава от канала
Simple demo:
.play code/concurrency101/channel-simple-demo.go /^func main/,
* Затваряне
Канал може да бъде затворен:
close(ch)
- Повече не може да бъде отворен
- Писането в него води до паника
- Четенето в него никога не блокира
- Може да прочетете всички вече буферирани стойности, ако каналът е бил буфериран
- След това четенето връща нулевата стойност за предавания тип и false като втори резултат
* Каналите са първокласни обекти в Go
- По канал може да пренасяте канал
c := make(chan chan int)
- Каналите могат да се подават като параметри на функции
func doSomething(input chan string) {
// do something
}
- Функциите могат да връщат канали като резултат.
func doSomethingElse() chan string {
result := make(chan string)
return result
}
* range
Помните ли как ви казахме, че `range` е нещо супер яко?
- Може да чете и от канали
- Блокира, докато не получи следващата стойност
- Излизаме от `for`, когато каналът бъде затворен
for val := range ch {
fmt.Printf("Recieved: %#v\n", val)
}
* Ограничени канали
- Каналите могат да бъдат само за четене (`<-chan`) или само за писане (`chan<-`)
- Това е по-полезно, отколкото звучи:
.play code/concurrency101/generator.go /func randomFeed/,
* Deadlock
.play code/concurrency101/deadlock.go /func main/,
* nil channel
Никога не използвайте неинициализиран канал!
- Писането в него блокира завинаги
package main
func main() {
var c chan string
c <- "ping" // deadlock
}
- Четенето от него... блокира завинаги
package main
import "fmt"
func main() {
var c chan string
fmt.Println(<-c) // deadlock
}
* Пример за синхронизация
.play code/concurrency101/synchronization.go /func main/,
- Не използвайте int или bool ако просто използвате канала за синхронизация.
- Използвайте struct{} за целта - безплатно от гледна точка на памет.
* По-сложен пример
var sem = make(chan struct{}, MaxOutstanding)
func init() {
for i := 0; i < MaxOutstanding; i++ {
sem <- struct{}{}
}
}
func handle(r *Request) {
<-sem
process(r)
sem <- struct{}{}
}
func Serve(queue chan *Request) {
for {
req := <-queue
go handle(req)
}
}
* Затваряне на канали
.play code/concurrency101/closing-channels.go
* Домашно