You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/LE-004-new-concurrency-model.md
+68-17
Original file line number
Diff line number
Diff line change
@@ -16,31 +16,80 @@ When comparing this with the Go concurrency model, where async function calls ar
16
16
17
17
## Proposed Solution
18
18
19
-
The new concurrency model is based on channels and coroutines. Coroutines are similar to async functions, but they are not blocking. They can be called using the `async` function. The `async` function is followed by an expression, which is executed in a new coroutine. The coroutine can be suspended using the `await` function. The `await` keyword is followed by an expression, which is evaluated in the current coroutine. The result of the expression is returned to the coroutine, which called `await`.
19
+
The new concurrency model is based on channels and coroutines. Coroutines are similar to async functions, but they are not blocking. They can be called using the `async` function. The `async` function is followed by an expression, which is executed in background.
20
20
21
21
```lithia
22
-
let task = async { =>
23
-
let channel = Channel 0
24
-
select { on, closed =>
25
-
on channel, { value =>
26
-
print value
27
-
}
28
-
on None, { _ =>
29
-
print "No value"
30
-
}
31
-
closed channel, { reason =>
32
-
print reason
33
-
}
22
+
// create an unbuffered channel
23
+
let channel = Channel 0
24
+
25
+
// start a new coroutine
26
+
async { =>
27
+
// send a value to the channel
28
+
// as unbuffered, send is blocking until received
29
+
channel.send "Hello World"
30
+
}
31
+
32
+
// start selecting on the channel
33
+
// blocking until fulfilled
34
+
select { on, closed =>
35
+
// on new value in channel
36
+
on channel, { value =>
37
+
print value
38
+
}
39
+
// channel is closed
40
+
closed channel, { reason =>
41
+
print reason
42
+
}
43
+
// default case
44
+
on None, { _ =>
45
+
print "No value"
34
46
}
35
47
}
36
-
await task
37
48
```
38
49
50
+
When spinning up a new async task, a `Task` is returned. This can be used to `await` for the task to finish.
51
+
But in contrast to other languages, long running tasks are not async by default and returning Tasks is considered an anti-pattern. Invocations of `async` and `await` are designed to take place on the call site.
52
+
53
+
```lithia
54
+
// start all jobs in background
55
+
let tasks = lists.map jobs, { job => async run job }
56
+
// wait for all tasks to finish
57
+
lists.map tasks, await
58
+
```
59
+
60
+
The goal of a `Task` is to avoid local channels to wait for multiple results, requested in parallel. They should not escape your function scope.
61
+
39
62
## Detailed Design
40
63
41
-
`Channel` is just a wrapper around a Go-channel. But in contrast to `close` in Go, `close` in Lithia takes a reason.
64
+
The new concurrency model is based around three basic building blocks:
65
+
66
+
1. Channels - represent a communication channel between different parts of the program
67
+
2. Select - allows to wait for certain events to take place
68
+
3. Async Tasks - represent a small unit of work, which can be executed in background
42
69
43
-
The `async` function directly creates a new Goroutine with a `Task` under the hood. It is implemented similar to `rx.Future`.
70
+
### Channels
71
+
72
+
Channels are used to communicate between different parts of the program. They are created using the `Channel` type. The `Channel` type is a wrapper around a Go-channel. It can be created with a buffer size. If the buffer size is 0, the channel is unbuffered. Otherwise it is buffered.
73
+
74
+
- Sending a value to a channel is blocking if the channel is unbuffered or if the buffer is full until there is a receiver.
75
+
- Receiving a value from a channel is blocking if the channel is unbuffered or if the buffer is empty until there is a sender.
76
+
- Closing a channel not blocking.
77
+
- Waiting for a channel to be closed is blocking until it has been blocked.
78
+
- Closing a channel requires a reason.
79
+
- Sending a value to a closed channel is a runtime error.
80
+
- Closing an already closed channel is a runtime error. Even if the reason is the same.
81
+
82
+
### Select
83
+
84
+
The `select` function allows to handle the fastest case in a set of channels or Async Tasks. It is blocking until one of the cases is fulfilled. The `select` function takes a function as argument. This function is called with two functions `on` and `closed`. The `on` function is used to register a case for a channel. The `closed` function is used to register a case for a closed channel.
85
+
86
+
If `on` is called with `None` or `Nil`, it is called if no other case is fulfilled immediately. If it has been omitted, the `select` function will block until one of the cases is fulfilled.
87
+
88
+
### Async Tasks
89
+
90
+
Async Tasks are used to execute a small unit of work in background. They are created using the `async` function. The `async` function takes an expression as argument. This expression is executed in background. The `async` function returns a `Task` which can be used to `await` for the task to finish.
91
+
92
+
When awaiting a task, the current coroutine is blocked until the task has finished. The result of the task is returned. Tasks cannot fail.
44
93
45
94
## Changes to the Standard Library
46
95
@@ -66,4 +115,6 @@ The `Task` could be dropped, but it might be useful.
66
115
67
116
## Acknowledgements
68
117
69
-
This is heavily inspired by Go. `Task` is inspired by Swift.
118
+
`Channel`, `select`, but also `async` are heavily inspired by Go. But in contrast to Go, they require a reason to close a channel.
119
+
120
+
`Task`, `async` and `await` are inspired by many implementations like in TypeScript or Swift. Especially decoupling errors from async operations like in Swift really fit into Lithia's design.
0 commit comments