Add concurrency
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The free tier of the OpenWeatherMap API allows for 60 requests per minute.
|
||||||
|
// ∴ we limit our requests to 1 per second.
|
||||||
|
var interval = 1 * time.Second
|
||||||
|
|
||||||
|
func processCSV(
|
||||||
|
file *os.File, ztchan chan *ZipTemp,
|
||||||
|
) {
|
||||||
|
var (
|
||||||
|
limiter = time.Tick(interval)
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
reader := csv.NewReader(file)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-limiter
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go getZipTemp(&wg, record, ztchan)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(ztchan)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
title = "TOP TEN ZIP CODES BY HIGHEST CURRENT TEMPERATURE"
|
||||||
|
)
|
||||||
|
|
||||||
|
func display(ztchan chan []*ZipTemp, done chan struct{}) {
|
||||||
|
for {
|
||||||
|
locations, more := <-ztchan
|
||||||
|
if !more {
|
||||||
|
done <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen.
|
||||||
|
print("\033[H\033[2J")
|
||||||
|
|
||||||
|
// Print the title and top ten.
|
||||||
|
fmt.Println(title)
|
||||||
|
fmt.Printf(
|
||||||
|
"Last update: %s.\n\n",
|
||||||
|
time.Now().Format(time.RFC1123),
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, zt := range locations {
|
||||||
|
if zt == nil {
|
||||||
|
fmt.Printf("%d. No data\n", i+1)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"%d. %.02f°F %s (%s, %s)\n",
|
||||||
|
i+1,
|
||||||
|
zt.Temperature,
|
||||||
|
zt.ZipCode,
|
||||||
|
zt.City,
|
||||||
|
zt.State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ZipTemp struct {
|
||||||
|
City string
|
||||||
|
Latitude string
|
||||||
|
Longitude string
|
||||||
|
State string
|
||||||
|
Temperature float64
|
||||||
|
Time time.Time
|
||||||
|
ZipCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
file, err := os.Open("./zipcodes-50.csv")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Set up channels.
|
||||||
|
var (
|
||||||
|
done = make(chan struct{})
|
||||||
|
ztchan = make(chan *ZipTemp)
|
||||||
|
ttchan = make(chan []*ZipTemp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start the rank and display goroutines.
|
||||||
|
go rank(ztchan, ttchan)
|
||||||
|
go display(ttchan, done)
|
||||||
|
|
||||||
|
// Process the CSV.
|
||||||
|
processCSV(file, ztchan)
|
||||||
|
|
||||||
|
// Wait for the last display output.
|
||||||
|
<-done
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func rank(ztchan chan *ZipTemp, ttchan chan []*ZipTemp) {
|
||||||
|
var topTen = make([]*ZipTemp, 10)
|
||||||
|
|
||||||
|
for {
|
||||||
|
zt, more := <-ztchan
|
||||||
|
if !more {
|
||||||
|
close(ttchan)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newItem bool
|
||||||
|
for i, tt := range topTen {
|
||||||
|
if tt != nil && tt.Temperature > zt.Temperature {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've found a zip code with a higher temperature.
|
||||||
|
// Insert it into the top ten.
|
||||||
|
topTen = append(topTen, nil)
|
||||||
|
copy(topTen[i+1:], topTen[i:])
|
||||||
|
topTen[i] = zt
|
||||||
|
|
||||||
|
// Drop the last value.
|
||||||
|
topTen = topTen[:10]
|
||||||
|
|
||||||
|
newItem = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the top ten hasn't changed, process the next zip code.
|
||||||
|
if !newItem {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the top ten has changed, message the display goroutine.
|
||||||
|
// Send a copy, to prevent race conditions.
|
||||||
|
var newTopTen = make([]*ZipTemp, 10)
|
||||||
|
|
||||||
|
for i, tt := range topTen {
|
||||||
|
if tt == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newTopTen[i] = tt
|
||||||
|
}
|
||||||
|
|
||||||
|
ttchan <- newTopTen
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export OPEN_WEATHER_MAP_API_KEY="$(pass open-weather-map/api-key)"
|
||||||
|
|
||||||
|
go run ./*.go
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dwrz/talks/concurrency/pkg/weather"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getZipTemp(
|
||||||
|
wg *sync.WaitGroup, record []string, ztchan chan *ZipTemp,
|
||||||
|
) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Ignore malformed records.
|
||||||
|
if len(record) < 5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var zt = &ZipTemp{
|
||||||
|
City: record[3],
|
||||||
|
Latitude: record[1],
|
||||||
|
Longitude: record[2],
|
||||||
|
State: record[4],
|
||||||
|
ZipCode: record[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore zip codes we were not able to get the weather for.
|
||||||
|
weather, err := weather.GetGeo(zt.Latitude, zt.Longitude)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zt.Temperature = weather.Temp
|
||||||
|
zt.Time = time.Now()
|
||||||
|
|
||||||
|
ztchan <- zt
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
15336,40.260141,-79.992678,Gastonville,PA,Washington
|
||||||
|
64475,40.439298,-94.62247,Parnell,MO,Nodaway
|
||||||
|
62864,38.293269,-88.892273,Mount Vernon,IL,Jefferson
|
||||||
|
36049,31.754655,-86.291595,Luverne,AL,Crenshaw
|
||||||
|
30549,34.10601,-83.589021,Jefferson,GA,Jackson
|
||||||
|
49235,41.930577,-84.156209,Clayton,MI,Lenawee
|
||||||
|
21561,39.538568,-79.190791,Swanton,MD,Garrett
|
||||||
|
13165,42.892556,-76.882263,Waterloo,NY,Seneca
|
||||||
|
36473,31.466945,-87.044687,Range,AL,Conecuh
|
||||||
|
98181,47.432251,-121.803388,Seattle,WA,King
|
||||||
|
54874,46.568809,-91.935156,South Range,WI,Douglas
|
||||||
|
45840,41.015219,-83.664786,Findlay,OH,Hancock
|
||||||
|
97303,44.992731,-123.01672,Salem,OR,Marion
|
||||||
|
91709,34.839964,-115.967051,Chino Hills,CA,San Bernardino
|
||||||
|
68048,40.971961,-96.001244,Plattsmouth,NE,Cass
|
||||||
|
15327,39.764089,-79.937419,Dilliner,PA,Greene
|
||||||
|
38954,33.445684,-89.910076,Sidon,MS,Carroll
|
||||||
|
29581,34.000746,-79.102314,Nichols,SC,Horry
|
||||||
|
81504,39.123554,-108.589414,Grand Junction,CO,Mesa
|
||||||
|
39161,32.498294,-89.868563,Sandhill,MS,Rankin
|
||||||
|
85737,32.435158,-110.984499,Tucson,AZ,Pima
|
||||||
|
55365,45.200875,-93.888099,Monticello,MN,Wright
|
||||||
|
99176,47.12196,-117.433708,Thornton,WA,Whitman
|
||||||
|
72532,36.061757,-91.607603,Evening Shade,AR,Sharp
|
||||||
|
72145,35.277393,-91.732556,Searcy,AR,White
|
||||||
|
75662,32.410306,-94.793392,Kilgore,TX,Gregg
|
||||||
|
23272,37.524246,-77.493157,Richmond,VA,Richmond City
|
||||||
|
99023,47.557733,-117.183637,Mica,WA,Spokane
|
||||||
|
77802,30.620983,-96.319377,Bryan,TX,Brazos
|
||||||
|
05463,44.878094,-73.338597,Isle La Motte,VT,Grand Isle
|
||||||
|
27110,36.027482,-80.20728,Winston Salem,NC,Forsyth
|
||||||
|
33404,26.645895,-80.430269,West Palm Beach,FL,Palm Beach
|
||||||
|
84141,40.668068,-111.908297,Salt Lake City,UT,Salt Lake
|
||||||
|
32867,28.566338,-81.260818,Orlando,FL,Orange
|
||||||
|
78461,27.777,-97.463213,Corpus Christi,TX,Nueces
|
||||||
|
84123,40.65963,-111.919269,Salt Lake City,UT,Salt Lake
|
||||||
|
76299,33.20743,-97.116282,Roanoke,TX,Denton
|
||||||
|
24830,37.375246,-81.653889,Elbert,WV,Mcdowell
|
||||||
|
61914,39.638,-88.75367,Bethany,IL,Moultrie
|
||||||
|
13089,43.02143,-76.197701,Liverpool,NY,Onondaga
|
||||||
|
03774,44.077803,-72.014608,North Haverhill,NH,Grafton
|
||||||
|
01098,42.40494,-72.896133,Worthington,MA,Hampshire
|
||||||
|
14517,42.591883,-77.894474,Nunda,NY,Livingston
|
||||||
|
16743,41.881851,-78.457878,Port Allegany,PA,Mckean
|
||||||
|
10048,40.71254,-74.013289,New York,NY,New York
|
||||||
|
19375,39.901177,-75.750203,Unionville,PA,Chester
|
||||||
|
44608,40.665824,-81.592696,Beach City,OH,Stark
|
||||||
|
14883,42.203102,-76.471165,Spencer,NY,Tioga
|
||||||
|
34218,27.499515,-82.709871,Holmes Beach,FL,Manatee
|
||||||
|
75037,32.767268,-96.777626,Irving,TX,Dallas
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var balance int = 100
|
||||||
|
// var mu sync.Mutex
|
||||||
|
|
||||||
|
fmt.Println("Initial balance:", balance)
|
||||||
|
|
||||||
|
// Apply concurrent adjustments to the balance.
|
||||||
|
// The sum of these adjustments is 0.
|
||||||
|
// We should see no changes in balance at the end.
|
||||||
|
var adjustments = []int{
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
-8, -4, -2, 0, 2, 4, 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(adjustments))
|
||||||
|
|
||||||
|
for _, adjustment := range adjustments {
|
||||||
|
go func(adjustment int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// mu.Lock()
|
||||||
|
|
||||||
|
balance += adjustment
|
||||||
|
|
||||||
|
// mu.Unlock()
|
||||||
|
|
||||||
|
}(adjustment)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
fmt.Println("Final balance:", balance)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for (( ; ; )) do
|
||||||
|
go run ./main.go
|
||||||
|
printf "\n"
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cities = []string{
|
||||||
|
"Chicago",
|
||||||
|
"Dallas",
|
||||||
|
"Houston",
|
||||||
|
"Los Angeles",
|
||||||
|
"New York",
|
||||||
|
"Philadelphia",
|
||||||
|
"Phoenix",
|
||||||
|
"San Antonio",
|
||||||
|
"San Diego",
|
||||||
|
"San Jose",
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
printTemperatures(cities)
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"\nTook %v to get temperatures sequentially.\n\n",
|
||||||
|
time.Since(now),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset the timer.
|
||||||
|
now = time.Now()
|
||||||
|
|
||||||
|
printTemperaturesAsync(cities)
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"\nTook %v to get temperatures concurrently.\n\n",
|
||||||
|
time.Since(now),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/dwrz/talks/concurrency/pkg/weather"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Hardcoded padding for longest city name, Philadelphia.
|
||||||
|
message = "%-12s %.2f°F\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printTemperatures(cities []string) {
|
||||||
|
for _, city := range cities {
|
||||||
|
cityWeather, err := weather.GetCity(city)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(
|
||||||
|
"error getting weather for %s: %v\n", city, err,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(message, city, cityWeather.Temp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTemperaturesAsync(cities []string) {
|
||||||
|
var weatherReports = make(chan string, len(cities))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(cities))
|
||||||
|
|
||||||
|
for _, city := range cities {
|
||||||
|
go func(city string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
cityWeather, err := weather.GetCity(city)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(
|
||||||
|
"error getting weather for %s: %v\n",
|
||||||
|
city, err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherReports <- fmt.Sprintf(
|
||||||
|
message, city, cityWeather.Temp,
|
||||||
|
)
|
||||||
|
}(city)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(weatherReports)
|
||||||
|
|
||||||
|
for report := range weatherReports {
|
||||||
|
fmt.Print(report)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export OPEN_WEATHER_MAP_API_KEY="$(pass open-weather-map/api-key)"
|
||||||
|
|
||||||
|
go run ./*.go
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module github.com/dwrz/talks/concurrency
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 930 KiB |
|
After Width: | Height: | Size: 437 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 400 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
package weather
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Temperatures temperatures `json:"main"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type temperatures struct {
|
||||||
|
Temp float64 `json:"temp"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package weather
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseURL = "https://api.openweathermap.org/data/2.5/weather"
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = func() string {
|
||||||
|
key := os.Getenv("OPEN_WEATHER_MAP_API_KEY")
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
panic("missing open weather map api key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return key
|
||||||
|
}()
|
||||||
|
|
||||||
|
type Weather struct {
|
||||||
|
Temp float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCity(city string) (*Weather, error) {
|
||||||
|
params := fmt.Sprintf(
|
||||||
|
"appid=%s&q=%s&units=imperial", key, url.QueryEscape(city),
|
||||||
|
)
|
||||||
|
u := fmt.Sprintf("%s?%s", baseURL, params)
|
||||||
|
|
||||||
|
res, err := http.Get(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
|
return nil, fmt.Errorf("%s (%d)", body, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response response
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Weather{Temp: response.Temperatures.Temp}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGeo(lat, lng string) (*Weather, error) {
|
||||||
|
params := fmt.Sprintf(
|
||||||
|
"appid=%s&lat=%s&lon=%s&units=imperial", key, lat, lng,
|
||||||
|
)
|
||||||
|
url := fmt.Sprintf("%s?%s", baseURL, params)
|
||||||
|
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
|
return nil, fmt.Errorf("%s (%d)", body, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response response
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Weather{Temp: response.Temperatures.Temp}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,492 @@
|
|||||||
|
* 1. Concurrency
|
||||||
|
|
||||||
|
- David Wen Riccardi-Zhu
|
||||||
|
- Senior Software Engineer @ Good Uncle
|
||||||
|
- https://github.com/dwrz
|
||||||
|
- dwrz@dwrz.net
|
||||||
|
|
||||||
|
Slides and code: https://github.com/dwrz/talks/concurrency.
|
||||||
|
|
||||||
|
* 2. Why
|
||||||
|
|
||||||
|
You'll likely encounter it at work:
|
||||||
|
|
||||||
|
- Client requests to your backend
|
||||||
|
- Database and API requests made by your backend
|
||||||
|
- Frontend rendering
|
||||||
|
|
||||||
|
* 3. Why
|
||||||
|
|
||||||
|
Performance Benefits
|
||||||
|
|
||||||
|
- I/O versus CPU bound applications.
|
||||||
|
- Typically outweigh gains from optimized data structures and algorithms.
|
||||||
|
- Hardware limitations.
|
||||||
|
- Rate of single core performance improvement is slowing.
|
||||||
|
- More cores.
|
||||||
|
|
||||||
|
* 4. Why
|
||||||
|
|
||||||
|
Expressive
|
||||||
|
|
||||||
|
* 5. Why
|
||||||
|
|
||||||
|
Difficult to do correctly.
|
||||||
|
|
||||||
|
Easy to do incorrectly.
|
||||||
|
|
||||||
|
* 6. Goals
|
||||||
|
|
||||||
|
- Give you a rough sense of what concurrency is.
|
||||||
|
- Provide context to help you understand how it's made possible.
|
||||||
|
- Practical demonstration, to expose some of the trade-offs.
|
||||||
|
|
||||||
|
* 7. Three Parts
|
||||||
|
|
||||||
|
1. Fundamentals
|
||||||
|
2. Application
|
||||||
|
3. Infrastructure
|
||||||
|
|
||||||
|
* 8. Definition
|
||||||
|
|
||||||
|
/Concurrency/
|
||||||
|
1. A running together in place or time.
|
||||||
|
2. Simultaneous occurrence.
|
||||||
|
|
||||||
|
OK starting point, but:
|
||||||
|
|
||||||
|
Time, and simultaneity, is /complicated/.
|
||||||
|
|
||||||
|
A second is a duration, not an instant.
|
||||||
|
|
||||||
|
Two things can happen in the same second and not be simultaneous.
|
||||||
|
|
||||||
|
In computer science, this nuance matters.
|
||||||
|
|
||||||
|
* 9. Time
|
||||||
|
|
||||||
|
|----------------------+----------------+----------------|
|
||||||
|
| System Event | Actual Latency | Scaled Latency |
|
||||||
|
|----------------------+----------------+----------------|
|
||||||
|
| One CPU cycle | 0.4 ns | 1 s |
|
||||||
|
| Level 1 cache access | 0.9 ns | 2 s |
|
||||||
|
| Level 2 cache access | 2.8 ns | 7 s |
|
||||||
|
| Level 3 cache access | 28 ns | 1 min |
|
||||||
|
| Main memory access | ~100 ns | 4 min |
|
||||||
|
| NVMe SSD I/O | ~25 μs | 17 hrs |
|
||||||
|
| SSD I/O | 50–150 μs | 1.5–4 days |
|
||||||
|
| Rotational disk I/O | 1–10 ms | 1–9 months |
|
||||||
|
| Internet: SF to NYC | 65 ms | 5 years |
|
||||||
|
| Internet: SF to HK | 141 ms | 11 years |
|
||||||
|
|----------------------+----------------+----------------|
|
||||||
|
|
||||||
|
Source: [[https://www.prowesscorp.com/computer-latency-at-a-human-scale/]]
|
||||||
|
|
||||||
|
* 10. Example
|
||||||
|
|
||||||
|
- Get the current temperature of ten most populous cities in the US.
|
||||||
|
|
||||||
|
- First, /sequentially./
|
||||||
|
- Then, /concurrently/.
|
||||||
|
|
||||||
|
[[file:cmd/temperatures/run.sh]]
|
||||||
|
|
||||||
|
* 11. Example
|
||||||
|
|
||||||
|
[[file:images/concurrent-requests.jpg]]
|
||||||
|
|
||||||
|
* 12. Cooking Recipes
|
||||||
|
|
||||||
|
- Step-by-step
|
||||||
|
- Concurrent
|
||||||
|
- Pre-heat oven
|
||||||
|
- Boil water
|
||||||
|
- Timers
|
||||||
|
|
||||||
|
* 13. Multitasking
|
||||||
|
|
||||||
|
Computers can "simultaneously" work on:
|
||||||
|
|
||||||
|
- Different /parts/ of a recipe
|
||||||
|
- Different /recipes/
|
||||||
|
|
||||||
|
* 14. htop
|
||||||
|
|
||||||
|
* 15. How?
|
||||||
|
|
||||||
|
- Operating System's *Scheduler*
|
||||||
|
- Hardware (interrupts)
|
||||||
|
- Not just multiple cores!
|
||||||
|
|
||||||
|
* 16. NB: Concurrency != Parallelism
|
||||||
|
|
||||||
|
First consumer multi-core CPUs released in mid-2000's.
|
||||||
|
|
||||||
|
Even before that, computers were able to multitask with one core.
|
||||||
|
|
||||||
|
* 17. Cooperative Scheduling
|
||||||
|
[[file:images/cooperative-scheduling.jpg]]
|
||||||
|
|
||||||
|
* 18. Preemptive Scheduling
|
||||||
|
[[file:images/preemptive-scheduling.jpg]]
|
||||||
|
|
||||||
|
* 19. Preemptive Scheduling
|
||||||
|
[[file:images/preemptive-scheduling-tree.jpg]]
|
||||||
|
|
||||||
|
* 20. Single Core
|
||||||
|
[[file:images/single-core-concurrency.jpg]]
|
||||||
|
|
||||||
|
* 21. Multi Core
|
||||||
|
[[file:images/preemptive-scheduling-multi-core.jpg]]
|
||||||
|
|
||||||
|
* 22. Concurrency versus Parallelism
|
||||||
|
|
||||||
|
- Parallelism :: performing computations at the same moment.
|
||||||
|
- Multiple cores.
|
||||||
|
- Multiples computers.
|
||||||
|
- Kind of like having multiple cooks in a kitchen.
|
||||||
|
|
||||||
|
- Concurrency :: performing computations over inter-leaving, non-sequential time periods.
|
||||||
|
- Single or multi-core.
|
||||||
|
|
||||||
|
* 23. Processes and Threads
|
||||||
|
|
||||||
|
Computers can "simultaneously" work on:
|
||||||
|
|
||||||
|
- Different /parts/ of a recipe
|
||||||
|
- Different /recipes/
|
||||||
|
|
||||||
|
- Process :: a program in execution (recipe).
|
||||||
|
- Thread :: smallest sequence of programmed instructions that can be managed independently by a scheduler (part of a recipe).
|
||||||
|
|
||||||
|
* 24. Processes and Threads
|
||||||
|
|
||||||
|
Processes
|
||||||
|
- Have their own memory space
|
||||||
|
- Instructions
|
||||||
|
- Constants
|
||||||
|
- Stack
|
||||||
|
- Heap
|
||||||
|
- Start out with one thread (the /main/ thread).
|
||||||
|
- Can fork -- create a child process.
|
||||||
|
- Inter-Process Communication (IPC)
|
||||||
|
|
||||||
|
* 25. Processes and Threads
|
||||||
|
|
||||||
|
Threads (a.k.a, lightweight processes)
|
||||||
|
|
||||||
|
- Share:
|
||||||
|
- Memory
|
||||||
|
- Instructions
|
||||||
|
- Open files (descriptors)
|
||||||
|
- Signals and signal handlers
|
||||||
|
- Etcetera...
|
||||||
|
|
||||||
|
- Less resource intensive than processes.
|
||||||
|
|
||||||
|
* 26. Processes and Threads
|
||||||
|
|
||||||
|
[[file:images/process-vs-thread.jpg]]
|
||||||
|
|
||||||
|
Source: https://stackoverflow.com/questions/16354460/forking-vs-threading/16354658
|
||||||
|
|
||||||
|
* 27. htop
|
||||||
|
|
||||||
|
Press ~H~ to show threads.
|
||||||
|
|
||||||
|
* 28. Languages and Runtimes
|
||||||
|
|
||||||
|
- ~C~, ~C++~, ~Rust~ allow for fine-grained control of processes and threads.
|
||||||
|
|
||||||
|
- ~JavaScript~: single-threaded (for your code), runtimes (Chrome, nodejs) implement concurrency via an event loop.
|
||||||
|
|
||||||
|
- ~Go~: runtime multi-threading, multiplexes goroutines onto threads.
|
||||||
|
|
||||||
|
* 29. nodejs
|
||||||
|
|
||||||
|
[[file:images/nodejs-event-loop.png]]
|
||||||
|
|
||||||
|
Source: [[https://medium.com/preezma/node-js-event-loop-architecture-go-deeper-node-core-c96b4cec7aa4]]
|
||||||
|
|
||||||
|
* 30. Go
|
||||||
|
|
||||||
|
[[file:images/go-scheduler.png]]
|
||||||
|
|
||||||
|
Source: https://freethreads.net/2019/01/23/go-runtime-scheduler-design-internals/
|
||||||
|
|
||||||
|
* 31. Quick Review
|
||||||
|
|
||||||
|
- Sequential versus Concurrent Steps
|
||||||
|
- Compare with recipes.
|
||||||
|
- Concurrency versus Parallelism
|
||||||
|
- Multiple cooks.
|
||||||
|
- Operating System Scheduler
|
||||||
|
- Processes and Threads
|
||||||
|
- Languages and Runtimes
|
||||||
|
|
||||||
|
* 32. Part 2: Using Concurrency
|
||||||
|
|
||||||
|
Typical use cases:
|
||||||
|
- Network I/O
|
||||||
|
- Requests between clients and servers.
|
||||||
|
- Requests to a database.
|
||||||
|
- Requests between microservices.
|
||||||
|
- Requests to external APIs.
|
||||||
|
|
||||||
|
- User interfaces
|
||||||
|
- Capturing user input
|
||||||
|
- Rendering output
|
||||||
|
|
||||||
|
- Similar, but independent computations.
|
||||||
|
|
||||||
|
- Solving problems that are best expressed with concurrency.
|
||||||
|
|
||||||
|
* 33. JavaScript
|
||||||
|
|
||||||
|
- Callbacks
|
||||||
|
- Promises
|
||||||
|
- async/await
|
||||||
|
|
||||||
|
What is the output of the following? Why?
|
||||||
|
#+begin_src javascript
|
||||||
|
console.log('start');
|
||||||
|
|
||||||
|
setTimeout(() => console.log('callback 1'), 0);
|
||||||
|
|
||||||
|
console.log('middle');
|
||||||
|
|
||||||
|
setTimeout(() => console.log('callback 2'), 0);
|
||||||
|
|
||||||
|
console.log('end');
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* 34. Go
|
||||||
|
|
||||||
|
1. Shared Memory with Mutex (Mutual Exclusion)
|
||||||
|
- Pitfall: synchronization, debugging.
|
||||||
|
2. Message Passing (Channels and Goroutines)
|
||||||
|
- Pitfall: resources usage, complexity.
|
||||||
|
|
||||||
|
* 35. Mutex Example
|
||||||
|
Concurrent updates to a bank account balance.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
[[file:cmd/mutex/main.go]]
|
||||||
|
|
||||||
|
Run: [[file:cmd/mutex/run.sh]]
|
||||||
|
- Without mutex.
|
||||||
|
- With mutex.
|
||||||
|
|
||||||
|
* 36. Data Races
|
||||||
|
[[file:images/data-race.jpg]]
|
||||||
|
|
||||||
|
* 37. Mutex
|
||||||
|
|
||||||
|
/Mutual Exclusion/
|
||||||
|
|
||||||
|
- Purpose: protect a critical section of code.
|
||||||
|
- Effect: serialization.
|
||||||
|
|
||||||
|
* 38. Race Detector
|
||||||
|
|
||||||
|
- Without mutex.
|
||||||
|
- With mutex.
|
||||||
|
|
||||||
|
* 39. Tradeoffs: Validating Correctness, Testing, Debugging
|
||||||
|
|
||||||
|
- Non-deterministic behavior
|
||||||
|
- Works fine on one run, fatal bug in another.
|
||||||
|
- Up to how the scheduler(s) order things at execution time.
|
||||||
|
- Concealed bugs
|
||||||
|
- Heisenbugs
|
||||||
|
|
||||||
|
* 40. Message Passing
|
||||||
|
|
||||||
|
Proverbs:
|
||||||
|
#+begin_quote
|
||||||
|
Don't communicate by sharing memory, share memory by communicating.
|
||||||
|
|
||||||
|
Channels orchestrate, mutexes serialize.
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
* 41. Requirements
|
||||||
|
|
||||||
|
- Read CSV
|
||||||
|
- 50 in our example
|
||||||
|
- Full list is ~42,000
|
||||||
|
|
||||||
|
- Get temperature with OpenWeatherMap API
|
||||||
|
- 60 requests per minute
|
||||||
|
- Need to rate limit our API requests.
|
||||||
|
|
||||||
|
- Display running list of top ten temperatures.
|
||||||
|
|
||||||
|
* 42. Concurrency Hallmarks
|
||||||
|
|
||||||
|
- Repeatedly processing similar data (lines of CSV)
|
||||||
|
- Network I/O
|
||||||
|
- UI I/O
|
||||||
|
|
||||||
|
* 43. Message Passing Example
|
||||||
|
|
||||||
|
Run: [[file:cmd/csp/run.sh]]
|
||||||
|
|
||||||
|
* 44. Code Walkthrough
|
||||||
|
|
||||||
|
[[file:cmd/csp/main.go]]
|
||||||
|
|
||||||
|
* 45. Channels and Goroutines
|
||||||
|
[[file:images/message-passing-example-diagram.jpg]]
|
||||||
|
|
||||||
|
* 46. Channels and Goroutines
|
||||||
|
[[file:images/go-scheduler.jpg]]
|
||||||
|
|
||||||
|
* 47. Scaling
|
||||||
|
|
||||||
|
What if:
|
||||||
|
- We wanted to process the entire list of 42,000 zip codes?
|
||||||
|
- We had access to the Enterprise tier of the Open Weather Map API (200,000 requests per minute).
|
||||||
|
|
||||||
|
Could we just get rid of the limiter?
|
||||||
|
|
||||||
|
* 48. Resources
|
||||||
|
|
||||||
|
Probably not, because:
|
||||||
|
|
||||||
|
- OS limit on open files (~ulimit~)
|
||||||
|
- Ask me how I know...
|
||||||
|
- Server load
|
||||||
|
- Network load
|
||||||
|
- CPU
|
||||||
|
- Ranking goroutine becomes a chokepoint.
|
||||||
|
- Data structures and algorithms could help here.
|
||||||
|
- Goroutines are cheap, but not free.
|
||||||
|
|
||||||
|
* 49. Possible Solutions
|
||||||
|
|
||||||
|
- Use a semaphore to limit concurrency.
|
||||||
|
- Buffers
|
||||||
|
|
||||||
|
* 50. Designing and Managing Concurrency
|
||||||
|
|
||||||
|
- Size of input matters.
|
||||||
|
- Resources
|
||||||
|
- Pipelines
|
||||||
|
- Chokepoints
|
||||||
|
- Backpressure
|
||||||
|
- Complexity
|
||||||
|
|
||||||
|
* 51. Quick Review
|
||||||
|
|
||||||
|
Techniques for implementing and managing concurrency:
|
||||||
|
- Event loop
|
||||||
|
- Mutexes
|
||||||
|
- Communication (channels and goroutines)
|
||||||
|
|
||||||
|
Concurrency Pitfalls:
|
||||||
|
- Synchronization
|
||||||
|
- Resource usage
|
||||||
|
- Debugging
|
||||||
|
- Complexity
|
||||||
|
- Need to think differently about concurrent code.
|
||||||
|
|
||||||
|
* 52. Part 3: Infrastructure Concurrency
|
||||||
|
|
||||||
|
Concurrent applications running concurrently on distributed computers.
|
||||||
|
|
||||||
|
- Client Application
|
||||||
|
- Backend Application
|
||||||
|
- Databases
|
||||||
|
- Host and Network Infrastructure
|
||||||
|
|
||||||
|
* 53. Student Context
|
||||||
|
|
||||||
|
- Simple architectures:
|
||||||
|
- Monolithic
|
||||||
|
- Single DB
|
||||||
|
- Few concurrent users
|
||||||
|
|
||||||
|
* 54. Professional Context
|
||||||
|
|
||||||
|
- Complex, sometimes messy architectures.
|
||||||
|
- Containers
|
||||||
|
- Queues
|
||||||
|
- Workers
|
||||||
|
- Tech Debt
|
||||||
|
- Multiple Servers
|
||||||
|
- Microservices
|
||||||
|
- Multiple datastores
|
||||||
|
- Distributed databases
|
||||||
|
- Many concurrent users
|
||||||
|
- Running longer (more vulnerable to failures, downtime)
|
||||||
|
|
||||||
|
* 55. Example
|
||||||
|
[[file:images/infrastructure-data-race.jpg]]
|
||||||
|
|
||||||
|
* 56. Review
|
||||||
|
|
||||||
|
- What concurrency is
|
||||||
|
- Sequential versus concurrent
|
||||||
|
- Comparison with recipes
|
||||||
|
- Compared to parallelism
|
||||||
|
- How it is implemented
|
||||||
|
- Operating System Scheduler and Interrupts
|
||||||
|
- Languages and Runtimes
|
||||||
|
|
||||||
|
* 57. Review
|
||||||
|
|
||||||
|
- When, why, and how to use it
|
||||||
|
- I/O + UI
|
||||||
|
- Similar, independent computations
|
||||||
|
- Expressiveness
|
||||||
|
|
||||||
|
- Pitfalls
|
||||||
|
- Synchronization
|
||||||
|
- Resources Usage
|
||||||
|
- Debugging
|
||||||
|
- Complexity
|
||||||
|
|
||||||
|
* 58. Review
|
||||||
|
|
||||||
|
- Infrastructure
|
||||||
|
- Data Race
|
||||||
|
|
||||||
|
* 59. Conclusion
|
||||||
|
|
||||||
|
- Goals:
|
||||||
|
- Give you a rough sense of what concurrency is.
|
||||||
|
- Provide context to help you understand what's going on.
|
||||||
|
- Practical exposure, to expose some of the trade-offs.
|
||||||
|
|
||||||
|
- Awareness and Sensitivity
|
||||||
|
|
||||||
|
- Remember:
|
||||||
|
- /Premature optimization is the root of all evil/.
|
||||||
|
- Tradeoffs and economics (resources, costs, developer time).
|
||||||
|
- Learning is continuous...
|
||||||
|
|
||||||
|
* 60. Further Reading
|
||||||
|
|
||||||
|
- [[https://slikts.github.io/concurrency-glossary/][Concurrency Glossary]]
|
||||||
|
- [[https://blog.golang.org/waza-talk][Concurrency is not parallelism]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Executable_and_Linkable_Format][Executable and Linkable Format]]
|
||||||
|
- [[https://www.youtube.com/watch?v=KBZlN0izeiY][GopherCon 2017: Kavya Joshi - Understanding Channels]]
|
||||||
|
- [[http://web.mit.edu/6.005/www/fa15/][MIT 6.005: Software Construction]]
|
||||||
|
- Chapters 19, 20, 22, 23
|
||||||
|
- [[https://www.gopl.io/][The Go Programming Language]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Concurrency_(computer_science)][Wikipedia: Concurrency]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Concurrency_control][Wikipedia: Concurrency Control]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Concurrent_computing][Wikipedia: Concurrent Computing]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Deadlock][Wikipedia: Deadlock]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Dining_philosophers_problem][Wikipedia: Dining Philosophers Problem]]
|
||||||
|
- [[https://www.backblaze.com/blog/whats-the-diff-programs-processes-and-threads/][What’s the Diff: Programs, Processes, and Threads]]
|
||||||
|
- [[https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=13s][What the heck is the event loop anyway? | Philip Roberts]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Heisenbug][Wikipedia: Heisenbug]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Interrupt][Wikipedia: Interrupt]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Libuv][Wikipedia: Libuv]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Process_(computing)][Wikipedia: Process]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Scheduling_(computing)][Wikipedia: Scheduling]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Therac-25][Wikipedia: Therac-25]]
|
||||||
|
- [[https://en.wikipedia.org/wiki/Thread_(computing)][Wikipedia: Thread]]
|
||||||
|
- [[https://github.com/danicat/pacgo][pacgo: A Pac Man clone written in Go]]
|
||||||
|
- [[https://medium.com/preezma/node-js-event-loop-architecture-go-deeper-node-core-c96b4cec7aa4][node.js event loop architecture]]
|
||||||