Slide 1

Slide 1 text

Concurrent Programming with tamizhvendan tamizhvendan Lead Consultant www.ajira.tech Tamizhvendan S Passionate, Pragmatic and Polyglot Programmer https://www.demystifyfp.com

Slide 2

Slide 2 text

A GitHub Profile API

Slide 3

Slide 3 text

The JSON response

Slide 4

Slide 4 text

GitHub APIs https:!//api.github.com/users/tamizhvendan https:!//api.github.com/users/tamizhvendan/repos https:!//api.github.com/repos/tamizhvendan/blog-samples/languages https:!//api.github.com/repos/tamizhvendan/CafeApp/languages https:!//api.github.com/repos/tamizhvendan/fsharp-phonecat/languages

Slide 5

Slide 5 text

The Sequential World time User API call Repo API call Lang API call Lang API call Lang API call

Slide 6

Slide 6 text

The “C” World time User API call Repo API call Lang API call Lang API call Lang API call

Slide 7

Slide 7 text

The Difference time User API call Repo API call Lang API call Lang API call Lang API call time User API call Repo API call Lang API call Lang API call Lang API call V/S

Slide 8

Slide 8 text

Concurrent Programming Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable. Concurrency is the composition of independently executing computations. Credits: Concurrency is not parallelism Talk by Rob Pike User API call Repo API call Lang API call Lang API call Lang API call User API call Repo API call Lang API call Lang API call Lang API call

Slide 9

Slide 9 text

Sounds like Functional Programming Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable. Concurrency is the composition of independently executing computations.

Slide 10

Slide 10 text

val getUser: UserName !-> User val getUserRepos: UserName !-> Repo [] val getTopThreeRepos: Repo []!-> Repo [] val getUserRepoLanguages: UserName !-> RepoName !-> Language[] User API call https:!//api.github.com/users/tamizhvendan Repo API call https:!//api.github.com/users/tamizhvendan/repos Lang API call https:!//api.github.com/repos/tamizhvendan/CafeApp/languages Functions!

Slide 11

Slide 11 text

The Sequential World time User API call Repo API call Lang API call Lang API call Lang API call val getGitHubProfile: UserName !-> Profile let getGitHubProfile userName = let user = getUser userName let userRepos = getUserRepos userName let topThreeUserRepos = getTopThreeRepos userRepos let topThreeUserReposLanguages = .... getUserRepoLanguages .... .... .... profile

Slide 12

Slide 12 text

val getUser: UserName !-> C val getUserRepos: UserName !-> C val getTopThreeRepos: Repo []!-> Repo [] val getUserRepoLanguages: UserName !-> RepoName !-> C User API call https:!//api.github.com/users/tamizhvendan Repo API call https:!//api.github.com/users/tamizhvendan/repos Lang API call https:!//api.github.com/repos/tamizhvendan/CafeApp/languages Functions in “C” World!

Slide 13

Slide 13 text

The “C” World val getGitHubProfile: UserName !-> C time User API call Repo API call Lang API call Lang API call Lang API call let getGitHubProfile userName = c { let user, userRepos = getUser userName !!<*> getUserRepos userName let topThreeUserRepos = getTopThreeRepos userRepos let topThreeUserReposLanguages = topThreeUserRepos !|> getUserRepoLanguages .... !|> C.conCollect .... .... profile }

Slide 14

Slide 14 text

What is the “C” World? A Programming Model that enables concurrency C<'T> Async<'T> Task<'T> Job<'T> .NET Framework F# Core Hopac Library http:!//tomasp.net/blog/csharp-fsharp-async-intro.aspx/ https:!//neoeinstein.github.io/blog/2016/04-08-hopac-getting-started-with-jobs/index.html

Slide 15

Slide 15 text

F# Library Programming Model inspired by Concurrent ML Job<‘a> - an individual unit of execution (aka lightweight Thread) Ch<‘a> - channels as communicate mechanism Alt<‘a> - coordinates and communicate between jobs Similar to Clojure’s core.async & Golang concurrency model Hopac

Slide 16

Slide 16 text

Job<‘a> !// Job let longerHelloWorldJob = job { do! timeOutMillis 2000 printfn "Hello, World!" } #time "on" run longerHelloWorldJob #time "off" Hello, World! Real: 00:00:02.003, CPU: 00:00:00.006, GC gen0: 0, gen1: 0

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Job<‘a> - a real world example type Product = { Id : int Name : string } Product Service type Review = { ProductId : int Author : string Comment : string } Review Service type ProductWithReviews = { Id : int Name : string Reviews : (string * string) list }

Slide 19

Slide 19 text

!// int !-> Job let getProduct id = job { !// Delay in the place of an external HTTP API call do! timeOutMillis 2000 return {Id = id; Name = "My Awesome Product"} }

Slide 20

Slide 20 text

let getProductReviews id = job { !// Delay in the place of an external HTTP API call do! timeOutMillis 3000 return [ {ProductId = id; Author = "John"; Comment = "It's awesome!"} {ProductId = id; Author = "Sam"; Comment = "Great product"} ] }

Slide 21

Slide 21 text

The Sequential Approach !// int !-> Job let getProductWithReviews id = job { let! product = getProduct id let! reviews = getProductReviews id let productReviews = reviews !|> List.map (fun r !-> r.Author,r.Comment) return { Id = id Name = product.Name Reviews = productReviews } }

Slide 22

Slide 22 text

The Sequential Approach #time "on" getProductWithReviews 1 !|> run #time "off" Real: 00:00:05.008, CPU: 00:00:00.009, GC gen0: 0, gen1: 0 val it : ProductWithReviews = {Id = 1; Name = "My Awesome Product"; Reviews = [("John", "It's awesome!"); ("Sam", "Great product")];}

Slide 23

Slide 23 text

Introducing Concurrency !// int !-> Job let getProductWithReviews id = job { let! product, reviews = getProduct id !!<*> getProductReviews id let productReviews = reviews !|> List.map (fun r !-> r.Author,r.Comment) return { Id = id Name = product.Name Reviews = productReviews } } val ( !!<*> ): Job<'x> !-> Job<'y> !-> Job<'x * 'y>

Slide 24

Slide 24 text

Introducing Concurrency Real: 00:00:03.005, CPU: 00:00:00.008, GC gen0: 0, gen1: 0 val it : ProductWithReviews = {Id = 1; Name = "My Awesome Product"; Reviews = [("John", "It's awesome!"); ("Sam", "Great product")];} #time "on" getProductWithReviews 1 !|> run #time "off"

Slide 25

Slide 25 text

time User API call Repo API call Lang API call Lang API call Lang API call let getUserDto username = job { let! user, repos = getUser username !!<*> getTopThreeUserRepos username let! repoDtos = repos !|> Array.map (getRepoDto username) !|> Job.conCollect return { Name = user.Name AvatarUrl = user.AvatarUrl TopThreeRepos = repoDtos.ToArray() } } A GitHub Profile API val conCollect: seq> !-> Job>

Slide 26

Slide 26 text

Inter-Thread Communication if (Monitor.TryEnter(lockObject, 300)) { try { !// Place code protected by the Monitor here. } finally { Monitor.Exit(lockObject); } } else { !// Code to execute if the attempt times out. } lock(lockObject) { myField!++; }

Slide 27

Slide 27 text

Share Memory By Communicating

Slide 28

Slide 28 text

Channel & Alt Abstractions to communicate and coordinate between jobs Synchronous by default, Supports asynchronous too! Lightweight First-Class Selective

Slide 29

Slide 29 text

Job 1 Job 2 Job 3 Job 4 time starting job:2 starting job:1 starting job:4 starting job:3 completed job:1 completed job:2 completed job:3 completed job:4 Real: 00:00:04.002, CPU: 00:00:00.013, GC gen0: 0, gen1: 0 Tracking Concurrent Job Status Job.conIgnore [job1;job2;job3;job4]

Slide 30

Slide 30 text

Tracking Concurrent Job Status 4 Jobs 1 Print Job JobStatus type JobStatus = | Started of jobId : int | Completed of jobId : int Ch.give Ch.take Channel Job x Print Job

Slide 31

Slide 31 text

Job 4 Print Job Job 1 Job 2 Print Job Job 4 Job 1 Print Job Job 3 Print Job Job 3 time Starting Status

Slide 32

Slide 32 text

Job 1 Print Job time Completion Status Job 2 Print Job Job 3 Print Job Job 4 Print Job

Slide 33

Slide 33 text

!// Ch !-> int !-> int !-> Job let createJob jobStatusChannel jobId delay = job { do! Ch.give jobStatusChannel (Started jobId) do! timeOutMillis delay do! Ch.give jobStatusChannel (Completed jobId) } Ch.give Channel Job -> Channel

Slide 34

Slide 34 text

!// Ch !-> Job let jobStatusPrinterJob jobStatusChannel = job { let! jobStatus = Ch.take jobStatusChannel match jobStatus with | Started jobId !-> printfn "starting job:%d" jobId | Completed jobId !-> printfn "completed job:%d" jobId } Ch.take Channel Channel -> Job

Slide 35

Slide 35 text

time !// unit !-> Job let main () = job { let jobStatusChannel = Ch() let jobStatusPrinter = jobStatusPrinterJob jobStatusChannel let jobs = [ Job.foreverServer jobStatusPrinter createJob jobStatusChannel 1 1000 createJob jobStatusChannel 2 2000 createJob jobStatusChannel 3 3000 createJob jobStatusChannel 4 4000 ] return! Job.conIgnore jobs }

Slide 36

Slide 36 text

starting job:2 starting job:1 starting job:4 starting job:3 completed job:1 completed job:2 completed job:3 completed job:4 Real: 00:00:04.002, CPU: 00:00:00.013, GC gen0: 0, gen1: 0 #time "on" main () !|> run #time "off"

Slide 37

Slide 37 text

A Slow Printer let jobStatusPrinterJob jobStatusChannel = job { let! jobStatus = Ch.take jobStatusChannel do! timeOutMillis 1000 match jobStatus with | Started jobId !-> printfn "starting job:%d" jobId | Completed jobId !-> printfn "completed job:%d" jobId }

Slide 38

Slide 38 text

#time "on" main () !|> run #time "off" !!--> Timing now on starting job:4 starting job:3 starting job:2 starting job:1 completed job:4 completed job:2 completed job:3 Real: 00:00:07.004, CPU: 00:00:00.014, GC gen0: 0, gen1: 0 val it : unit = () !!--> Timing now off > completed job:1

Slide 39

Slide 39 text

let main () = job { .... .... let jobStatusPrinter2 = jobStatusPrinterJob jobStatusChannel let jobs = [ .... Job.foreverServer jobStatusPrinter2 …. ] return! Job.conIgnore jobs } Scale Up with one more printer!

Slide 40

Slide 40 text

!!--> Timing now on starting job:2 starting job:4 starting job:1 starting job:3 completed job:1 completed job:2 Real: 00:00:04.010, CPU: 00:00:00.018, GC gen0: 0, gen1: 0 val it : unit = () !!--> Timing now off > completed job:4 completed job:3 #time "on" main () !|> run #time "off" It’s faster again!

Slide 41

Slide 41 text

Other Modes BoundedMb<‘x> Ch.send

Slide 42

Slide 42 text

Alt Selective synchronous operation Choose … C1 C2 Cn Alt<‘x> is a subtype of Job<‘X> Ch<‘x> is a subtype of Alt<‘X>

Slide 43

Slide 43 text

Alt in action give me an answer after sometime! type Answer = | FourtyTwo !// int !-> Alt let getAnswer delay = timeOutMillis delay !|> Alt.afterFun (fun _ !-> FourtyTwo) Real: 00:00:01.001, CPU: 00:00:00.001, GC gen0: 0, gen1: 0 val it : Answer = FourtyTwo #time "on" getAnswer 1000 !|> run #time "off"

Slide 44

Slide 44 text

Alt in action give me an answer or timeout! type Answer = | FourtyTwo | TimedOut !// int !-> int !-> Alt let getAnswerOrTimeOut delay tolerance = let timeOutAlt = timeOutMillis tolerance !|> Alt.afterFun (fun _ !-> TimedOut) Alt.choose [ timeOutAlt getAnswer delay ]

Slide 45

Slide 45 text

Alt in action give me an answer or timeout! #time "on" getAnswerOrTimeOut 1000 2000 !|> run #time "off" Real: 00:00:01.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0 val it : Answer = FourtyTwo #time "on" getAnswerOrTimeOut 1000 500 !|> run #time "off" Real: 00:00:00.501, CPU: 00:00:00.000, GC gen0: 0, gen1: 0 val it : Answer = TimedOut

Slide 46

Slide 46 text

Fan-out & Fan-in Can we calculate SHA256 hash of all the files in a directory concurrently?

Slide 47

Slide 47 text

A Generic Worker f:’a!->’b id inCh<‘a> outCh<‘b>

Slide 48

Slide 48 text

A Generic Worker type WorkerResponse<'T> = { Result : 'T Id : int } !// int !-> ('a !-> 'b) !-> Ch<'a> !-> Ch> let worker id f inCh = let outCh = Ch>() let onInput x = let y = f x Ch.give outCh {Result = y; Id = id} Ch.take inCh !|> Alt.afterJob onInput !|> Job.foreverServer !|> start outCh

Slide 49

Slide 49 text

A Generic Input Feeder !// Ch<'a> !-> 'a list !-> Alt let rec feedInput inCh inputs = match inputs with | [] !-> Alt.always () | x !:: xs !-> Ch.give inCh x !|> Alt.afterJob (fun _ !-> feedInput inCh xs) inCh<‘a> ‘a list

Slide 50

Slide 50 text

SHA256 Computation The Biz Logic (f) type Sha256Result = { FileName : string Hash : string } !// string !-> Sha256Result let computeSHA256 filePath = use sha256 = new SHA256Managed() use stream = File.OpenRead filePath let hash = sha256.ComputeHash(stream) !|> Seq.map (fun b !-> b.ToString("x2")) !|> String.concat "" { FileName = FileInfo(filePath).Name Hash = hash}

Slide 51

Slide 51 text

Putting things Together !// int !-> string !-> Job let sha256OfDirFiles workerCount directoryPath = let inCh = Ch() let filePaths = Directory.GetFiles(directoryPath) !|> Array.toList feedInput inCh filePaths !|> start inCh string list let initializeWorker id = worker (id+1) computeSHA256 inCh let workerOutChannels = List.init workerCount initializeWorker SHA256 id inCh outCh> SHA256 id inCh outCh> … let printResponse r = printfn "Worker %d: %A" r.Id r.Result Alt.choose workerOutChannels !|> Alt.afterFun printResponse !|> Job.forN filePaths.Length Print outCh>

Slide 52

Slide 52 text

Stress Testing (532 files - 16.41 GB) sha256OfDirFiles 1 “my_downloads_dir” !|> run Real: 00:06:11.336, CPU: 00:05:19.298, GC gen0: 5, gen1: 0 sha256OfDirFiles 2 “my_downloads_dir” !|> run Real: 00:04:07.560, CPU: 00:05:44.337, GC gen0: 5, gen1: 0 sha256OfDirFiles 3 “my_downloads_dir” !|> run Real: 00:04:11.717, CPU: 00:06:08.536, GC gen0: 4, gen1: 0 sha256OfDirFiles 4 “my_downloads_dir” !|> run Real: 00:03:27.888, CPU: 00:07:36.922, GC gen0: 5, gen1: 0 sha256OfDirFiles 5 “my_downloads_dir” !|> run Real: 00:04:39.357, CPU: 00:06:53.018, GC gen0: 5, gen1: 0 sha256OfDirFiles 6 “my_downloads_dir” !|> run Real: 00:04:01.523, CPU: 00:06:47.747, GC gen0: 5, gen1: 0

Slide 53

Slide 53 text

Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable. Concurrency is the composition of independently executing computations. SHA256 SHA256 … Print

Slide 54

Slide 54 text

Summary Concurrency !-> Structuring Functional Programming ❤ Concurrent Programming Choose a Right tool for the Job!

Slide 55

Slide 55 text

Deep Dive https://www.demystifyfp.com/tags/hopac

Slide 56

Slide 56 text

References Hopac Documentation Go Concurrency Patterns Concurrency is not Parallelism - Rob Pike