Skip to content

Commit 3df4e17

Browse files
committed
Improve documentation of limitedParallelism
1 parent e88bbd6 commit 3df4e17

File tree

1 file changed

+54
-15
lines changed

1 file changed

+54
-15
lines changed

kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt

+54-15
Original file line numberDiff line numberDiff line change
@@ -65,37 +65,76 @@ public abstract class CoroutineDispatcher :
6565

6666
/**
6767
* Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism].
68-
* The resulting view uses the original dispatcher for execution, but with the guarantee that
68+
* The resulting view uses the original dispatcher for execution but with the guarantee that
6969
* no more than [parallelism] coroutines are executed at the same time.
7070
*
7171
* This method does not impose restrictions on the number of views or the total sum of parallelism values,
72-
* each view controls its own parallelism independently with the guarantee that the effective parallelism
72+
* each view controls its own parallelism independently with the guarantee that effective parallelism
7373
* of all views cannot exceed the actual parallelism of the original dispatcher.
7474
*
75-
* ### Limitations
76-
*
77-
* The default implementation of `limitedParallelism` does not support direct dispatchers,
78-
* such as executing the given runnable in place during [dispatch] calls.
79-
* Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
80-
* For direct dispatchers, it is recommended to override this method
81-
* and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
75+
* The resulting dispatcher does not guarantee that the coroutines will always be dispatched on the same
76+
* subset of threads, it only guarantees that at most [parallelism] coroutines are executed at the same time,
77+
* and reuses threads from the original dispatchers.
78+
* It does not constitute a resource -- it is a _view_ of the underlying dispatcher that can be thrown away
79+
* and is not required to be closed.
8280
*
8381
* ### Example of usage
8482
* ```
85-
* private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
83+
* // Background dispatcher for the application
84+
* val dispatcher = newFixedThreadPoolContext(4, "App Background")
8685
* // At most 2 threads will be processing images as it is really slow and CPU-intensive
87-
* private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2)
86+
* val imageProcessingDispatcher = dispatcher.limitedParallelism(2)
8887
* // At most 3 threads will be processing JSON to avoid image processing starvation
89-
* private val jsonProcessingDispatcher = backgroundDispatcher.limitedParallelism(3)
88+
* val jsonProcessingDispatcher = dispatcher.limitedParallelism(3)
9089
* // At most 1 thread will be doing IO
91-
* private val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1)
90+
* val fileWriterDispatcher = dispatcher.limitedParallelism(1)
9291
* ```
9392
* Note how in this example the application has an executor with 4 threads, but the total sum of all limits
94-
* is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism.
93+
* is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism,
94+
* and at most 4 threads can exist in the system.
9595
*
9696
* Note that this example was structured in such a way that it illustrates the parallelism guarantees.
9797
* In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a
98-
* `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them.
98+
* `backgroundDispatcher`.
99+
*
100+
* ### `limitedParallelism(1)` pattern
101+
*
102+
* One of the common patterns is confining the execution of specific tasks to a sequential execution in background
103+
* with `limitedParallelism(1)` invocation.
104+
* For that purpose, the implementation guarantees that tasks are executed sequentially and that a happens-before relation
105+
* is established between them:
106+
*
107+
* ```
108+
* val confined = Dispatchers.Default.limitedParallelism(1)
109+
* var counter = 0
110+
*
111+
* // Invoked from arbitrary coroutines
112+
* launch(confined) {
113+
* // This increment is sequential and race-free
114+
* ++counter
115+
* }
116+
* ```
117+
* Note that there is no guarantee that the underlying system thread will always be the same.
118+
*
119+
* ### Dispatchers.IO
120+
*
121+
* `Dispatcher.IO` is considered _elastic_ for the purposes of limited parallelism -- the sum of
122+
* views is not restricted by the capacity of `Dispatchers.IO`.
123+
* See `Dispatchers.IO` documentation for more details.
124+
*
125+
* ### Restrictions and implementation details
126+
*
127+
* The default implementation of `limitedParallelism` does not support direct dispatchers,
128+
* such as executing the given runnable in place during [dispatch] calls.
129+
* Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
130+
* For direct dispatchers, it is recommended to override this method
131+
* and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
132+
*
133+
* Implementations of this method are allowed to return `this` if the current dispatchercan satisfy the requested parallelism,
134+
* for example when the maximal parallelsm provided by the dispatcher is less than the requested one.
135+
*
136+
* @throws IllegalArgumentException if the given [parallelism] is non-positive
137+
* @throws UnsupportedOperationException if the current dispatcher does not support limited parallelism views
99138
*/
100139
@ExperimentalCoroutinesApi
101140
public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher {

0 commit comments

Comments
 (0)