Slide 1

Slide 1 text

PyCON APAC 2013 - 2013/09/14 by Benoît Chesneau Experimentation in porting the Go concurrency model in Python Saturday, September 14, 13

Slide 2

Slide 2 text

• tired of the current status quo • the go concurrency model seems to fit perfectly to Python • it’s a good distraction why ? Saturday, September 14, 13

Slide 3

Slide 3 text

the go memory model Saturday, September 14, 13

Slide 4

Slide 4 text

• goroutines (coroutines) • a goroutine doesn’t know anything about others • channels are the only way to communicate • select to wait on multiple channels share nothing Saturday, September 14, 13

Slide 5

Slide 5 text

the go scheduler Saturday, September 14, 13

Slide 6

Slide 6 text

• OS Thread (M) • Scheduler context (P) • A goroutine 3 entities M P G Saturday, September 14, 13

Slide 7

Slide 7 text

the go scheduler M P G G G G M P G G G G Saturday, September 14, 13

Slide 8

Slide 8 text

• each thread (M) is holding a context P • Each context is running a goroutine • Number of context = GOMAXPROC • A context maintains unscheduled goroutines in a run queue the go scheduler Saturday, September 14, 13

Slide 9

Slide 9 text

the go scheduler M P G G G G running goroutine Saturday, September 14, 13

Slide 10

Slide 10 text

• Why having a context? • to handle blocking calls and syscalls • On blocking call a new thread is used to to hold the context • the other thread continue the execution context ? Saturday, September 14, 13

Slide 11

Slide 11 text

blocking call M0 P G G G G0 M1 P G G G M1 M0 G0 syscall Saturday, September 14, 13

Slide 12

Slide 12 text

• When a goroutine is running... • ...the current M makes sure another is running • reduce the time to launch a thread • once released a thread is iddling the go scheduler Saturday, September 14, 13

Slide 13

Slide 13 text

• to improve the concurrency, ... • steal the work in other contexts stealing work Saturday, September 14, 13

Slide 14

Slide 14 text

Saturday, September 14, 13

Slide 15

Slide 15 text

Stealing work M P G Gm G G0 M P M P G G M P G Gm Saturday, September 14, 13

Slide 16

Slide 16 text

• 1.2 adding preemption • A way to stop the work every N ops. what’s next? Saturday, September 14, 13

Slide 17

Slide 17 text

Implementing it in Python Saturday, September 14, 13

Slide 18

Slide 18 text

• GIL • 1 OS thread is executed at a time • works well for I/O bound operations since they can work in the background • no built-in implementation of the coroutines Drawbacks of Python Saturday, September 14, 13

Slide 19

Slide 19 text

• Only 1 context / Python VM • Coroutines are always scheduled in the main thread. • the main thread hold the context • Blocking call operations (mostly io) are executed in their thread choices Saturday, September 14, 13

Slide 20

Slide 20 text

• offset, library implementing the go concurrency model in Python • compatible Python 2.7, Python 3.3x and sup • ...and Pypy • http://github.com/benoitc/offset offset Saturday, September 14, 13

Slide 21

Slide 21 text

an offset is a small, virtually complete daughter plant that has been naturally and asexually produced on the mother plant. Saturday, September 14, 13

Slide 22

Slide 22 text

• python-fibers on cpython https://github.com/saghul/python-fibers • continulets on pypy • abstracted in a Proc class goroutines Saturday, September 14, 13

Slide 23

Slide 23 text

• OS Thread (F) • Scheduler context (P) • A goroutine offset scheduler: 3 entities F P G Saturday, September 14, 13

Slide 24

Slide 24 text

the offset scheduler F P G G G G Saturday, September 14, 13

Slide 25

Slide 25 text

• The context lives in the main thread • 1 context / Python VM • a context holds the coroutines in a run queue. goroutines Saturday, September 14, 13

Slide 26

Slide 26 text

the go scheduler P G G G G running goroutine Saturday, September 14, 13

Slide 27

Slide 27 text

• patched functions and objects from the stdlib maintained in the syscall module • a system call is executed in its own thread • use the Futures from the concurrent module • number of io threads is set using OFFSET_MAX_THREADS. Default to the number of CPUs. syscalls Saturday, September 14, 13

Slide 28

Slide 28 text

blocking call P G G G G0 P G G G F G0 syscall Saturday, September 14, 13

Slide 29

Slide 29 text

• the goroutine is put out the run queue • a Future is launched and will executed the blocking function (syscall) • the link between the future is stored in the scheduler • the scheduler always wait for syscalls if no goroutines are scheduled entering in syscall Saturday, September 14, 13

Slide 30

Slide 30 text

• the goroutine is put back on the top of the run queue • the result is returned syscall exit Saturday, September 14, 13

Slide 31

Slide 31 text

Goroutine example from offset import go, maintask, run from offset import time def say(s): for i in range(5): time.sleep(100 * time.MILLISECOND) print(s) @maintask def main(): go(say, "world") say("hello") if __name__ == "__main__": run() package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") } Python Go Saturday, September 14, 13

Slide 32

Slide 32 text

• channels are fully implemented in offset • when a sender or a receiver need to wait, the goroutine is put out of the run queue • when a message can be sent or received the target is put back in the run queue implementing the channel Saturday, September 14, 13

Slide 33

Slide 33 text

channel example from offset import ( makechan, go, maintask, run ) def sum(a, c): s = 0 for v in a: s += v c.send(s) @maintask def main(): a = [7, 2, 8, -9, 4, 0] c = makechan() go(sum, a[:int(len(a)/2)], c) go(sum, a[int(len(a)/2):], c) x, y = c.recv(), c.recv() print(x, y, x+y) if __name__ == "__main__": run() package main import "fmt" func sum(a []int, c chan int) { sum := 0 for _, v := range a { sum += v } c <- sum // send sum to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x+y) } Python Go Saturday, September 14, 13

Slide 34

Slide 34 text

• a buffer to maintain new messages • the sender can send until the buffer is full • the receiver always block waiting a message and eventually wake up the sender buffered channels Saturday, September 14, 13

Slide 35

Slide 35 text

buffered channel example from offset import ( makechan, maintask, run ) @maintask def main(): c = makechan(2) c.send(1) c.send(2) print(c.recv()) print(c.recv()) if __name__ == "__main__": run() package main import "fmt" func main() { c := make(chan int, 2) c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } Python Go Saturday, September 14, 13

Slide 36

Slide 36 text

• 3 pass • pass 1 - look for something already waiting • pass 2 - enqueue on all channels • pass 3 - dequeue from unsucessful channels • return the selected case implementing select Saturday, September 14, 13

Slide 37

Slide 37 text

example of select from offset import ( makechan, select, go, run, maintask ) def fibonacci(c, quit): x, y = 0, 1 while True: ret = select(c.if_send(x), quit.if_recv()) if ret == c.if_send(x): x, y = y, x+y elif ret == quit.if_recv(): print("quit") return @maintask def main(): c = makechan() quit = makechan() def f(): for i in range(10): print(c.recv()) quit.send(0) go(f) fibonacci(c, quit) if __name__ == "__main__": run() package main import "fmt" func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) } Python Go Saturday, September 14, 13

Slide 38

Slide 38 text

• sync: basic synchronization primitives 100% • time: timer, ticker and sleep implemented • syscalls: only select functions for now • coming: net and io modules other modules implemented Saturday, September 14, 13

Slide 39

Slide 39 text

• spawn multiple python vm to add // • remove the run command (?) • syscall patching using wrapt: https://github.com/GrahamDumpleton/wrapt future improvements Saturday, September 14, 13

Slide 40

Slide 40 text

• First stable version this month • http://github.com/benoitc/offset future improvements Saturday, September 14, 13

Slide 41

Slide 41 text

? @benoitc Saturday, September 14, 13