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: CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,10 @@
1
1
# News
2
2
3
+
## v1.2.0 - 2023-08-06
4
+
5
+
- Priorities can now be non-integer.
6
+
- Relax some of the previous deprecations, implement `Base.lock` and `Base.trylock`, and document the differences in blocking and yield-ness of Base and ConcurrentSim methods.
7
+
3
8
## v1.1.0 - 2023-08-02
4
9
5
10
- Start using `Base`'s API: `Base.unlock`, `Base.islocked`, `Base.isready`, `Base.put!`, `Base.take!`. Deprecate `put`, `release`. Moreover, consider using `Base.take!` instead of `Base.get` (which was not deprecated yet, as we decide which semantics to follow). Lastly, `Base.lock` and `Base.trylock` are **not** implement -- they are superficially similar to `request` and `tryrequest`, but have to be explicitly `@yield`-ed.
Copy file name to clipboardExpand all lines: README.md
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -59,4 +59,7 @@ A [detailed change log is kept](https://github.com/JuliaDynamics/ConcurrentSim.j
59
59
60
60
## Alternatives
61
61
62
-
`ConcurrentSim.jl` and `DiscreteEvents.jl` both provide for typical event-based simulations. `ConcurrentSim.jl` is built around coroutines (implemented in `ResumableFunctions.jl`), while `DiscreteEvents.jl` uses Julia's async primitives via `Channels`. If you are evaluating which library to you for your goals, `ConcurrentSim.jl` might be a good choice if you are used to python's SimPy, but otherwise you are advised to try a small demo project in each and do your own benchmarks. Do not hesitate to submit issues on Github with questions or suggestions or feature requests. We value hearing what your experience with this library (compared to other libraries) has been.
62
+
`ConcurrentSim.jl` and `DiscreteEvents.jl` both provide for typical event-based simulations.
63
+
`ConcurrentSim.jl` is built around coroutines (implemented in `ResumableFunctions.jl`), while `DiscreteEvents.jl` uses Julia's async primitives via `Channels`.
64
+
`DiscreteEvents.jl` has an explicit clock that "ticks" at a fixed finite resolution, while `ConcurrentSim.jl` uses coroutines to make arbitrary jumps in time.
65
+
If you are evaluating which library to you for your goals, `ConcurrentSim.jl` might be a good choice if you are used to python's SimPy, but otherwise you are advised to try a small demo project in each and do your own benchmarks. Do not hesitate to submit issues on Github with questions or suggestions or feature requests. We value hearing what your experience with this library (compared to other libraries) has been.
The goal of this page is to list the most common synchronization and resource management patterns used in `ConcurrentSim.jl` simulations and to briefly compare them to Julia's base capabilities for asynchronous and parallel programming.
4
+
5
+
There are many different approaches to discrete event simulation in particular and to asynchronous and parallel programming in general. This page assumes some rudimentary understanding of concurrency in programming. While not necessary, you are encouraged to explore the following resources for a more holistic understanding:
6
+
7
+
- "concurrency" vs "parallelism" - see [stackoverflow.com](https://stackoverflow.com/questions/1050222/what-is-the-difference-between-concurrency-and-parallelism) on the topic;
8
+
- "threads" vs "tasks": A task is the actual piece of work, a thread is the "runway" on which a task runs. You can have more tasks than threads and you can even have tasks that jump between threads - see Julia's [parallel programming documentation](https://docs.julialang.org/en/v1/manual/parallel-computing/) (in particular the [async](https://docs.julialang.org/en/v1/manual/asynchronous-programming/) and [multithreading](https://docs.julialang.org/en/v1/manual/multi-threading/) docs), and multiple Julia blog post on [multithreading](https://julialang.org/blog/2019/07/multithreading/) and [its misuses](https://julialang.org/blog/2023/07/PSA-dont-use-threadid/);
9
+
- "locks" used to guard (or synchronize) the access to a given resource: i.e. one threads locks an array while modifying it in order to ensure that another thread will not be modifying it at the same time. Julia's `Base` multithreading capabilities provide a `ReentrantLock`, together with a `lock`, `trylock`, `unlock`, and `islocked` API;
10
+
- "channels" used to organize concurrent tasks. Julia's `Base` multithreading capabilities provide `Channel`, together with `take!`, `put!`, `isready`;
11
+
- knowing of the ["red/blue-colored functions" metaphor](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) can be valuable as well as learning of "promises" and "futures".
12
+
13
+
Programming discrete event simulations can be very similar to async parallel programming, except for the fact that in the simulation the "time" is fictitious (and tracking it is a big part of the value proposition in the simulation software). On the other hand, in usual parallel programming the goal is simply to do as much work as possible in the shortest (actual) time. In that context, one possible use of discrete event simulations is to cheaply model and optimize various parallel implementations of actual expensive algorithms (whether numerical computer algorithms or the algorithms used to schedule a real factory or a fleet of trucks).
14
+
15
+
In particular, the `ConcurrentSim.jl` package uses the async "coroutines" model of parallel programing. `ConcurrentSim` uses the `ResumableFunctions.jl` package to build its coroutines, which uses the `@resumable` macro to mark a function as an "async" coroutine and the `@yield` macro to yield between coroutines.
16
+
17
+
!!! warning "Base Julia coroutines vs ConcurrentSim coroutines"
18
+
The `ConcurrentSim` and `ResumableFunctions` coroutines are currently incompatible with Julia's base coroutines (which based around `wait` and `fetch`). A separate coroutines implementation was necessary, because Julia's coroutines are designed for computationally heavy tasks and practical parallel algorithms, leading to significant overhead when they are used with extremely large numbers of computationally cheap tasks, as it is common in discrete event simulators. `ResumableFunctions`'s coroutines are single threaded but with drastically lower call overhead.
19
+
A future long-term goal of ours is to unify the API used by `ResumableFunctions` and base Julia, but this will not be achieved in the near term, hence the need for pages like this one.
20
+
21
+
Without further ado, here is the typical API used with:
22
+
23
+
-`ConcurrentSim.Resource` which is used to represent scarce resource that can be used by only up to a fixed number of tasks. If the limit is just one task (the default), this is very similar to `Base.ReentrantLock`. `Resource` is a special case of `Container` with an integer "resource counter".
24
+
-`ConcurrentSim.Store` which is used to represent a FILO stack.
25
+
26
+
```@raw html
27
+
<div style="width:120%;min-width:120%;">
28
+
```
29
+
30
+
||`Base``ReentrantLock`|`Base``Channel`|`ConcurrentSim``Container`|`ConcurrentSim``Resource`, i.e. `Container{Int}`|`ConcurrentSim``Store`||
31
+
|---|:---|:---|:---|:---|:---|:---:|
32
+
|`put!`|❌|❌|@yield|@yield|@yield|low-level "put an object in" API|
33
+
|`take!`|❌|block|❌|❌|@yield|the `Channel`-like API for `Store`|
34
+
|`lock`|block|❌|❌|@yield|❌|the `Lock`-like API for `Resource` (there is also `trylock`)|
35
+
|`unlock`|✔️|❌|❌|@yield|❌|the `Lock`-like API for `Resource`|
36
+
|`isready`|❌|✔️|✔️|✔️|✔️|something is stored in the resource|
37
+
|`islocked`|✔️|❌|✔️|✔️|✔️|the resource can not store anything more|
38
+
39
+
```@raw html
40
+
</div>
41
+
```
42
+
43
+
The table denotes which methods exist (✔️), are blocking (block), need to be explicitly yielded with `ResumableFunctions` (@yield), or are not applicable (❌).
44
+
45
+
As you can see `Resource` shares some properties with `ReentrantLock` and avails itself of the `lock`/`unlock`/`trylock` Base API. `Store` similarly shares some properties with `Channel` and shares the `put!`/`take!` Base API. Of note is that when the Base API would be blocking, the corresponding `ConcurrentSim` methods actually give coroutines that need to be `@yield`-ed.
46
+
47
+
`take!` and `unlock` are both implemented on top of the lower level `get`.
48
+
49
+
The `Base.lock` and `Base.unlock` are aliased to `ConcurrentSim.request` and `ConcurrentSim.release` respectively for semantic convenience when working with `Resource`.
50
+
51
+
`unlock(::Resource)` is instantaneous so the `@yield` is not strictly necessary. Similarly for `put!(::Store)` if the store has infinite capacity.
0 commit comments