Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/neilotoole/errgroup
errgroup with goroutine worker limits
https://github.com/neilotoole/errgroup
errgroup go golang goroutine goroutine-pool pool sync workerpool
Last synced: 6 days ago
JSON representation
errgroup with goroutine worker limits
- Host: GitHub
- URL: https://github.com/neilotoole/errgroup
- Owner: neilotoole
- License: mit
- Created: 2020-06-26T06:07:39.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-01-10T16:32:04.000Z (almost 2 years ago)
- Last Synced: 2024-07-31T20:51:54.125Z (5 months ago)
- Topics: errgroup, go, golang, goroutine, goroutine-pool, pool, sync, workerpool
- Language: Go
- Homepage:
- Size: 24.4 KB
- Stars: 159
- Watchers: 5
- Forks: 11
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-go - neilotoole/errgroup - Drop-in alternative to `sync/errgroup`, limited to a pool of N worker goroutines. (Goroutines / Search and Analytic Databases)
- awesome-go-extra - errgroup - 06-26T06:07:39Z|2022-04-01T10:03:37Z| (Goroutines / Advanced Console UIs)
README
[![Actions Status](https://github.com/neilotoole/errgroup/workflows/Go/badge.svg)](https://github.com/neilotoole/errgroup/actions?query=workflow%3AGo)
[![Go Report Card](https://goreportcard.com/badge/neilotoole/errgroup)](https://goreportcard.com/report/neilotoole/errgroup)
[![release](https://img.shields.io/badge/release-v0.1.5-green.svg)](https://github.com/neilotoole/errgroup/releases/tag/v0.1.5)
[![Coverage](https://gocover.io/_badge/github.com/neilotoole/errgroup)](https://gocover.io/github.com/neilotoole/errgroup)
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/neilotoole/errgroup)
[![license](https://img.shields.io/github/license/neilotoole/errgroup)](./LICENSE)# neilotoole/errgroup
`neilotoole/errgroup` is a drop-in alternative to Go's wonderful
[`sync/errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) but
limited to `N` goroutines. This is useful for interaction with rate-limited
APIs, databases, and the like.> **Note**
> The `sync/errgroup` package [now](https://github.com/neilotoole/errgroup/issues/14) has a [Group.SetLimit](https://pkg.go.dev/golang.org/x/sync/errgroup#Group.SetLimit) method,
> which eliminates the need for `neilotoole/errgroup`. This package will no longer be maintained. Use `sync/errgroup` instead.## Overview
In effect, `neilotoole/errgroup` is `sync/errgroup` but with a worker pool
of `N` goroutines. The exported API is identical but for an additional
function `WithContextN`, which allows the caller
to specify the maximum number of goroutines (`numG`) and the capacity
of the queue channel (`qSize`) used to hold work before it is picked
up by a worker goroutine. The zero `Group` and the `Group` returned
by `WithContext` have `numG` and `qSize` equal to `runtime.NumCPU`.## Usage
The exported API of this package mirrors the `sync/errgroup` package.
The only change needed is the import path of the package, from:```go
import (
"golang.org/x/sync/errgroup"
)
```to
```go
import (
"github.com/neilotoole/errgroup"
)
```Then use in the normal manner. See the [godoc](https://pkg.go.dev/github.com/neilotoole/errgroup) for more.
```go
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
// do something
return nil
})err := g.Wait()
```Many users will have no need to tweak the `numG` and `qCh` params. However, benchmarking
may suggest particular values for your workload. For that you'll need `WithContextN`:```go
numG, qSize := 8, 4
g, ctx := errgroup.WithContextN(ctx, numG, qSize)```
## Performance
The motivation for creating `neilotoole/errgroup` was to provide rate-limiting while
maintaining the lovely `sync/errgroup` semantics. Sacrificing some
performance vs `sync/errgroup` was assumed. However, benchmarking
suggests that this implementation can be more effective than `sync/errgroup`
when tuned for a specific workload.Below is a selection of benchmark results. How to read this: a workload is _X_ tasks
of _Y_ complexity. The workload is executed for:
- `sync/errgroup`, listed as `sync_errgroup`
- a non-parallel implementation (`sequential`)
- various `{numG, qSize}` configurations of `neilotoole/errgroup`, listed as `errgroupn_{numG}_{qSize}````
BenchmarkGroup_Short/complexity_5/tasks_50/errgroupn_default_16_16-16 25574 46867 ns/op 688 B/op 12 allocs/op
BenchmarkGroup_Short/complexity_5/tasks_50/errgroupn_4_4-16 24908 48926 ns/op 592 B/op 12 allocs/op
BenchmarkGroup_Short/complexity_5/tasks_50/errgroupn_16_4-16 24895 48313 ns/op 592 B/op 12 allocs/op
BenchmarkGroup_Short/complexity_5/tasks_50/errgroupn_32_4-16 24853 48284 ns/op 592 B/op 12 allocs/op
BenchmarkGroup_Short/complexity_5/tasks_50/sync_errgroup-16 18784 65826 ns/op 1858 B/op 55 allocs/op
BenchmarkGroup_Short/complexity_5/tasks_50/sequential-16 10000 111483 ns/op 0 B/op 0 allocs/opBenchmarkGroup_Short/complexity_20/tasks_50/errgroupn_default_16_16-16 3745 325993 ns/op 1168 B/op 27 allocs/op
BenchmarkGroup_Short/complexity_20/tasks_50/errgroupn_4_4-16 5186 227034 ns/op 1072 B/op 27 allocs/op
BenchmarkGroup_Short/complexity_20/tasks_50/errgroupn_16_4-16 3970 312816 ns/op 1076 B/op 27 allocs/op
BenchmarkGroup_Short/complexity_20/tasks_50/errgroupn_32_4-16 3715 320757 ns/op 1073 B/op 27 allocs/op
BenchmarkGroup_Short/complexity_20/tasks_50/sync_errgroup-16 2739 432093 ns/op 1862 B/op 55 allocs/op
BenchmarkGroup_Short/complexity_20/tasks_50/sequential-16 2306 520947 ns/op 0 B/op 0 allocs/opBenchmarkGroup_Short/complexity_40/tasks_250/errgroupn_default_16_16-16 354 3602666 ns/op 1822 B/op 47 allocs/op
BenchmarkGroup_Short/complexity_40/tasks_250/errgroupn_4_4-16 420 2468605 ns/op 1712 B/op 47 allocs/op
BenchmarkGroup_Short/complexity_40/tasks_250/errgroupn_16_4-16 334 3581349 ns/op 1716 B/op 47 allocs/op
BenchmarkGroup_Short/complexity_40/tasks_250/errgroupn_32_4-16 310 3890316 ns/op 1712 B/op 47 allocs/op
BenchmarkGroup_Short/complexity_40/tasks_250/sync_errgroup-16 253 4740462 ns/op 8303 B/op 255 allocs/op
BenchmarkGroup_Short/complexity_40/tasks_250/sequential-16 200 5924693 ns/op 0 B/op 0 allocs/op
```The overall impression is that `neilotoole/errgroup` can provide higher
throughput than `sync/errgroup` for these (CPU-intensive) workloads,
sometimes significantly so. As always, these benchmark results should
not be taken as gospel: your results may vary.## Design Note
Why require an explicit `qSize` limit?If the number of calls to `Group.Go` results in `qCh` becoming
full, the `Go` method will block until worker goroutines relieve `qCh`.
This behavior is in contrast to `sync/errgroup`'s `Go` method, which doesn't block.
While `neilotoole/errgroup` aims to be as much of a behaviorally similar
"drop-in" alternative to `sync/errgroup` as possible, this blocking behavior
is a conscious deviation.Noting that the capacity of `qCh` is controlled by `qSize`, it's probable an
alternative implementation could be built that uses a (growable) slice
acting - if `qCh` is full - as a buffer for functions passed to `Go`.
Consideration of this potential design led to this [issue](https://github.com/golang/go/issues/20352)
regarding _unlimited capacity channels_, or perhaps better characterized
in this particular case as "_growable capacity channels_". If such a
feature existed in the language, it's possible that this implementation might
have taken advantage of it, at least in the first-pass release (benchmarking notwithstanding).
However benchmarking seems to suggest that a relatively
small `qSize` has performance benefits for some workloads, so it's possible
that the explicit `qSize` requirement is a better design choice regardless.