aqua-book/language/flow/parallel.md

149 lines
4.3 KiB
Markdown
Raw Normal View History

2021-06-28 11:27:19 +00:00
# Parallel
Parallel execution is where everything becomes shiny.
## Contract
2021-06-28 11:27:19 +00:00
Parallel arms have no access to each other's data. Sync points must be explicit \(see Join behavior\).
If any arm is executed successfully, the flow execution continues.
All the data defined in parallel arms is available in the subsequent code.
## Implementation limitation
2021-06-28 11:27:19 +00:00
Parallel execution has some implementation limitations:
* Parallel means independent execution on different peers
* No parallelism when executing a script on a single peer \(fix planned\)
* No concurrency in services: one service instance does only one job simultaneously. Keep services small \(wasm limitation\)
We might overcome these limitations later, but for now, plan your application design having this in mind.
## Parallel operations
2021-06-28 11:27:19 +00:00
## par
2021-06-28 11:27:19 +00:00
`par` syntax is derived from π-calculus notation of parallelism: `A | B`
```text
-- foo and bar will be executed in parallel, if possible
foo()
par bar()
-- It's useful to combine `par` with `on` block,
-- to delegate further execution to different peers.
-- In this case execution will continue on two peers, independently
on "peer 1":
x <- foo()
par on "peer 2":
y <- bar()
2021-06-28 11:27:19 +00:00
-- Once any of the previous functions return x or y,
-- execution continues. We don't know the order, so
-- if y is returned first, hello(x) will not execute
hello(x)
hello(y)
-- You can fix it with par
-- What's comes faster, will advance the execution flow
hello(x)
par hello(y)
```
`par` works in infix manner between the previously stated function and the next one.
### co
2021-06-28 11:27:19 +00:00
`co` , short for `coroutine`, prefixes an operation to send it to background. From π-calculus perspective, it's the same as `A | null`, where `null`-process is the one that does nothing and completes instantly.
```text
-- Let's send foo to background and continue
co foo()
-- Do something on another peer, not blocking the flow on this one
co on "some peer":
baz()
2021-06-28 11:27:19 +00:00
-- This foo does not wait for baz()
foo()
-- Assume that foo is executed on another machine
co try:
x <- foo()
-- bar will not wait for foo to be executed or even launched
bar()
-- bax will wait for foo to complete
-- if foo failed, x will never resolve
-- and bax will never execute
bax(x)
```
## Join behavior
2021-06-28 11:27:19 +00:00
Join means that data was created by different parallel execution flows and then used on a single peer to perform computations. It works the same way for any parallel blocks, be it `par`, `co` or something else \(`for par`\).
In Aqua, you can refer to previously defined variables. In case of sequential computations, they are available, if execution not failed:
```text
-- Start execution somewhere
on peer1:
-- Go to peer1, execute foo, remember x
x <- foo()
2021-06-28 11:27:19 +00:00
-- x is available at this point
2021-06-28 11:27:19 +00:00
on peer2:
-- Go to peer2, execute bar, remember y
y <- bar()
-- Both x and y are available at this point
-- Use them in a function
baz(x, y)
2021-06-28 11:27:19 +00:00
```
Let's make this script parallel: execute `foo` and `bar` on different peers in parallel, then use both to compute `baz`.
```text
-- Start execution somewhere
on peer1:
-- Go to peer1, execute foo, remember x
x <- foo()
2021-06-28 11:27:19 +00:00
-- Notice par on the next line: it means, go to peer2 in parallel with peer1
2021-06-28 11:27:19 +00:00
par on peer2:
-- Go to peer2, execute bar, remember y
y <- bar()
-- Remember the contract: either x or y is available at this point
-- As it's enough to execute just one branch to advance further
baz(x, y)
```
What will happen when execution comes to `baz`?
Actually, the script will be executed twice: first time it will be sent from `peer1`, and second time from `peer2`. Or another way round: `peer2` then `peer1`, we don't know who is faster.
When execution will get to `baz` for the first time, [Aqua VM](../../runtimes/aqua-vm.md) will realize that it lacks some data that is expected to be computed above in the parallel branch. And halt.
After the second branch executes, VM will be woken up again, reach the same piece of code and realize that now it has enough data to proceed.
This way you can express race \(see [Collection types](../types.md#collection-types) and [Conditional return](conditional.md#conditional-return) for other uses of this pattern\):
```text
-- Initiate a stream to write into it several times
results: *string
on peer1:
results <- foo()
par on peer2:
results <- bar()
-- When any result is returned, take the first (the fastest) to proceed
baz(results!0)
```