Skip to content

Commit 1688204

Browse files
committed
Update best practices usage for Barrier.
1 parent 7c0cbdf commit 1688204

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

guides/best-practices/readme.md

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,24 @@ This expresses the intent to the caller that this method should only be invoked
5555

5656
## Use barriers to manage unbounded concurrency
5757

58-
Barriers provide a way to manage an unbounded number of tasks.
58+
Barriers provide a way to manage an unbounded number of tasks. The top-level `Barrier` method creates a barrier with built-in load management using an `Async::Idler`.
5959

6060
```ruby
61-
Async do
62-
barrier = Async::Barrier.new
63-
61+
Barrier do |barrier|
62+
items.each do |item|
63+
barrier.async do
64+
process(item)
65+
end
66+
end
67+
end
68+
```
69+
70+
The barrier will automatically wait for all tasks to complete and stop any outstanding tasks when the block exits. By default, it uses an `Async::Idler` to prevent system overload by scheduling tasks when the system load is below 80%.
71+
72+
If you want to process tasks in order of completion, you can explicitly call `wait` with a block:
73+
74+
```ruby
75+
Barrier do |barrier|
6476
items.each do |item|
6577
barrier.async do
6678
process(item)
@@ -75,46 +87,57 @@ Async do
7587
# If you don't want to wait for any more tasks you can break:
7688
break
7789
end
78-
79-
# Or just wait for all tasks to finish:
80-
barrier.wait # May raise an exception if a task failed.
81-
ensure
82-
# Stop all outstanding tasks in the barrier:
83-
barrier&.stop
90+
end
91+
```
92+
93+
To disable load management (not recommended for unbounded concurrency), you can pass `parent: nil`:
94+
95+
```ruby
96+
Barrier(parent: nil) do |barrier|
97+
# No load management - creates tasks as fast as possible
98+
items.each do |item|
99+
barrier.async do
100+
process(item)
101+
end
102+
end
84103
end
85104
```
86105

87106
## Use a semaphore to limit the number of concurrent tasks
88107

89-
Semaphores allow you to limit the level of concurrency to a fixed number of tasks:
108+
Semaphores allow you to limit the level of concurrency to a fixed number of tasks. When using semaphores with barriers, the barrier should be the root of your task hierarchy, and the semaphore should be a child of the barrier:
90109

91110
```ruby
92-
Async do |task|
93-
barrier = Async::Barrier.new
111+
Barrier(parent: nil) do |barrier|
94112
semaphore = Async::Semaphore.new(4, parent: barrier)
95113

96-
# Since the semaphore.async may block, we need to run the work scheduling in a child task:
97-
task.async do
98-
items.each do |item|
99-
semaphore.async do
100-
process(item)
101-
end
114+
items.each do |item|
115+
semaphore.async do
116+
process(item)
102117
end
103118
end
104-
105-
# Wait for all the work to complete:
106-
barrier.wait
107-
ensure
108-
# Stop all outstanding tasks in the barrier:
109-
barrier&.stop
110119
end
111120
```
112121

113-
In general, the barrier should be the root of your task hierarchy, and the semaphore should be a child of the barrier. This allows you to manage the lifetime of all tasks created by the semaphore, and ensures that all tasks are stopped when the barrier is stopped.
122+
In this example, we use `parent: nil` for the barrier to disable load management, since the semaphore already provides concurrency control. The semaphore limits execution to 4 concurrent tasks, and the barrier ensures all tasks are stopped when the block exits.
114123

115124
### Idler
116125

117-
Idlers are like semaphores but with a limit defined by current processor utilization. In other words, an idler will do work up to a specific ratio of idle/busy time in the scheduler, and try to maintain that.
126+
Idlers are like semaphores but with a limit defined by current processor utilization. In other words, an idler will schedule work up to a specific ratio of idle/busy time in the scheduler.
127+
128+
The top-level `Barrier` method uses an idler by default, making it safe for unbounded concurrency:
129+
130+
```ruby
131+
Barrier do |barrier| # Uses Async::Idler.new(0.8) by default
132+
work.each do |work|
133+
barrier.async do
134+
work.call
135+
end
136+
end
137+
end
138+
```
139+
140+
You can also use an idler directly without a barrier:
118141

119142
```ruby
120143
Async do

0 commit comments

Comments
 (0)