Documentation
¶
Overview ¶
Package taskgroup manages collections of cooperating goroutines. It defines a Group that handles waiting for goroutine termination and the propagation of error values. The caller may provide a callback to filter and respond to task errors.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Collector ¶ added in v0.7.0
type Collector[T any] struct { // contains filtered or unexported fields }
A Collector collects values reported by task functions and delivers them to an accumulator function.
Example ¶
package main
import (
"errors"
"fmt"
"math/rand"
"github.com/creachadair/taskgroup"
)
func shuffled(n int) []int {
vs := make([]int, n)
for i := range vs {
vs[i] = i + 1
}
rand.Shuffle(n, func(i, j int) {
vs[i], vs[j] = vs[j], vs[i]
})
return vs
}
func main() {
var total int
c := taskgroup.NewCollector[int](func(v int) {
total += v
})
const numTasks = 25
input := shuffled(500)
// Start a bunch of tasks to find elements in the input...
g := taskgroup.New(nil)
for i := 0; i < numTasks; i++ {
target := i + 1
g.Go(c.Task(func() (int, error) {
for _, v := range input {
if v == target {
return v, nil
}
}
return 0, errors.New("not found")
}))
}
// Wait for the searchers to finish, then signal the collector to stop.
g.Wait()
c.Wait()
// Now get the final result.
fmt.Println(total)
}
Output: 325
func NewCollector ¶ added in v0.7.0
NewCollector creates a new collector that delivers task values to the specified accumulator function. The collector serializes calls to value, so that it is safe for the function to access shared state without a lock. The caller must call Wait when the collector is no longer needed, even if it has not been used.
func (*Collector[T]) NoError ¶ added in v0.7.0
NoError returns a Task wrapping a call to f. The resulting task reports a nil error for all calls.
func (*Collector[T]) Stream ¶ added in v0.7.1
Stream returns a task wrapping a call to f, which is passed a channel on which results can be sent to the accumulator.
Note: f must not close its argument channel.
type ErrorFunc ¶
An ErrorFunc is called by a group each time a task reports an error. Its return value replaces the reported error, so the ErrorFunc can filter or suppress errors by modifying or discarding the input error.
func Listen ¶
Listen creates an ErrorFunc that reports each non-nil task error to f. The resulting ErrorFunc returns task errors unmodified.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/creachadair/taskgroup"
)
func main() {
g := taskgroup.New(taskgroup.Listen(func(e error) {
fmt.Println(e)
}))
g.Go(func() error { return errors.New("heard you") })
fmt.Println(g.Wait()) // the error was preserved
}
Output: heard you heard you
func Trigger ¶
func Trigger(f func()) ErrorFunc
Trigger creates an ErrorFunc that calls f each time a task reports an error. The resulting ErrorFunc returns task errors unmodified.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/creachadair/taskgroup"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const badTask = 5
// Construct a group in which any task error cancels the context.
g := taskgroup.New(taskgroup.Trigger(cancel))
for i := 0; i < 10; i++ {
i := i
g.Go(func() error {
if i == badTask {
return fmt.Errorf("task %d failed", i)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(10 * time.Second):
return nil
}
})
}
if err := g.Wait(); err == nil {
log.Fatal("I expected an error here")
} else {
fmt.Println(err.Error())
}
}
Output: task 5 failed
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
A Group manages a collection of cooperating goroutines. New tasks are added to the group with the Go method. Call the Wait method to wait for the tasks to complete. A zero value is ready for use, but must not be copied after its first use.
The group collects any errors returned by the tasks in the group. The first non-nil error reported by any task (and not otherwise filtered) is returned from the Wait method.
Example ¶
package main
import (
"fmt"
"github.com/creachadair/taskgroup"
)
func main() {
msg := make(chan string)
g := taskgroup.New(nil)
g.Go(func() error {
msg <- "ping"
fmt.Println(<-msg)
return nil
})
g.Go(func() error {
fmt.Println(<-msg)
msg <- "pong"
return nil
})
g.Wait()
fmt.Println("<done>")
}
Output: ping pong <done>
func New ¶
New constructs a new empty group. If ef != nil, it is called for each error reported by a task running in the group. The value returned by ef replaces the task's error. If ef == nil, errors are not filtered.
Calls to ef are issued by a single goroutine, so it is safe for ef to manipulate local data structures without additional locking.
func (*Group) Limit ¶
Limit returns g and a function that starts each task passed to it in g, allowing no more than n tasks to be active concurrently. If n ≤ 0, the start function is equivalent to g.Go, which enforces no limit.
The limiting mechanism is optional, and the underlying group is not restricted. A call to the start function will block until a slot is available, but calling g.Go directly will add a task unconditionally and will not take up a limiter slot.
Example ¶
var p peakValue
g, start := taskgroup.New(nil).Limit(4)
for i := 0; i < 100; i++ {
start(func() error {
p.inc()
defer p.dec()
time.Sleep(1 * time.Microsecond)
return nil
})
}
g.Wait()
fmt.Printf("Max active ≤ 4: %v\n", p.max <= 4)
Output: Max active ≤ 4: true
func (*Group) Wait ¶
Wait blocks until all the goroutines currently active in the group have returned, and all reported errors have been delivered to the callback. It returns the first non-nil error returned by any of the goroutines in the group and not filtered by an ErrorFunc.
It is safe to call Wait concurrently from multiple goroutines, but as with sync.WaitGroup no tasks can be added to g while any call to Wait is in progress. Once all Wait calls have returned, the group is ready for reuse.
type Single ¶ added in v0.4.0
type Single[T any] struct { // contains filtered or unexported fields }
A single manages a single background goroutine. The task is started when the value is first created, and the caller can use the Wait method to block until it has exited.
Example ¶
package main
import (
"fmt"
"io"
"log"
"time"
"github.com/creachadair/taskgroup"
)
type slowReader struct {
n int
d time.Duration
}
func (s *slowReader) Read(data []byte) (int, error) {
if s.n == 0 {
return 0, io.EOF
}
time.Sleep(s.d)
nr := min(len(data), s.n)
s.n -= nr
for i := 0; i < nr; i++ {
data[i] = 'x'
}
return nr, nil
}
func main() {
// A fake reader to simulate a slow file read.
// 2500 bytes and each read takes 50ms.
sr := &slowReader{2500, 50 * time.Millisecond}
var data []byte
// Start a task to read te "file" in the background.
fmt.Println("start")
s := taskgroup.Go(func() error {
var err error
data, err = io.ReadAll(sr)
return err
})
fmt.Println("work, work")
if err := s.Wait(); err != nil {
log.Fatalf("Read failed: %v", err)
}
fmt.Println("done")
fmt.Println(len(data), "bytes")
}
Output: start work, work done 2500 bytes
func Call ¶ added in v0.6.0
Call starts task in a new goroutine. The caller must call Wait to wait for the task to return and collect its result.