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
@@ -766,14 +767,16 @@ concurrencpp also provides parallel coroutines, which start to run inside a give
766
767
Every parallel coroutine must meet the following preconditions:
767
768
768
769
1. Returns any of `result` / `null_result` .
769
-
1. Gets `executor_tag` as its first argument .
770
-
1. Gets any of `type*` / `type&` / `std::shared_ptr<type>`, where `type` is a concrete class of `executor` as its second argument.
771
-
1. Contains any of `co_await` or `co_return` in its body.
770
+
2. Gets `executor_tag` as its first argument .
771
+
3. Gets any of `type*` / `type&` / `std::shared_ptr<type>`, where `type` is a concrete class of `executor` as its second argument.
772
+
4. Contains any of `co_await` or `co_return` in its body.
773
+
5. Is not a member function or a lambda function
772
774
773
775
If all the above applies, the function is a parallel coroutine:
774
776
concurrencpp will start the coroutine suspended and immediately reschedule it to run in the provided executor.
775
777
`concurrencpp::executor_tag` is a dummy placeholder to tell the concurrencpp runtime that this function is not a regular function, it needs to start running inside the given executor.
776
-
Applications can then consume the result of the parallel coroutine by using the returned result object.
778
+
If the executor passed to the parallel coroutine is null, the coroutine will not start to run and an `std::invalid_argument` exception will be thrown synchronously.
779
+
If all preconditions are met, Applications can consume the result of the parallel coroutine by using the returned result object.
777
780
778
781
#### *Parallel Fibonacci example:*
779
782
```cpp
@@ -1650,6 +1653,210 @@ class generator_iterator {
1650
1653
};
1651
1654
```
1652
1655
1656
+
### Asynchronous locks
1657
+
Regular synchronous locks cannot be used safely inside coroutines for a number of reasons:
1658
+
1659
+
- Synchronous locks, such as `std::mutex`, are expected to be locked and unlocked in the same thread of execution. Unlocking a synchronous lock in a thread which had not locked it is undefined behavior. Since tasks can be suspended and resumed in any thread of execution, synchronous locks will break when used inside tasks.
1660
+
- Synchronous locks were created to work with *threads* and not with *coroutines*. If a synchronous lock is already locked by one thread, then when another thread tries to lock it, the entire thread of execution will be blocked and will be unblocked when the lock is released. This mechanism works well for traditional multi-threading paradigms but not for coroutines: with coroutines, we want *tasks* to be *suspended and resumed* without blocking or interfering with the execution of underlying threads and executors.
1661
+
1662
+
`concurrencpp::async_lock` solves those issues by providing a similar API to `std::mutex`, with the main difference that calls to `concurrencpp::async_lock` will return a lazy-result that can be `co_awaited` safely inside tasks. If one task tries to lock an async-lock and fails, the task will be suspended, and will be resumed when the lock is unlocked and acquired by the suspended task. This allows executors to process a huge amount of tasks waiting to acquire a lock without expensive context-switching and expensive kernel calls.
1663
+
1664
+
Similar to how `std::mutex` works, only one task can acquire `async_lock` at any given time, and a *read barrier* is place at the moment of acquiring. Releasing an async lock places a *write barrier* and allows the next task to acquire it, creating a chain of one-modifier at a time who sees the changes other modifiers had done and posts its modifications for the next modifiers to see.
1665
+
1666
+
Like `std::mutex`, `concurrencpp::async_lock` ***is not recursive***. Extra attention must be given when acquiring such lock - A lock must not be acquired again in a task that has been spawned by another task which had already acquired the lock. In such case, an unavoidable dead-lock will occur. Unlike other objects in concurrencpp, `async_lock` is neither copiable nor movable.
1667
+
1668
+
Like standard locks, `concurrencpp::async_lock` is meant to be used with scoped wrappers which leverage C++ RAII idiom to ensure locks are always unlocked upon function return or thrown exception. `async_lock::lock` returns a lazy-result of a scoped wrapper that calls `async_lock::unlock` on destruction. Raw uses of `async_lock::unlock` are discouraged. `concurrencpp::scoped_async_lock` acts as the scoped wrapper and provides an API which is almost identical to `std::unique_lock`. `concurrencpp::scoped_async_lock` is movable, but not copiable.
1669
+
1670
+
`async_lock::lock` and `scoped_async_lock::lock` require a resume-executor as their parameter. Upon calling those methods, if the lock is available for locking, then it is locked and the current task is resumed immediately. If not, then the current task is suspended, and will be resumed inside the given resume-executor when the lock is finally acquired by it.
1671
+
1672
+
`concurrencpp::scoped_async_lock` wraps an `async_lock` and ensure it's properly unlocked. like `std::unique_lock`, there are cases it does not wrap any lock, and in this case it's considered to be empty. An empty `scoped_async_lock` can happen when it's defaultly constructed, moved, or `scoped_async_lock::release` method is called. An empty scoped-async-lock will not unlock any lock on destruction.
1673
+
1674
+
Even if the scoped-async-lock is not empty, it does not mean that it owns the underlying async-lock and it will unlock it on destruction. Non-empty and non-owning scoped-async locks can happen if `scoped_async_lock::unlock` was called or the scoped-async-lock was constructed using `scoped_async_lock(async_lock&, std::defer_lock_t)` constructor.
Throws std::system_error if *this does not wrap any lock.
1815
+
Throws std::system_error if wrapped lock is already locked.
1816
+
Throws any exception async_lock::try_lock throws.
1817
+
*/
1818
+
lazy_result<bool> try_lock();
1819
+
1820
+
/*
1821
+
Calls async_lock::unlock on the wrapped lock.
1822
+
If *this does not wrap any lock, this method does nothing.
1823
+
Throws std::system_error if *this wraps a lock and it is not locked.
1824
+
*/
1825
+
void unlock();
1826
+
1827
+
/*
1828
+
Checks whether *this wraps a locked mutex or not.
1829
+
Returns true if wrapped locked is in acquired state, false otherwise.
1830
+
*/
1831
+
bool owns_lock() const noexcept;
1832
+
1833
+
/*
1834
+
Equivalent to owns_lock.
1835
+
*/
1836
+
explicit operator bool() const noexcept;
1837
+
1838
+
/*
1839
+
Swaps the contents of *this and rhs.
1840
+
*/
1841
+
void swap(scoped_async_lock& rhs) noexcept;
1842
+
1843
+
/*
1844
+
Empties *this and returns a pointer to the previously wrapped lock.
1845
+
After a call to this method, *this doesn't wrap any lock.
1846
+
The previously wrapped lock is not released,
1847
+
it must be released by either unlocking it manually through the returned pointer or by
1848
+
capturing the pointer with another scoped_async_lock which will take ownerwhip over it.
1849
+
*/
1850
+
async_lock* release() noexcept;
1851
+
1852
+
/*
1853
+
Returns a pointer to the wrapped async_lock, or a null pointer if there is no wrapped async_lock.
1854
+
*/
1855
+
async_lock* mutex() const noexcept;
1856
+
};
1857
+
1858
+
```
1859
+
1653
1860
### The runtime object
1654
1861
1655
1862
The concurrencpp runtime object is the agent used to acquire, store and create new executors.
@@ -1988,14 +2195,17 @@ $ cd build/test
1988
2195
$ ctest . -V
1989
2196
```
1990
2197
1991
-
##### Via vcpkg on Windows and *nix platforms
2198
+
##### Via package managers on Windows and *nix platforms
1992
2199
1993
-
Alternatively to building and installing the library manually, developers may get stable releases of concurrencpp as [vcpkg](https://vcpkg.io/)packages:
2200
+
Alternatively to building and installing the library manually, developers may get stable releases of concurrencpp via the [vcpkg](https://vcpkg.io/)and [Conan](https://conan.io/) package managers:
1994
2201
2202
+
vcpkg:
1995
2203
```shell
1996
2204
$ vcpkg install concurrencpp
1997
2205
```
1998
2206
2207
+
Conan: [concurrencpp on ConanCenter](https://conan.io/center/concurrencpp)
2208
+
1999
2209
##### Experimenting with the built-in sandbox
2000
2210
concurrencpp comes with a built-in sandbox program which developers can modify and experiment, without having to install or link the compiled library to a different code-base. In order to play with the sandbox, developers can modify `sandbox/main.cpp` and compile the application using the following commands:
0 commit comments