Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Experimentation in porting the Go concurrency model in Python

Experimentation in porting the Go concurrency model in Python

Lot of people are trying to port the actor pattern to Python, but this pattern isn’t really designed for such languages. On the contrary the Go concurrency model has some attractive points that can be easily ported to Python. This talk will describe the go concurrency model and my own experimentation named pffset to port it in Python. In this talk.

Benoit Chesneau

September 14, 2013
Tweet

More Decks by Benoit Chesneau

Other Decks in Programming

Transcript

  1. PyCON APAC 2013 - 2013/09/14 by Benoît Chesneau Experimentation in

    porting the Go concurrency model in Python Saturday, September 14, 13
  2. • 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
  3. • 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
  4. • OS Thread (M) • Scheduler context (P) • A

    goroutine 3 entities M P G Saturday, September 14, 13
  5. the go scheduler M P G G G G M

    P G G G G Saturday, September 14, 13
  6. • 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
  7. the go scheduler M P G G G G running

    goroutine Saturday, September 14, 13
  8. • 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
  9. blocking call M0 P G G G G0 M1 P

    G G G M1 M0 G0 syscall Saturday, September 14, 13
  10. • 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
  11. • to improve the concurrency, ... • steal the work

    in other contexts stealing work Saturday, September 14, 13
  12. Stealing work M P G Gm G G0 M P

    M P G G M P G Gm Saturday, September 14, 13
  13. • 1.2 adding preemption • A way to stop the

    work every N ops. what’s next? Saturday, September 14, 13
  14. • 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
  15. • 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
  16. • 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
  17. an offset is a small, virtually complete daughter plant that

    has been naturally and asexually produced on the mother plant. Saturday, September 14, 13
  18. • python-fibers on cpython https://github.com/saghul/python-fibers • continulets on pypy •

    abstracted in a Proc class goroutines Saturday, September 14, 13
  19. • OS Thread (F) • Scheduler context (P) • A

    goroutine offset scheduler: 3 entities F P G Saturday, September 14, 13
  20. • 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
  21. • 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
  22. blocking call P G G G G0 P G G

    G F G0 syscall Saturday, September 14, 13
  23. • 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
  24. • the goroutine is put back on the top of

    the run queue • the result is returned syscall exit Saturday, September 14, 13
  25. 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
  26. • 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
  27. 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
  28. • 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
  29. 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
  30. • 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
  31. 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
  32. • 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
  33. • 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