forked from boostorg/unordered
documented boost::concurrent_flat_set
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
|
||||
:idprefix: concurrent_
|
||||
|
||||
Boost.Unordered currently provides just one concurrent container named `boost::concurrent_flat_map`.
|
||||
`boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from
|
||||
Boost.Unordered provides `boost::concurrent_flat_set` and `boost::concurrent_flat_map`,
|
||||
hash tables that allow concurrent write/read access from
|
||||
different threads without having to implement any synchronzation mechanism on the user's side.
|
||||
|
||||
[source,c++]
|
||||
@@ -36,16 +36,16 @@ In the example above, threads access `m` without synchronization, just as we'd d
|
||||
single-threaded scenario. In an ideal setting, if a given workload is distributed among
|
||||
_N_ threads, execution is _N_ times faster than with one thread —this limit is
|
||||
never attained in practice due to synchronization overheads and _contention_ (one thread
|
||||
waiting for another to leave a locked portion of the map), but `boost::concurrent_flat_map`
|
||||
is designed to perform with very little overhead and typically achieves _linear scaling_
|
||||
waiting for another to leave a locked portion of the map), but Boost.Unordered concurrent containers
|
||||
are designed to perform with very little overhead and typically achieve _linear scaling_
|
||||
(that is, performance is proportional to the number of threads up to the number of
|
||||
logical cores in the CPU).
|
||||
|
||||
== Visitation-based API
|
||||
|
||||
The first thing a new user of `boost::concurrent_flat_map` will notice is that this
|
||||
class _does not provide iterators_ (which makes it technically
|
||||
not a https://en.cppreference.com/w/cpp/named_req/Container[Container^]
|
||||
The first thing a new user of `boost::concurrent_flat_set` or `boost::concurrent_flat_map`
|
||||
will notice is that these classes _do not provide iterators_ (which makes then technically
|
||||
not https://en.cppreference.com/w/cpp/named_req/Container[Containers^]
|
||||
in the C++ standard sense). The reason for this is that iterators are inherently
|
||||
thread-unsafe. Consider this hypothetical code:
|
||||
|
||||
@@ -73,7 +73,7 @@ m.visit(k, [](const auto& x) { // x is the element with key k (if it exists)
|
||||
----
|
||||
|
||||
The visitation function passed by the user (in this case, a lambda function)
|
||||
is executed internally by `boost::concurrent_flat_map` in
|
||||
is executed internally by Boost.Unordered in
|
||||
a thread-safe manner, so it can access the element without worrying about other
|
||||
threads interfering in the process.
|
||||
|
||||
@@ -112,7 +112,7 @@ if (found) {
|
||||
}
|
||||
----
|
||||
|
||||
Visitation is prominent in the API provided by `boost::concurrent_flat_map`, and
|
||||
Visitation is prominent in the API provided by `boost::concurrent_flat_ser` and `boost::concurrent_flat_map`, and
|
||||
many classical operations have visitation-enabled variations:
|
||||
|
||||
[source,c++]
|
||||
@@ -129,13 +129,17 @@ the element: as a general rule, operations on a `boost::concurrent_flat_map` `m`
|
||||
will grant visitation functions const/non-const access to the element depending on whether
|
||||
`m` is const/non-const. Const access can be always be explicitly requested
|
||||
by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result
|
||||
in higher parallelization. Consult the xref:#concurrent_flat_map[reference]
|
||||
for a complete list of available operations.
|
||||
in higher parallelization. For `boost::concurrent_flat_set`, on the other hand,
|
||||
visitation is always const access.
|
||||
Consult the references of
|
||||
xref:#concurrent_flat_set[`boost::concurrent_flat_set`] and
|
||||
xref:#concurrent_flat_map[`boost::concurrent_flat_map`]
|
||||
for the complete list of visitation-enabled operations.
|
||||
|
||||
== Whole-Table Visitation
|
||||
|
||||
In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all`
|
||||
as an alternative way to process all the elements in the map:
|
||||
In the absence of iterators, `visit_all` is provided
|
||||
as an alternative way to process all the elements in the container:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
@@ -187,12 +191,12 @@ m.erase_if([](auto& x) {
|
||||
`visit_while` and `erase_if` can also be parallelized. Note that, in order to increase efficiency,
|
||||
whole-table visitation operations do not block the table during execution: this implies that elements
|
||||
may be inserted, modified or erased by other threads during visitation. It is
|
||||
advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map`
|
||||
advisable not to assume too much about the exact global state of a concurrent container
|
||||
at any point in your program.
|
||||
|
||||
== Blocking Operations
|
||||
|
||||
``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any
|
||||
``boost::concurrent_flat_set``s and ``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any
|
||||
Boost.Unordered container. Unlike most other operations, these are _blocking_,
|
||||
that is, all other threads are prevented from accesing the tables involved while a copy, assignment,
|
||||
clear or merge operation is in progress. Blocking is taken care of automatically by the library
|
||||
@@ -204,8 +208,10 @@ reserving space in advance of bulk insertions will generally speed up the proces
|
||||
|
||||
== Interoperability with non-concurrent containers
|
||||
|
||||
As their internal data structure is basically the same, `boost::unordered_flat_map` can
|
||||
be efficiently move-constructed from `boost::concurrent_flat_map` and vice versa.
|
||||
As open-addressing and concurrent containers are based on the same internal data structure,
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map` can
|
||||
be efficiently move-constructed from `boost::concurrent_flat_set` and `boost::concurrent_flat_map`,
|
||||
respectively, and vice versa.
|
||||
This interoperability comes handy in multistage scenarios where parts of the data processing happen
|
||||
in parallel whereas other steps are non-concurrent (or non-modifying). In the following example,
|
||||
we want to construct a histogram from a huge input vector of words:
|
||||
|
||||
Reference in New Issue
Block a user