mirror of
https://github.com/panjf2000/ants.git
synced 2025-12-16 09:51:02 +00:00
commit
9c0876f0bc
13
.github/release-drafter.yml
vendored
13
.github/release-drafter.yml
vendored
@ -44,7 +44,7 @@ autolabeler:
|
||||
title:
|
||||
- /fix/i
|
||||
- /bug/i
|
||||
- /patch/i
|
||||
- /resolve/i
|
||||
- label: docs
|
||||
files:
|
||||
- '*.md'
|
||||
@ -60,6 +60,15 @@ autolabeler:
|
||||
- /update/i
|
||||
- /remove/i
|
||||
- /delete/i
|
||||
- label: optimization
|
||||
title:
|
||||
- /opt:/i
|
||||
- /refactor/i
|
||||
- /optimize/i
|
||||
- /improve/i
|
||||
- /update/i
|
||||
- /remove/i
|
||||
- /delete/i
|
||||
- label: new feature
|
||||
title:
|
||||
- /feat:/i
|
||||
@ -75,7 +84,7 @@ autolabeler:
|
||||
- label: chores
|
||||
title:
|
||||
- /chore/i
|
||||
- /\bmisc\b/i
|
||||
- /misc/i
|
||||
- /cleanup/i
|
||||
- /clean up/i
|
||||
- label: major
|
||||
|
||||
48
.github/workflows/stale-bot.yml
vendored
Normal file
48
.github/workflows/stale-bot.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
name: Monitor inactive issues and PRs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
operations-per-run: 50
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 7
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-message: |
|
||||
This issue is marked as stale because it has been open for 30 days with no activity.
|
||||
|
||||
You should take one of the following actions:
|
||||
- Manually close this issue if it is no longer relevant
|
||||
- Comment if you have more information to share
|
||||
|
||||
This issue will be automatically closed in 7 days if no further activity occurs.
|
||||
close-issue-message: |
|
||||
This issue was closed because it has been inactive for 7 days since being marked as stale.
|
||||
|
||||
If you believe this is a false alarm, please leave a comment for it or open a new issue, you can also reopen this issue directly if you have permission.
|
||||
days-before-pr-stale: 21
|
||||
days-before-pr-close: 7
|
||||
stale-pr-label: 'stale'
|
||||
stale-pr-message: |
|
||||
This PR is marked as stale because it has been open for 21 days with no activity.
|
||||
|
||||
You should take one of the following actions:
|
||||
- Manually close this PR if it is no longer relevant
|
||||
- Push new commits or comment if you have more information to share
|
||||
|
||||
This PR will be automatically closed in 7 days if no further activity occurs.
|
||||
close-pr-message: |
|
||||
This PR was closed because it has been inactive for 7 days since being marked as stale.
|
||||
|
||||
If you believe this is a false alarm, feel free to reopen this PR or create a new one.
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@ -42,35 +42,21 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
|
||||
- name: Setup and run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.57.2
|
||||
version: v1.62.2
|
||||
args: --timeout 5m -v -E gofumpt -E gocritic -E misspell -E revive -E godot
|
||||
test:
|
||||
needs: lint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [1.13, 1.22]
|
||||
go: [1.18, 1.23]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
include:
|
||||
# TODO(panjf2000): There is an uncanny issue arising when downloading
|
||||
# go modules on macOS 13 for Go1.13. So we use macOS 12 for now,
|
||||
# but try to figure it out and use macOS once it's resolved.
|
||||
# https://github.com/panjf2000/ants/actions/runs/9546726268/job/26310385582
|
||||
- go: 1.13
|
||||
os: macos-12
|
||||
exclude:
|
||||
# Starting macOS 14 GitHub Actions runners are arm-based,
|
||||
# but Go didn't support arm64 until 1.16. Thus, we must
|
||||
# replace the macOS 14 runner with macOS 12 runner for Go 1.13.
|
||||
# Ref: https://github.com/actions/runner-images/issues/9741
|
||||
- go: 1.13
|
||||
os: macos-latest
|
||||
name: Go ${{ matrix.go }} @ ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os}}
|
||||
steps:
|
||||
@ -101,7 +87,7 @@ jobs:
|
||||
run: go test -v -race -coverprofile="codecov.report" -covermode=atomic
|
||||
|
||||
- name: Upload code coverage report to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./codecov.report
|
||||
flags: unittests
|
||||
|
||||
309
README.md
309
README.md
@ -7,6 +7,7 @@
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
|
||||
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
|
||||
<br/>
|
||||
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square&logo=go" /></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
|
||||
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
|
||||
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
|
||||
@ -22,10 +23,11 @@ Library `ants` implements a goroutine pool with fixed capacity, managing and rec
|
||||
|
||||
- Managing and recycling a massive number of goroutines automatically
|
||||
- Purging overdue goroutines periodically
|
||||
- Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool
|
||||
- Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool, etc.
|
||||
- Handle panic gracefully to prevent programs from crash
|
||||
- Efficient in memory usage and it even achieves [higher performance](#-performance-summary) than unlimited goroutines in Golang
|
||||
- Efficient in memory usage and it may even achieve ***higher performance*** than unlimited goroutines in Go
|
||||
- Nonblocking mechanism
|
||||
- Preallocated memory (ring buffer, optional)
|
||||
|
||||
## 💡 How `ants` works
|
||||
|
||||
@ -60,205 +62,30 @@ go get -u github.com/panjf2000/ants/v2
|
||||
```
|
||||
|
||||
## 🛠 How to use
|
||||
Just imagine that your program starts a massive number of goroutines, resulting in a huge consumption of memory. To mitigate that kind of situation, all you need to do is to import `ants` package and submit all your tasks to a default pool with fixed capacity, activated when package `ants` is imported:
|
||||
Check out [the examples](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples) for basic usage.
|
||||
|
||||
``` go
|
||||
package main
|
||||
### Functional options for pool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
`ants.Options`contains all optional configurations of the ants pool, which allows you to customize the goroutine pool by invoking option functions to set up each configuration in `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` method.
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
Check out [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) and [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option) for more details.
|
||||
|
||||
var sum int32
|
||||
### Customize pool capacity
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a function,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
|
||||
// Use the MultiPool and set the capacity of the 10 goroutine pools to unlimited.
|
||||
// If you use -1 as the pool size parameter, the size will be unlimited.
|
||||
// There are two load-balancing algorithms for pools: ants.RoundRobin and ants.LeastTasks.
|
||||
mp, _ := ants.NewMultiPool(10, -1, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mp.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mp.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the MultiPoolFunc and set the capacity of 10 goroutine pools to (runTimes/10).
|
||||
mpf, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
}, ants.LeastTasks)
|
||||
defer mpf.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mpf.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mpf.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500*2 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Functional options for ants pool
|
||||
|
||||
```go
|
||||
// Option represents the optional function.
|
||||
type Option func(opts *Options)
|
||||
|
||||
// Options contains all options which will be applied when instantiating a ants pool.
|
||||
type Options struct {
|
||||
// ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers,
|
||||
// the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been
|
||||
// used for more than `ExpiryDuration`.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicates whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
|
||||
// Logger is the customized logger for logging info, if it is not set,
|
||||
// default standard logger from log package is used.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// WithOptions accepts the whole options config.
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
// WithExpiryDuration sets up the interval time of cleaning up goroutines.
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithPreAlloc indicates whether it should malloc for workers.
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool.
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonblocking indicates that pool will return nil when there is no available workers.
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
// WithPanicHandler sets up panic handler.
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets up a customized logger.
|
||||
func WithLogger(logger Logger) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Logger = logger
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ants.Options`contains all optional configurations of the ants pool, which allows you to customize the goroutine pool by invoking option functions to set up each configuration in `NewPool`/`NewPoolWithFunc`method.
|
||||
|
||||
### Customize limited pool
|
||||
|
||||
`ants` also supports customizing the capacity of the pool. You can invoke the `NewPool` method to instantiate a pool with a given capacity, as follows:
|
||||
`ants` supports customizing the capacity of the pool. You can call the `NewPool` method to instantiate a `Pool` with a given capacity, as follows:
|
||||
|
||||
``` go
|
||||
p, _ := ants.NewPool(10000)
|
||||
```
|
||||
|
||||
### Submit tasks
|
||||
Tasks can be submitted by calling `ants.Submit(func())`
|
||||
Tasks can be submitted by calling `ants.Submit`
|
||||
```go
|
||||
ants.Submit(func(){})
|
||||
```
|
||||
|
||||
### Tune pool capacity in runtime
|
||||
You can tune the capacity of `ants` pool in runtime with `Tune(int)`:
|
||||
### Tune pool capacity at runtime
|
||||
You can tune the capacity of `ants` pool at runtime with `ants.Tune`:
|
||||
|
||||
``` go
|
||||
pool.Tune(1000) // Tune its capacity to 1000
|
||||
@ -272,20 +99,26 @@ Don't worry about the contention problems in this case, the method here is threa
|
||||
`ants` allows you to pre-allocate the memory of the goroutine queue in the pool, which may get a performance enhancement under some special certain circumstances such as the scenario that requires a pool with ultra-large capacity, meanwhile, each task in goroutine lasts for a long time, in this case, pre-mallocing will reduce a lot of memory allocation in goroutine queue.
|
||||
|
||||
```go
|
||||
// ants will pre-malloc the whole capacity of pool when you invoke this method
|
||||
// ants will pre-malloc the whole capacity of pool when calling ants.NewPool.
|
||||
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
```
|
||||
|
||||
### Release Pool
|
||||
### Release pool
|
||||
|
||||
```go
|
||||
pool.Release()
|
||||
```
|
||||
|
||||
### Reboot Pool
|
||||
or
|
||||
|
||||
```go
|
||||
// A pool that has been released can be still used once you invoke the Reboot().
|
||||
pool.ReleaseTimeout(time.Second * 3)
|
||||
```
|
||||
|
||||
### Reboot pool
|
||||
|
||||
```go
|
||||
// A pool that has been released can be still used after calling the Reboot().
|
||||
pool.Reboot()
|
||||
```
|
||||
|
||||
@ -314,20 +147,20 @@ The source code in `ants` is available under the [MIT License](/LICENSE).
|
||||
|
||||
## 🖥 Use cases
|
||||
|
||||
### business companies
|
||||
### business corporations
|
||||
|
||||
The following companies/organizations use `ants` in production.
|
||||
Trusted by the following corporations/organizations.
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencent.com">
|
||||
<a href="https://www.tencent.com/">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytedance.com/" target="_blank">
|
||||
<a href="https://www.bytedance.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
@ -344,12 +177,12 @@ The following companies/organizations use `ants` in production.
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencentmusic.com/" target="_blank">
|
||||
<a href="https://www.tencentmusic.com/en-us/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent-music-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.futuhk.com/" target="_blank">
|
||||
<a href="https://www.futuhk.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
@ -367,11 +200,11 @@ The following companies/organizations use `ants` in production.
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile.png" width="250" />
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.360.com" target="_blank">
|
||||
<a href="https://www.360.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
@ -381,43 +214,56 @@ The following companies/organizations use `ants` in production.
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.matrixorigin.io" target="_blank">
|
||||
<a href="https://www.matrixorigin.io/" target="_blank">
|
||||
<img src="https://www.matrixorigin.io/_next/static/media/logo-light-en.42553c69.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://adguard-dns.io" target="_blank">
|
||||
<a href="https://adguard-dns.io/" target="_blank">
|
||||
<img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://bk.tencent.com" target="_blank">
|
||||
<a href="https://bk.tencent.com/" target="_blank">
|
||||
<img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.alibabacloud.com" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-intl.png" width="250" />
|
||||
<a href="https://www.alibabacloud.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-intl-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.zuoyebang.com" target="_blank">
|
||||
<a href="https://www.zuoyebang.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.antgroup.com/en" target="_blank">
|
||||
<img src="https://gw.alipayobjects.com/mdn/rms_27e257/afts/img/A*PLZaSZnCPAwAAAAAAAAAAAAAARQnAQ" width="250" />
|
||||
<a href="https://www.antgroup.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://zilliz.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://amap.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
If you're also using `ants` in production, please help us enrich this list by opening a pull request.
|
||||
|
||||
### open-source software
|
||||
|
||||
The open-source projects below do concurrent programming with the help of `ants`.
|
||||
@ -425,6 +271,7 @@ The open-source projects below do concurrent programming with the help of `ants`
|
||||
- [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
|
||||
- [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications.
|
||||
- [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
|
||||
- [TDengine](https://github.com/taosdata/TDengine): TDengine is an open source, high-performance, cloud native time-series database optimized for Internet of Things (IoT), Connected Cars, and Industrial IoT.
|
||||
- [siyuan](https://github.com/siyuan-note/siyuan): SiYuan is a local-first personal knowledge management system that supports complete offline use, as well as end-to-end encrypted synchronization.
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
|
||||
@ -441,7 +288,7 @@ The open-source projects below do concurrent programming with the help of `ants`
|
||||
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
|
||||
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
|
||||
- [trpc-go](https://github.com/trpc-group/trpc-go): A pluggable, high-performance RPC framework written in Golang.
|
||||
- [motan-go](https://github.com/weibocom/motan-go): a remote procedure call (RPC) framework for the rapid development of high-performance distributed services.
|
||||
- [motan-go](https://github.com/weibocom/motan-go): Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services. motan-go is the golang implementation of Motan.
|
||||
|
||||
#### All use cases:
|
||||
|
||||
@ -453,9 +300,9 @@ If you have `ants` integrated into projects, feel free to open a pull request re
|
||||
|
||||
## 🔋 JetBrains OS licenses
|
||||
|
||||
`ants` had been being developed with GoLand under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here.
|
||||
`ants` has been being developed with GoLand under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here.
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>
|
||||
|
||||
## 💰 Backers
|
||||
|
||||
@ -477,50 +324,6 @@ Become a bronze sponsor with a monthly donation of $10 and get your logo on our
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/AliPay.JPG" width="250" align="middle"/>
|
||||
<a href="https://www.paypal.me/R136a1X" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/PayPal.JPG" width="250" align="middle"/></a>
|
||||
|
||||
## 💵 Patrons
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/patrick-othmer">
|
||||
<img src="https://avatars1.githubusercontent.com/u/8964313" width="100" alt="Patrick Othmer" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/panjf2000/ants">
|
||||
<img src="https://avatars2.githubusercontent.com/u/50285334" width="100" alt="Jimmy" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/cafra">
|
||||
<img src="https://avatars0.githubusercontent.com/u/13758306" width="100" alt="ChenZhen" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/yangwenmai">
|
||||
<img src="https://avatars0.githubusercontent.com/u/1710912" width="100" alt="Mai Yang" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/BeijingWks">
|
||||
<img src="https://avatars3.githubusercontent.com/u/33656339" width="100" alt="王开帅" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/refs">
|
||||
<img src="https://avatars3.githubusercontent.com/u/6905948" width="100" alt="Unger Alejandro" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/Wuvist">
|
||||
<img src="https://avatars.githubusercontent.com/u/657796" width="100" alt="Weng Wei" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔋 Sponsorship
|
||||
|
||||
<p>
|
||||
|
||||
292
README_ZH.md
292
README_ZH.md
@ -7,6 +7,7 @@
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
|
||||
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
|
||||
<br/>
|
||||
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square&logo=go" /></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
|
||||
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
|
||||
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
|
||||
@ -16,16 +17,17 @@
|
||||
|
||||
## 📖 简介
|
||||
|
||||
`ants`是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。
|
||||
`ants` 是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。
|
||||
|
||||
## 🚀 功能:
|
||||
|
||||
- 自动调度海量的 goroutines,复用 goroutines
|
||||
- 定期清理过期的 goroutines,进一步节省资源
|
||||
- 提供了大量有用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool
|
||||
- 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等
|
||||
- 优雅处理 panic,防止程序崩溃
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下比原生 goroutine 并发具有[更高的性能](#-性能小结)
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能***
|
||||
- 非阻塞机制
|
||||
- 预分配内存 (环形队列,可选)
|
||||
|
||||
## 💡 `ants` 是如何运行的
|
||||
|
||||
@ -60,192 +62,17 @@ go get -u github.com/panjf2000/ants/v2
|
||||
```
|
||||
|
||||
## 🛠 使用
|
||||
写 go 并发程序的时候如果程序会启动大量的 goroutine ,势必会消耗大量的系统资源(内存,CPU),通过使用 `ants`,可以实例化一个 goroutine 池,复用 goroutine ,节省资源,提升性能:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var sum int32
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a function,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
|
||||
// Use the MultiPool and set the capacity of the 10 goroutine pools to unlimited.
|
||||
// If you use -1 as the pool size parameter, the size will be unlimited.
|
||||
// There are two load-balancing algorithms for pools: ants.RoundRobin and ants.LeastTasks.
|
||||
mp, _ := ants.NewMultiPool(10, -1, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mp.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mp.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the MultiPoolFunc and set the capacity of 10 goroutine pools to (runTimes/10).
|
||||
mpf, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
}, ants.LeastTasks)
|
||||
defer mpf.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mpf.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mpf.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500*2 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
}
|
||||
```
|
||||
基本的使用请查看[示例](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples).
|
||||
|
||||
### Pool 配置
|
||||
|
||||
```go
|
||||
// Option represents the optional function.
|
||||
type Option func(opts *Options)
|
||||
通过在调用 `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` 之时使用各种 optional function,可以设置 `ants.Options` 中各个配置项的值,然后用它来定制化 goroutine pool。
|
||||
|
||||
// Options contains all options which will be applied when instantiating a ants pool.
|
||||
type Options struct {
|
||||
// ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers,
|
||||
// the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been
|
||||
// used for more than `ExpiryDuration`.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicates whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
|
||||
// Logger is the customized logger for logging info, if it is not set,
|
||||
// default standard logger from log package is used.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// WithOptions accepts the whole options config.
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
// WithExpiryDuration sets up the interval time of cleaning up goroutines.
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithPreAlloc indicates whether it should malloc for workers.
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool.
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonblocking indicates that pool will return nil when there is no available workers.
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
// WithPanicHandler sets up panic handler.
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets up a customized logger.
|
||||
func WithLogger(logger Logger) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Logger = logger
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过在调用`NewPool`/`NewPoolWithFunc`之时使用各种 optional function,可以设置`ants.Options`中各个配置项的值,然后用它来定制化 goroutine pool.
|
||||
更多细节请查看 [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) 和 [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option)
|
||||
|
||||
|
||||
### 自定义池
|
||||
`ants`支持实例化使用者自己的一个 Pool ,指定具体的池容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 Pool ,如下:
|
||||
### 自定义 pool 容量
|
||||
`ants` 支持实例化使用者自己的一个 Pool,指定具体的 pool 容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 `Pool`,如下:
|
||||
|
||||
``` go
|
||||
p, _ := ants.NewPool(10000)
|
||||
@ -253,13 +80,13 @@ p, _ := ants.NewPool(10000)
|
||||
|
||||
### 任务提交
|
||||
|
||||
提交任务通过调用 `ants.Submit(func())`方法:
|
||||
提交任务通过调用 `ants.Submit` 方法:
|
||||
```go
|
||||
ants.Submit(func(){})
|
||||
```
|
||||
|
||||
### 动态调整 goroutine 池容量
|
||||
需要动态调整 goroutine 池容量可以通过调用`Tune(int)`:
|
||||
需要动态调整 pool 容量可以通过调用 `ants.Tune`:
|
||||
|
||||
``` go
|
||||
pool.Tune(1000) // Tune its capacity to 1000
|
||||
@ -270,10 +97,10 @@ pool.Tune(100000) // Tune its capacity to 100000
|
||||
|
||||
### 预先分配 goroutine 队列内存
|
||||
|
||||
`ants`允许你预先把整个池的容量分配内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。
|
||||
`ants` 支持预先为 pool 分配容量的内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。
|
||||
|
||||
```go
|
||||
// ants will pre-malloc the whole capacity of pool when you invoke this function
|
||||
// 提前分配的 pool 容量的内存空间
|
||||
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
```
|
||||
|
||||
@ -283,6 +110,12 @@ p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
pool.Release()
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```go
|
||||
pool.ReleaseTimeout(time.Second * 3)
|
||||
```
|
||||
|
||||
### 重启 Pool
|
||||
|
||||
```go
|
||||
@ -323,7 +156,7 @@ pool.Reboot()
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencent.com">
|
||||
<a href="https://www.tencent.com/">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
@ -368,57 +201,69 @@ pool.Reboot()
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile.png" width="250" />
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.360.com" target="_blank">
|
||||
<a href="https://www.360.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.huaweicloud.com" target="_blank">
|
||||
<a href="https://www.huaweicloud.com/" target="_blank">
|
||||
<img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/wangxue/header/logo.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://matrixorigin.cn" target="_blank">
|
||||
<a href="https://matrixorigin.cn/" target="_blank">
|
||||
<img src="https://matrixorigin.cn/_next/static/media/logo-light-zh.a2a8f3c0.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://adguard-dns.io" target="_blank">
|
||||
<a href="https://adguard-dns.io/" target="_blank">
|
||||
<img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://bk.tencent.com" target="_blank">
|
||||
<a href="https://bk.tencent.com/" target="_blank">
|
||||
<img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://cn.aliyun.com" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-cn.png" width="250" />
|
||||
<a href="https://cn.aliyun.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-cn-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.zuoyebang.com" target="_blank">
|
||||
<a href="https://www.zuoyebang.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.antgroup.com" target="_blank">
|
||||
<img src="https://gw.alipayobjects.com/mdn/rms_27e257/afts/img/A*PLZaSZnCPAwAAAAAAAAAAAAAARQnAQ" width="250" />
|
||||
<a href="https://www.antgroup.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://zilliz.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://amap.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。
|
||||
|
||||
### 开源软件
|
||||
|
||||
这些开源项目借助 `ants` 进行并发编程。
|
||||
@ -426,6 +271,7 @@ pool.Reboot()
|
||||
- [gnet](https://github.com/panjf2000/gnet): gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。
|
||||
- [milvus](https://github.com/milvus-io/milvus): 一个高度灵活、可靠且速度极快的云原生开源向量数据库。
|
||||
- [nps](https://github.com/ehang-io/nps): 一款轻量级、高性能、功能强大的内网穿透代理服务器。
|
||||
- [TDengine](https://github.com/taosdata/TDengine): TDengine 是一款开源、高性能、云原生的时序数据库 (Time-Series Database, TSDB)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。
|
||||
- [siyuan](https://github.com/siyuan-note/siyuan): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
|
||||
@ -442,7 +288,7 @@ pool.Reboot()
|
||||
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
|
||||
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
|
||||
- [trpc-go](https://github.com/trpc-group/trpc-go): 一个 Go 实现的可插拔的高性能 RPC 框架。
|
||||
- [motan-go](https://github.com/weibocom/motan-go): 一套高性能、易于使用的分布式远程服务调用(RPC)框架。motan-go 是 motan 的 Go 语言实现。
|
||||
- [motan-go](https://github.com/weibocom/motan-go): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。
|
||||
|
||||
#### 所有案例:
|
||||
|
||||
@ -456,7 +302,7 @@ pool.Reboot()
|
||||
|
||||
`ants` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>
|
||||
|
||||
## 💰 支持
|
||||
|
||||
@ -478,50 +324,6 @@ pool.Reboot()
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/AliPay.JPG" width="250" align="middle"/>
|
||||
<a href="https://www.paypal.me/R136a1X" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/PayPal.JPG" width="250" align="middle"/></a>
|
||||
|
||||
## 资助者
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/patrick-othmer">
|
||||
<img src="https://avatars1.githubusercontent.com/u/8964313" width="100" alt="Patrick Othmer" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/panjf2000/ants">
|
||||
<img src="https://avatars2.githubusercontent.com/u/50285334" width="100" alt="Jimmy" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/cafra">
|
||||
<img src="https://avatars0.githubusercontent.com/u/13758306" width="100" alt="ChenZhen" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/yangwenmai">
|
||||
<img src="https://avatars0.githubusercontent.com/u/1710912" width="100" alt="Mai Yang" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/BeijingWks">
|
||||
<img src="https://avatars3.githubusercontent.com/u/33656339" width="100" alt="王开帅" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/refs">
|
||||
<img src="https://avatars3.githubusercontent.com/u/6905948" width="100" alt="Unger Alejandro" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://github.com/Wuvist">
|
||||
<img src="https://avatars.githubusercontent.com/u/657796" width="100" alt="Weng Wei" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔋 赞助商
|
||||
|
||||
<p>
|
||||
|
||||
407
ants.go
407
ants.go
@ -20,15 +20,27 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Package ants implements an efficient and reliable goroutine pool for Go.
|
||||
//
|
||||
// With ants, Go applications are able to limit the number of active goroutines,
|
||||
// recycle goroutines efficiently, and reduce the memory footprint significantly.
|
||||
// Package ants is extremely useful in the scenarios where a massive number of
|
||||
// goroutines are created and destroyed frequently, such as highly-concurrent
|
||||
// batch processing systems, HTTP servers, services of asynchronous tasks, etc.
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
syncx "github.com/panjf2000/ants/v2/pkg/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -72,6 +84,9 @@ var (
|
||||
// ErrInvalidLoadBalancingStrategy will be returned when trying to create a MultiPool with an invalid load-balancing strategy.
|
||||
ErrInvalidLoadBalancingStrategy = errors.New("invalid load-balancing strategy")
|
||||
|
||||
// ErrInvalidMultiPoolSize will be returned when trying to create a MultiPool with an invalid size.
|
||||
ErrInvalidMultiPoolSize = errors.New("invalid size for multiple pool")
|
||||
|
||||
// workerChanCap determines whether the channel of a worker should be a buffered channel
|
||||
// to get the best performance. Inspired by fasthttp at
|
||||
// https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139
|
||||
@ -88,22 +103,12 @@ var (
|
||||
return 1
|
||||
}()
|
||||
|
||||
// log.Lmsgprefix is not available in go1.13, just make an identical value for it.
|
||||
logLmsgprefix = 64
|
||||
defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|logLmsgprefix|log.Lmicroseconds))
|
||||
defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds))
|
||||
|
||||
// Init an instance pool when importing ants.
|
||||
defaultAntsPool, _ = NewPool(DefaultAntsPoolSize)
|
||||
)
|
||||
|
||||
const nowTimeUpdateInterval = 500 * time.Millisecond
|
||||
|
||||
// Logger is used for logging formatted messages.
|
||||
type Logger interface {
|
||||
// Printf must have the same semantics as log.Printf.
|
||||
Printf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// Submit submits a task to pool.
|
||||
func Submit(task func()) error {
|
||||
return defaultAntsPool.Submit(task)
|
||||
@ -138,3 +143,383 @@ func ReleaseTimeout(timeout time.Duration) error {
|
||||
func Reboot() {
|
||||
defaultAntsPool.Reboot()
|
||||
}
|
||||
|
||||
// Logger is used for logging formatted messages.
|
||||
type Logger interface {
|
||||
// Printf must have the same semantics as log.Printf.
|
||||
Printf(format string, args ...any)
|
||||
}
|
||||
|
||||
// poolCommon contains all common fields for other sophisticated pools.
|
||||
type poolCommon struct {
|
||||
// capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to
|
||||
// avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool
|
||||
// which submits a new task to the same pool.
|
||||
capacity int32
|
||||
|
||||
// running is the number of the currently running goroutines.
|
||||
running int32
|
||||
|
||||
// lock for protecting the worker queue.
|
||||
lock sync.Locker
|
||||
|
||||
// workers is a slice that store the available workers.
|
||||
workers workerQueue
|
||||
|
||||
// state is used to notice the pool to closed itself.
|
||||
state int32
|
||||
|
||||
// cond for waiting to get an idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// done is used to indicate that all workers are done.
|
||||
allDone chan struct{}
|
||||
// once is used to make sure the pool is closed just once.
|
||||
once *sync.Once
|
||||
|
||||
// workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock
|
||||
waiting int32
|
||||
|
||||
purgeDone int32
|
||||
purgeCtx context.Context
|
||||
stopPurge context.CancelFunc
|
||||
|
||||
ticktockDone int32
|
||||
ticktockCtx context.Context
|
||||
stopTicktock context.CancelFunc
|
||||
|
||||
now atomic.Value
|
||||
|
||||
options *Options
|
||||
}
|
||||
|
||||
func newPool(size int, options ...Option) (*poolCommon, error) {
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
}
|
||||
|
||||
opts := loadOptions(options...)
|
||||
|
||||
if !opts.DisablePurge {
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = DefaultCleanIntervalTime
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = defaultLogger
|
||||
}
|
||||
|
||||
p := &poolCommon{
|
||||
capacity: int32(size),
|
||||
allDone: make(chan struct{}),
|
||||
lock: syncx.NewSpinLock(),
|
||||
once: &sync.Once{},
|
||||
options: opts,
|
||||
}
|
||||
if p.options.PreAlloc {
|
||||
if size == -1 {
|
||||
return nil, ErrInvalidPreAllocSize
|
||||
}
|
||||
p.workers = newWorkerQueue(queueTypeLoopQueue, size)
|
||||
} else {
|
||||
p.workers = newWorkerQueue(queueTypeStack, 0)
|
||||
}
|
||||
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
p.goPurge()
|
||||
p.goTicktock()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
|
||||
func (p *poolCommon) purgeStaleWorkers() {
|
||||
ticker := time.NewTicker(p.options.ExpiryDuration)
|
||||
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.purgeDone, 1)
|
||||
}()
|
||||
|
||||
purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-purgeCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
var isDormant bool
|
||||
p.lock.Lock()
|
||||
staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
|
||||
n := p.Running()
|
||||
isDormant = n == 0 || n == len(staleWorkers)
|
||||
p.lock.Unlock()
|
||||
|
||||
// Clean up the stale workers.
|
||||
for i := range staleWorkers {
|
||||
staleWorkers[i].finish()
|
||||
staleWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up (no worker is running),
|
||||
// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
|
||||
if isDormant && p.Waiting() > 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nowTimeUpdateInterval = 500 * time.Millisecond
|
||||
|
||||
// ticktock is a goroutine that updates the current time in the pool regularly.
|
||||
func (p *poolCommon) ticktock() {
|
||||
ticker := time.NewTicker(nowTimeUpdateInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.ticktockDone, 1)
|
||||
}()
|
||||
|
||||
ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-ticktockCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
p.now.Store(time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *poolCommon) goPurge() {
|
||||
if p.options.DisablePurge {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
|
||||
go p.purgeStaleWorkers()
|
||||
}
|
||||
|
||||
func (p *poolCommon) goTicktock() {
|
||||
p.now.Store(time.Now())
|
||||
p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
|
||||
go p.ticktock()
|
||||
}
|
||||
|
||||
func (p *poolCommon) nowTime() time.Time {
|
||||
return p.now.Load().(time.Time)
|
||||
}
|
||||
|
||||
// Running returns the number of workers currently running.
|
||||
func (p *poolCommon) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns the number of available workers, -1 indicates this pool is unlimited.
|
||||
func (p *poolCommon) Free() int {
|
||||
c := p.Cap()
|
||||
if c < 0 {
|
||||
return -1
|
||||
}
|
||||
return c - p.Running()
|
||||
}
|
||||
|
||||
// Waiting returns the number of tasks waiting to be executed.
|
||||
func (p *poolCommon) Waiting() int {
|
||||
return int(atomic.LoadInt32(&p.waiting))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *poolCommon) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
|
||||
func (p *poolCommon) Tune(size int) {
|
||||
capacity := p.Cap()
|
||||
if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
if size > capacity {
|
||||
if size-capacity == 1 {
|
||||
p.cond.Signal()
|
||||
return
|
||||
}
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the pool is closed.
|
||||
func (p *poolCommon) IsClosed() bool {
|
||||
return atomic.LoadInt32(&p.state) == CLOSED
|
||||
}
|
||||
|
||||
// Release closes this pool and releases the worker queue.
|
||||
func (p *poolCommon) Release() {
|
||||
if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
|
||||
return
|
||||
}
|
||||
|
||||
if p.stopPurge != nil {
|
||||
p.stopPurge()
|
||||
p.stopPurge = nil
|
||||
}
|
||||
if p.stopTicktock != nil {
|
||||
p.stopTicktock()
|
||||
p.stopTicktock = nil
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
p.workers.reset()
|
||||
p.lock.Unlock()
|
||||
|
||||
// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
|
||||
// those callers blocking infinitely.
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error {
|
||||
if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
p.Release()
|
||||
|
||||
var purgeCh <-chan struct{}
|
||||
if !p.options.DisablePurge {
|
||||
purgeCh = p.purgeCtx.Done()
|
||||
} else {
|
||||
purgeCh = p.allDone
|
||||
}
|
||||
|
||||
if p.Running() == 0 {
|
||||
p.once.Do(func() {
|
||||
close(p.allDone)
|
||||
})
|
||||
}
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return ErrTimeout
|
||||
case <-p.allDone:
|
||||
<-purgeCh
|
||||
<-p.ticktockCtx.Done()
|
||||
if p.Running() == 0 &&
|
||||
(p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
|
||||
atomic.LoadInt32(&p.ticktockDone) == 1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool, it does nothing if the pool is not closed.
|
||||
// If you intend to reboot a closed pool, use ReleaseTimeout() instead of
|
||||
// Release() to ensure that all workers are stopped and resource are released
|
||||
// before rebooting, otherwise you may run into data race.
|
||||
func (p *poolCommon) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.purgeDone, 0)
|
||||
p.goPurge()
|
||||
atomic.StoreInt32(&p.ticktockDone, 0)
|
||||
p.goTicktock()
|
||||
p.allDone = make(chan struct{})
|
||||
p.once = &sync.Once{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *poolCommon) addRunning(delta int) int {
|
||||
return int(atomic.AddInt32(&p.running, int32(delta)))
|
||||
}
|
||||
|
||||
func (p *poolCommon) addWaiting(delta int) {
|
||||
atomic.AddInt32(&p.waiting, int32(delta))
|
||||
}
|
||||
|
||||
// retrieveWorker returns an available worker to run the tasks.
|
||||
func (p *poolCommon) retrieveWorker() (w worker, err error) {
|
||||
p.lock.Lock()
|
||||
|
||||
retry:
|
||||
// First try to fetch the worker from the queue.
|
||||
if w = p.workers.detach(); w != nil {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If the worker queue is empty, and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
p.lock.Unlock()
|
||||
w = p.workerCache.Get().(worker)
|
||||
w.run()
|
||||
return
|
||||
}
|
||||
|
||||
// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
|
||||
if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolOverload
|
||||
}
|
||||
|
||||
// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolClosed
|
||||
}
|
||||
|
||||
goto retry
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *poolCommon) revertWorker(worker worker) bool {
|
||||
if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
|
||||
p.cond.Broadcast()
|
||||
return false
|
||||
}
|
||||
|
||||
worker.setLastUsedTime(p.nowTime())
|
||||
|
||||
p.lock.Lock()
|
||||
// To avoid memory leaks, add a double check in the lock scope.
|
||||
// Issue: https://github.com/panjf2000/ants/issues/113
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
if err := p.workers.insert(worker); err != nil {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
package ants_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
@ -30,6 +30,8 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,11 +45,15 @@ func demoFunc() {
|
||||
time.Sleep(time.Duration(BenchParam) * time.Millisecond)
|
||||
}
|
||||
|
||||
func demoPoolFunc(args interface{}) {
|
||||
func demoPoolFunc(args any) {
|
||||
n := args.(int)
|
||||
time.Sleep(time.Duration(n) * time.Millisecond)
|
||||
}
|
||||
|
||||
func demoPoolFuncInt(n int) {
|
||||
time.Sleep(time.Duration(n) * time.Millisecond)
|
||||
}
|
||||
|
||||
var stopLongRunningFunc int32
|
||||
|
||||
func longRunningFunc() {
|
||||
@ -56,16 +62,12 @@ func longRunningFunc() {
|
||||
}
|
||||
}
|
||||
|
||||
var stopLongRunningPoolFunc int32
|
||||
func longRunningPoolFunc(arg any) {
|
||||
<-arg.(chan struct{})
|
||||
}
|
||||
|
||||
func longRunningPoolFunc(arg interface{}) {
|
||||
if ch, ok := arg.(chan struct{}); ok {
|
||||
<-ch
|
||||
return
|
||||
}
|
||||
for atomic.LoadInt32(&stopLongRunningPoolFunc) == 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
func longRunningPoolFuncCh(ch chan struct{}) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func BenchmarkGoroutines(b *testing.B) {
|
||||
@ -122,7 +124,7 @@ func BenchmarkErrGroup(b *testing.B) {
|
||||
|
||||
func BenchmarkAntsPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := NewPool(PoolCap, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
@ -140,7 +142,7 @@ func BenchmarkAntsPool(b *testing.B) {
|
||||
|
||||
func BenchmarkAntsMultiPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := NewMultiPool(10, PoolCap/10, RoundRobin, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
@ -178,7 +180,7 @@ func BenchmarkSemaphoreThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := NewPool(PoolCap, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
@ -190,7 +192,7 @@ func BenchmarkAntsPoolThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := NewMultiPool(10, PoolCap/10, RoundRobin, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
@ -202,7 +204,7 @@ func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := NewPool(PoolCap, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
@ -214,7 +216,7 @@ func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := NewMultiPool(10, PoolCap/10, RoundRobin, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
990
ants_test.go
990
ants_test.go
File diff suppressed because it is too large
Load Diff
174
example_test.go
Normal file
174
example_test.go
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
sum int32
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
func incSum(i any) {
|
||||
incSumInt(i.(int32))
|
||||
}
|
||||
|
||||
func incSumInt(i int32) {
|
||||
atomic.AddInt32(&sum, i)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func ExamplePool() {
|
||||
ants.Reboot() // ensure the default pool is available
|
||||
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
// Use the default pool.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = ants.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
wg.Add(runTimes)
|
||||
// Use the new pool.
|
||||
pool, _ := ants.NewPool(10)
|
||||
defer pool.Release()
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = pool.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output:
|
||||
// The result is 499500
|
||||
// The result is 499500
|
||||
}
|
||||
|
||||
func ExamplePoolWithFunc() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
pool, _ := ants.NewPoolWithFunc(10, incSum)
|
||||
defer pool.Release()
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = pool.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExamplePoolWithFuncGeneric() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
pool, _ := ants.NewPoolWithFuncGeneric(10, incSumInt)
|
||||
defer pool.Release()
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = pool.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPool() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPool(10, runTimes/10, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = mp.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPoolWithFunc() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, incSum, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = mp.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPoolWithFuncGeneric() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPoolWithFuncGeneric(10, runTimes/10, incSumInt, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = mp.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
114
examples/main.go
114
examples/main.go
@ -1,114 +0,0 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2018 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var sum int32
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a function,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
|
||||
// Use the MultiPool and set the capacity of the 10 goroutine pools to unlimited.
|
||||
// If you use -1 as the pool size parameter, the size will be unlimited.
|
||||
// There are two load-balancing algorithms for pools: ants.RoundRobin and ants.LeastTasks.
|
||||
mp, _ := ants.NewMultiPool(10, -1, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mp.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mp.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the MultiPoolFunc and set the capacity of 10 goroutine pools to (runTimes/10).
|
||||
mpf, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
}, ants.LeastTasks)
|
||||
defer mpf.ReleaseTimeout(5 * time.Second)
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = mpf.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", mpf.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
if sum != 499500*2 {
|
||||
panic("the final result is wrong!!!")
|
||||
}
|
||||
}
|
||||
8
go.mod
8
go.mod
@ -1,8 +1,14 @@
|
||||
module github.com/panjf2000/ants/v2
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/sync v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
12
multipool.go
12
multipool.go
@ -25,6 +25,7 @@ package ants
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -58,6 +59,10 @@ type MultiPool struct {
|
||||
// NewMultiPool instantiates a MultiPool with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
@ -69,16 +74,13 @@ func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...O
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPool{pools: pools, lbs: lbs}, nil
|
||||
return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
if idx = int((atomic.AddUint32(&mp.index, 1) - 1) % uint32(len(mp.pools))); idx == -1 {
|
||||
idx = 0
|
||||
}
|
||||
return
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
|
||||
@ -25,6 +25,7 @@ package ants
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -46,7 +47,11 @@ type MultiPoolWithFunc struct {
|
||||
|
||||
// NewMultiPoolWithFunc instantiates a MultiPoolWithFunc with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPoolWithFunc(size, sizePerPool int, fn func(interface{}), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
|
||||
func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
@ -58,16 +63,13 @@ func NewMultiPoolWithFunc(size, sizePerPool int, fn func(interface{}), lbs LoadB
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPoolWithFunc{pools: pools, lbs: lbs}, nil
|
||||
return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
if idx = int((atomic.AddUint32(&mp.index, 1) - 1) % uint32(len(mp.pools))); idx == -1 {
|
||||
idx = 0
|
||||
}
|
||||
return
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
@ -82,7 +84,7 @@ func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
}
|
||||
|
||||
// Invoke submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPoolWithFunc) Invoke(args interface{}) (err error) {
|
||||
func (mp *MultiPoolWithFunc) Invoke(args any) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
215
multipool_func_generic.go
Normal file
215
multipool_func_generic.go
Normal file
@ -0,0 +1,215 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// MultiPoolWithFuncGeneric is the generic version of MultiPoolWithFunc.
|
||||
type MultiPoolWithFuncGeneric[T any] struct {
|
||||
pools []*PoolWithFuncGeneric[T]
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// NewMultiPoolWithFuncGeneric instantiates a MultiPoolWithFunc with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFuncGeneric[T], error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
pools := make([]*PoolWithFuncGeneric[T], size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPoolWithFuncGeneric(sizePerPool, fn, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPoolWithFuncGeneric[T]{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
if n := pool.Running(); n < leastTasks {
|
||||
leastTasks = n
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Invoke submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
||||
return mp.pools[mp.next(LeastTasks)].Invoke(args)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running workers across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Running()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunningByIndex returns the number of the currently running workers in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Running(), nil
|
||||
}
|
||||
|
||||
// Free returns the number of available workers across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Free()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FreeByIndex returns the number of available workers in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Free(), nil
|
||||
}
|
||||
|
||||
// Waiting returns the number of the currently waiting tasks across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Waiting()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Waiting(), nil
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Cap()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tune resizes each pool in multi-pool.
|
||||
//
|
||||
// Note that this method doesn't resize the overall
|
||||
// capacity of multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool {
|
||||
return atomic.LoadInt32(&mp.state) == CLOSED
|
||||
}
|
||||
|
||||
// ReleaseTimeout closes the multi-pool with a timeout,
|
||||
// it waits all pools to be closed before timing out.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Duration) error {
|
||||
if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
errCh := make(chan error, len(mp.pools))
|
||||
var wg errgroup.Group
|
||||
for i, pool := range mp.pools {
|
||||
func(p *PoolWithFuncGeneric[T], idx int) {
|
||||
wg.Go(func() error {
|
||||
err := p.ReleaseTimeout(timeout)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("pool %d: %v", idx, err)
|
||||
}
|
||||
errCh <- err
|
||||
return err
|
||||
})
|
||||
}(pool, i)
|
||||
}
|
||||
|
||||
_ = wg.Wait()
|
||||
|
||||
var errStr strings.Builder
|
||||
for i := 0; i < len(mp.pools); i++ {
|
||||
if err := <-errCh; err != nil {
|
||||
errStr.WriteString(err.Error())
|
||||
errStr.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
|
||||
if errStr.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimSuffix(errStr.String(), " | "))
|
||||
}
|
||||
|
||||
// Reboot reboots a released multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
26
options.go
26
options.go
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2018. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
@ -34,7 +56,7 @@ type Options struct {
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
PanicHandler func(any)
|
||||
|
||||
// Logger is the customized logger for logging info, if it is not set,
|
||||
// default standard logger from log package is used.
|
||||
@ -80,7 +102,7 @@ func WithNonblocking(nonblocking bool) Option {
|
||||
}
|
||||
|
||||
// WithPanicHandler sets up panic handler.
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
func WithPanicHandler(panicHandler func(any)) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
Benchmark result for three types of locks:
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/panjf2000/ants/v2/internal/sync
|
||||
pkg: github.com/panjf2000/ants/v2/pkg/sync
|
||||
BenchmarkMutex-10 10452573 111.1 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSpinLock-10 58953211 18.01 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBackOffSpinLock-10 100000000 10.81 ns/op 0 B/op 0 allocs/op
|
||||
25
pkg/sync/sync.go
Normal file
25
pkg/sync/sync.go
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package sync provides some handy implementations for synchronization access.
|
||||
// At the moment, there is only an implementation of spin-lock.
|
||||
package sync
|
||||
403
pool.go
403
pool.go
@ -22,206 +22,13 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
syncx "github.com/panjf2000/ants/v2/internal/sync"
|
||||
)
|
||||
|
||||
type poolCommon struct {
|
||||
// capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to
|
||||
// avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool
|
||||
// which submits a new task to the same pool.
|
||||
capacity int32
|
||||
|
||||
// running is the number of the currently running goroutines.
|
||||
running int32
|
||||
|
||||
// lock for protecting the worker queue.
|
||||
lock sync.Locker
|
||||
|
||||
// workers is a slice that store the available workers.
|
||||
workers workerQueue
|
||||
|
||||
// state is used to notice the pool to closed itself.
|
||||
state int32
|
||||
|
||||
// cond for waiting to get an idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// done is used to indicate that all workers are done.
|
||||
allDone chan struct{}
|
||||
// once is used to make sure the pool is closed just once.
|
||||
once *sync.Once
|
||||
|
||||
// workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock
|
||||
waiting int32
|
||||
|
||||
purgeDone int32
|
||||
purgeCtx context.Context
|
||||
stopPurge context.CancelFunc
|
||||
|
||||
ticktockDone int32
|
||||
ticktockCtx context.Context
|
||||
stopTicktock context.CancelFunc
|
||||
|
||||
now atomic.Value
|
||||
|
||||
options *Options
|
||||
}
|
||||
|
||||
// Pool accepts the tasks and process them concurrently,
|
||||
// it limits the total of goroutines to a given number by recycling goroutines.
|
||||
// Pool is a goroutine pool that limits and recycles a mass of goroutines.
|
||||
// The pool capacity can be fixed or unlimited.
|
||||
type Pool struct {
|
||||
poolCommon
|
||||
*poolCommon
|
||||
}
|
||||
|
||||
// purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
|
||||
func (p *Pool) purgeStaleWorkers() {
|
||||
ticker := time.NewTicker(p.options.ExpiryDuration)
|
||||
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.purgeDone, 1)
|
||||
}()
|
||||
|
||||
purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-purgeCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
var isDormant bool
|
||||
p.lock.Lock()
|
||||
staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
|
||||
n := p.Running()
|
||||
isDormant = n == 0 || n == len(staleWorkers)
|
||||
p.lock.Unlock()
|
||||
|
||||
// Notify obsolete workers to stop.
|
||||
// This notification must be outside the p.lock, since w.task
|
||||
// may be blocking and may consume a lot of time if many workers
|
||||
// are located on non-local CPUs.
|
||||
for i := range staleWorkers {
|
||||
staleWorkers[i].finish()
|
||||
staleWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up (no worker is running),
|
||||
// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
|
||||
if isDormant && p.Waiting() > 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ticktock is a goroutine that updates the current time in the pool regularly.
|
||||
func (p *Pool) ticktock() {
|
||||
ticker := time.NewTicker(nowTimeUpdateInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.ticktockDone, 1)
|
||||
}()
|
||||
|
||||
ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-ticktockCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
p.now.Store(time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) goPurge() {
|
||||
if p.options.DisablePurge {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
|
||||
go p.purgeStaleWorkers()
|
||||
}
|
||||
|
||||
func (p *Pool) goTicktock() {
|
||||
p.now.Store(time.Now())
|
||||
p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
|
||||
go p.ticktock()
|
||||
}
|
||||
|
||||
func (p *Pool) nowTime() time.Time {
|
||||
return p.now.Load().(time.Time)
|
||||
}
|
||||
|
||||
// NewPool instantiates a Pool with customized options.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
}
|
||||
|
||||
opts := loadOptions(options...)
|
||||
|
||||
if !opts.DisablePurge {
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = DefaultCleanIntervalTime
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = defaultLogger
|
||||
}
|
||||
|
||||
p := &Pool{poolCommon: poolCommon{
|
||||
capacity: int32(size),
|
||||
allDone: make(chan struct{}),
|
||||
lock: syncx.NewSpinLock(),
|
||||
once: &sync.Once{},
|
||||
options: opts,
|
||||
}}
|
||||
p.workerCache.New = func() interface{} {
|
||||
return &goWorker{
|
||||
pool: p,
|
||||
task: make(chan func(), workerChanCap),
|
||||
}
|
||||
}
|
||||
if p.options.PreAlloc {
|
||||
if size == -1 {
|
||||
return nil, ErrInvalidPreAllocSize
|
||||
}
|
||||
p.workers = newWorkerQueue(queueTypeLoopQueue, size)
|
||||
} else {
|
||||
p.workers = newWorkerQueue(queueTypeStack, 0)
|
||||
}
|
||||
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
p.goPurge()
|
||||
p.goTicktock()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Submit submits a task to this pool.
|
||||
// Submit submits a task to the pool.
|
||||
//
|
||||
// Note that you are allowed to call Pool.Submit() from the current Pool.Submit(),
|
||||
// but what calls for special attention is that you will get blocked with the last
|
||||
@ -239,198 +46,20 @@ func (p *Pool) Submit(task func()) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Running returns the number of workers currently running.
|
||||
func (p *Pool) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns the number of available workers, -1 indicates this pool is unlimited.
|
||||
func (p *Pool) Free() int {
|
||||
c := p.Cap()
|
||||
if c < 0 {
|
||||
return -1
|
||||
}
|
||||
return c - p.Running()
|
||||
}
|
||||
|
||||
// Waiting returns the number of tasks waiting to be executed.
|
||||
func (p *Pool) Waiting() int {
|
||||
return int(atomic.LoadInt32(&p.waiting))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *Pool) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
|
||||
func (p *Pool) Tune(size int) {
|
||||
capacity := p.Cap()
|
||||
if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
if size > capacity {
|
||||
if size-capacity == 1 {
|
||||
p.cond.Signal()
|
||||
return
|
||||
}
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the pool is closed.
|
||||
func (p *Pool) IsClosed() bool {
|
||||
return atomic.LoadInt32(&p.state) == CLOSED
|
||||
}
|
||||
|
||||
// Release closes this pool and releases the worker queue.
|
||||
func (p *Pool) Release() {
|
||||
if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
|
||||
return
|
||||
// NewPool instantiates a Pool with customized options.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.stopPurge != nil {
|
||||
p.stopPurge()
|
||||
p.stopPurge = nil
|
||||
}
|
||||
if p.stopTicktock != nil {
|
||||
p.stopTicktock()
|
||||
p.stopTicktock = nil
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
p.workers.reset()
|
||||
p.lock.Unlock()
|
||||
// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
|
||||
// those callers blocking infinitely.
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func (p *Pool) ReleaseTimeout(timeout time.Duration) error {
|
||||
if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
p.Release()
|
||||
|
||||
var purgeCh <-chan struct{}
|
||||
if !p.options.DisablePurge {
|
||||
purgeCh = p.purgeCtx.Done()
|
||||
} else {
|
||||
purgeCh = p.allDone
|
||||
}
|
||||
|
||||
if p.Running() == 0 {
|
||||
p.once.Do(func() {
|
||||
close(p.allDone)
|
||||
})
|
||||
}
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return ErrTimeout
|
||||
case <-p.allDone:
|
||||
<-purgeCh
|
||||
<-p.ticktockCtx.Done()
|
||||
if p.Running() == 0 &&
|
||||
(p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
|
||||
atomic.LoadInt32(&p.ticktockDone) == 1 {
|
||||
return nil
|
||||
}
|
||||
pool := &Pool{poolCommon: pc}
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorker{
|
||||
pool: pool,
|
||||
task: make(chan func(), workerChanCap),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool, it does nothing if the pool is not closed.
|
||||
// If you intend to reboot a closed pool, use ReleaseTimeout() instead of
|
||||
// Release() to ensure that all workers are stopped and resource are released
|
||||
// before rebooting, otherwise you may run into data race.
|
||||
func (p *Pool) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.purgeDone, 0)
|
||||
p.goPurge()
|
||||
atomic.StoreInt32(&p.ticktockDone, 0)
|
||||
p.goTicktock()
|
||||
p.allDone = make(chan struct{})
|
||||
p.once = &sync.Once{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) addRunning(delta int) int {
|
||||
return int(atomic.AddInt32(&p.running, int32(delta)))
|
||||
}
|
||||
|
||||
func (p *Pool) addWaiting(delta int) {
|
||||
atomic.AddInt32(&p.waiting, int32(delta))
|
||||
}
|
||||
|
||||
// retrieveWorker returns an available worker to run the tasks.
|
||||
func (p *Pool) retrieveWorker() (w worker, err error) {
|
||||
p.lock.Lock()
|
||||
|
||||
retry:
|
||||
// First try to fetch the worker from the queue.
|
||||
if w = p.workers.detach(); w != nil {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If the worker queue is empty, and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
p.lock.Unlock()
|
||||
w = p.workerCache.Get().(*goWorker)
|
||||
w.run()
|
||||
return
|
||||
}
|
||||
|
||||
// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
|
||||
if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolOverload
|
||||
}
|
||||
|
||||
// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolClosed
|
||||
}
|
||||
|
||||
goto retry
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *Pool) revertWorker(worker *goWorker) bool {
|
||||
if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
|
||||
p.cond.Broadcast()
|
||||
return false
|
||||
}
|
||||
|
||||
worker.lastUsed = p.nowTime()
|
||||
|
||||
p.lock.Lock()
|
||||
// To avoid memory leaks, add a double check in the lock scope.
|
||||
// Issue: https://github.com/panjf2000/ants/issues/113
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
if err := p.workers.insert(worker); err != nil {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
|
||||
return true
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
371
pool_func.go
371
pool_func.go
@ -22,379 +22,54 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
syncx "github.com/panjf2000/ants/v2/internal/sync"
|
||||
)
|
||||
|
||||
// PoolWithFunc accepts the tasks and process them concurrently,
|
||||
// it limits the total of goroutines to a given number by recycling goroutines.
|
||||
// PoolWithFunc is like Pool but accepts a unified function for all goroutines to execute.
|
||||
type PoolWithFunc struct {
|
||||
poolCommon
|
||||
*poolCommon
|
||||
|
||||
// poolFunc is the function for processing tasks.
|
||||
poolFunc func(interface{})
|
||||
// fn is the unified function for processing tasks.
|
||||
fn func(any)
|
||||
}
|
||||
|
||||
// purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
|
||||
func (p *PoolWithFunc) purgeStaleWorkers() {
|
||||
ticker := time.NewTicker(p.options.ExpiryDuration)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.purgeDone, 1)
|
||||
}()
|
||||
|
||||
purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-purgeCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
var isDormant bool
|
||||
p.lock.Lock()
|
||||
staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
|
||||
n := p.Running()
|
||||
isDormant = n == 0 || n == len(staleWorkers)
|
||||
p.lock.Unlock()
|
||||
|
||||
// Notify obsolete workers to stop.
|
||||
// This notification must be outside the p.lock, since w.task
|
||||
// may be blocking and may consume a lot of time if many workers
|
||||
// are located on non-local CPUs.
|
||||
for i := range staleWorkers {
|
||||
staleWorkers[i].finish()
|
||||
staleWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up (no worker is running),
|
||||
// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
|
||||
if isDormant && p.Waiting() > 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ticktock is a goroutine that updates the current time in the pool regularly.
|
||||
func (p *PoolWithFunc) ticktock() {
|
||||
ticker := time.NewTicker(nowTimeUpdateInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.ticktockDone, 1)
|
||||
}()
|
||||
|
||||
ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-ticktockCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
p.now.Store(time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PoolWithFunc) goPurge() {
|
||||
if p.options.DisablePurge {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
|
||||
go p.purgeStaleWorkers()
|
||||
}
|
||||
|
||||
func (p *PoolWithFunc) goTicktock() {
|
||||
p.now.Store(time.Now())
|
||||
p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
|
||||
go p.ticktock()
|
||||
}
|
||||
|
||||
func (p *PoolWithFunc) nowTime() time.Time {
|
||||
return p.now.Load().(time.Time)
|
||||
}
|
||||
|
||||
// NewPoolWithFunc instantiates a PoolWithFunc with customized options.
|
||||
func NewPoolWithFunc(size int, pf func(interface{}), options ...Option) (*PoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
}
|
||||
|
||||
if pf == nil {
|
||||
return nil, ErrLackPoolFunc
|
||||
}
|
||||
|
||||
opts := loadOptions(options...)
|
||||
|
||||
if !opts.DisablePurge {
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = DefaultCleanIntervalTime
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = defaultLogger
|
||||
}
|
||||
|
||||
p := &PoolWithFunc{
|
||||
poolCommon: poolCommon{
|
||||
capacity: int32(size),
|
||||
allDone: make(chan struct{}),
|
||||
lock: syncx.NewSpinLock(),
|
||||
once: &sync.Once{},
|
||||
options: opts,
|
||||
},
|
||||
poolFunc: pf,
|
||||
}
|
||||
p.workerCache.New = func() interface{} {
|
||||
return &goWorkerWithFunc{
|
||||
pool: p,
|
||||
args: make(chan interface{}, workerChanCap),
|
||||
}
|
||||
}
|
||||
if p.options.PreAlloc {
|
||||
if size == -1 {
|
||||
return nil, ErrInvalidPreAllocSize
|
||||
}
|
||||
p.workers = newWorkerQueue(queueTypeLoopQueue, size)
|
||||
} else {
|
||||
p.workers = newWorkerQueue(queueTypeStack, 0)
|
||||
}
|
||||
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
p.goPurge()
|
||||
p.goTicktock()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Invoke submits a task to pool.
|
||||
// Invoke passes arguments to the pool.
|
||||
//
|
||||
// Note that you are allowed to call Pool.Invoke() from the current Pool.Invoke(),
|
||||
// but what calls for special attention is that you will get blocked with the last
|
||||
// Pool.Invoke() call once the current Pool runs out of its capacity, and to avoid this,
|
||||
// you should instantiate a PoolWithFunc with ants.WithNonblocking(true).
|
||||
func (p *PoolWithFunc) Invoke(args interface{}) error {
|
||||
func (p *PoolWithFunc) Invoke(arg any) error {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.inputParam(args)
|
||||
w.inputArg(arg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Running returns the number of workers currently running.
|
||||
func (p *PoolWithFunc) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns the number of available workers, -1 indicates this pool is unlimited.
|
||||
func (p *PoolWithFunc) Free() int {
|
||||
c := p.Cap()
|
||||
if c < 0 {
|
||||
return -1
|
||||
}
|
||||
return c - p.Running()
|
||||
}
|
||||
|
||||
// Waiting returns the number of tasks waiting to be executed.
|
||||
func (p *PoolWithFunc) Waiting() int {
|
||||
return int(atomic.LoadInt32(&p.waiting))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *PoolWithFunc) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
|
||||
func (p *PoolWithFunc) Tune(size int) {
|
||||
capacity := p.Cap()
|
||||
if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
if size > capacity {
|
||||
if size-capacity == 1 {
|
||||
p.cond.Signal()
|
||||
return
|
||||
}
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the pool is closed.
|
||||
func (p *PoolWithFunc) IsClosed() bool {
|
||||
return atomic.LoadInt32(&p.state) == CLOSED
|
||||
}
|
||||
|
||||
// Release closes this pool and releases the worker queue.
|
||||
func (p *PoolWithFunc) Release() {
|
||||
if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
|
||||
return
|
||||
// NewPoolWithFunc instantiates a PoolWithFunc with customized options.
|
||||
func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) {
|
||||
if pf == nil {
|
||||
return nil, ErrLackPoolFunc
|
||||
}
|
||||
|
||||
if p.stopPurge != nil {
|
||||
p.stopPurge()
|
||||
p.stopPurge = nil
|
||||
}
|
||||
if p.stopTicktock != nil {
|
||||
p.stopTicktock()
|
||||
p.stopTicktock = nil
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
p.workers.reset()
|
||||
p.lock.Unlock()
|
||||
// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
|
||||
// those callers blocking infinitely.
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func (p *PoolWithFunc) ReleaseTimeout(timeout time.Duration) error {
|
||||
if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
|
||||
return ErrPoolClosed
|
||||
pool := &PoolWithFunc{
|
||||
poolCommon: pc,
|
||||
fn: pf,
|
||||
}
|
||||
|
||||
p.Release()
|
||||
|
||||
var purgeCh <-chan struct{}
|
||||
if !p.options.DisablePurge {
|
||||
purgeCh = p.purgeCtx.Done()
|
||||
} else {
|
||||
purgeCh = p.allDone
|
||||
}
|
||||
|
||||
if p.Running() == 0 {
|
||||
p.once.Do(func() {
|
||||
close(p.allDone)
|
||||
})
|
||||
}
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return ErrTimeout
|
||||
case <-p.allDone:
|
||||
<-purgeCh
|
||||
<-p.ticktockCtx.Done()
|
||||
if p.Running() == 0 &&
|
||||
(p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
|
||||
atomic.LoadInt32(&p.ticktockDone) == 1 {
|
||||
return nil
|
||||
}
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorkerWithFunc{
|
||||
pool: pool,
|
||||
arg: make(chan any, workerChanCap),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool, it does nothing if the pool is not closed.
|
||||
// If you intend to reboot a closed pool, use ReleaseTimeout() instead of
|
||||
// Release() to ensure that all workers are stopped and resource are released
|
||||
// before rebooting, otherwise you may run into data race.
|
||||
func (p *PoolWithFunc) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.purgeDone, 0)
|
||||
p.goPurge()
|
||||
atomic.StoreInt32(&p.ticktockDone, 0)
|
||||
p.goTicktock()
|
||||
p.allDone = make(chan struct{})
|
||||
p.once = &sync.Once{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PoolWithFunc) addRunning(delta int) int {
|
||||
return int(atomic.AddInt32(&p.running, int32(delta)))
|
||||
}
|
||||
|
||||
func (p *PoolWithFunc) addWaiting(delta int) {
|
||||
atomic.AddInt32(&p.waiting, int32(delta))
|
||||
}
|
||||
|
||||
// retrieveWorker returns an available worker to run the tasks.
|
||||
func (p *PoolWithFunc) retrieveWorker() (w worker, err error) {
|
||||
p.lock.Lock()
|
||||
|
||||
retry:
|
||||
// First try to fetch the worker from the queue.
|
||||
if w = p.workers.detach(); w != nil {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If the worker queue is empty, and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
p.lock.Unlock()
|
||||
w = p.workerCache.Get().(*goWorkerWithFunc)
|
||||
w.run()
|
||||
return
|
||||
}
|
||||
|
||||
// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
|
||||
if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolOverload
|
||||
}
|
||||
|
||||
// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolClosed
|
||||
}
|
||||
|
||||
goto retry
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *PoolWithFunc) revertWorker(worker *goWorkerWithFunc) bool {
|
||||
if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
|
||||
p.cond.Broadcast()
|
||||
return false
|
||||
}
|
||||
|
||||
worker.lastUsed = p.nowTime()
|
||||
|
||||
p.lock.Lock()
|
||||
// To avoid memory leaks, add a double check in the lock scope.
|
||||
// Issue: https://github.com/panjf2000/ants/issues/113
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
if err := p.workers.insert(worker); err != nil {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
|
||||
return true
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
71
pool_func_generic.go
Normal file
71
pool_func_generic.go
Normal file
@ -0,0 +1,71 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
// PoolWithFuncGeneric is the generic version of PoolWithFunc.
|
||||
type PoolWithFuncGeneric[T any] struct {
|
||||
*poolCommon
|
||||
|
||||
// fn is the unified function for processing tasks.
|
||||
fn func(T)
|
||||
}
|
||||
|
||||
// Invoke passes the argument to the pool to start a new task.
|
||||
func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.(*goWorkerWithFuncGeneric[T]).arg <- arg
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPoolWithFuncGeneric instantiates a PoolWithFuncGeneric[T] with customized options.
|
||||
func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Option) (*PoolWithFuncGeneric[T], error) {
|
||||
if pf == nil {
|
||||
return nil, ErrLackPoolFunc
|
||||
}
|
||||
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool := &PoolWithFuncGeneric[T]{
|
||||
poolCommon: pc,
|
||||
fn: pf,
|
||||
}
|
||||
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorkerWithFuncGeneric[T]{
|
||||
pool: pool,
|
||||
arg: make(chan T, workerChanCap),
|
||||
exit: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
16
worker.go
16
worker.go
@ -31,6 +31,8 @@ import (
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorker struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *Pool
|
||||
|
||||
@ -64,11 +66,11 @@ func (w *goWorker) run() {
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for f := range w.task {
|
||||
if f == nil {
|
||||
for fn := range w.task {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
f()
|
||||
fn()
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
@ -84,10 +86,10 @@ func (w *goWorker) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorker) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
|
||||
func (w *goWorker) inputFunc(fn func()) {
|
||||
w.task <- fn
|
||||
}
|
||||
|
||||
func (w *goWorker) inputParam(interface{}) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@ -31,11 +31,13 @@ import (
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorkerWithFunc struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *PoolWithFunc
|
||||
|
||||
// args is a job should be done.
|
||||
args chan interface{}
|
||||
// arg is the argument for the function.
|
||||
arg chan any
|
||||
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
@ -64,11 +66,11 @@ func (w *goWorkerWithFunc) run() {
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for args := range w.args {
|
||||
if args == nil {
|
||||
for arg := range w.arg {
|
||||
if arg == nil {
|
||||
return
|
||||
}
|
||||
w.pool.poolFunc(args)
|
||||
w.pool.fn(arg)
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
@ -77,17 +79,17 @@ func (w *goWorkerWithFunc) run() {
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) finish() {
|
||||
w.args <- nil
|
||||
w.arg <- nil
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) inputFunc(func()) {
|
||||
panic("unreachable")
|
||||
func (w *goWorkerWithFunc) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) inputParam(arg interface{}) {
|
||||
w.args <- arg
|
||||
func (w *goWorkerWithFunc) inputArg(arg any) {
|
||||
w.arg <- arg
|
||||
}
|
||||
|
||||
96
worker_func_generic.go
Normal file
96
worker_func_generic.go
Normal file
@ -0,0 +1,96 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// goWorkerWithFunc is the actual executor who runs the tasks,
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorkerWithFuncGeneric[T any] struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *PoolWithFuncGeneric[T]
|
||||
|
||||
// arg is a job should be done.
|
||||
arg chan T
|
||||
|
||||
// exit signals the goroutine to exit.
|
||||
exit chan struct{}
|
||||
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
// that performs the function calls.
|
||||
func (w *goWorkerWithFuncGeneric[T]) run() {
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
|
||||
w.pool.once.Do(func() {
|
||||
close(w.pool.allDone)
|
||||
})
|
||||
}
|
||||
w.pool.workerCache.Put(w)
|
||||
if p := recover(); p != nil {
|
||||
if ph := w.pool.options.PanicHandler; ph != nil {
|
||||
ph(p)
|
||||
} else {
|
||||
w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
|
||||
}
|
||||
}
|
||||
// Call Signal() here in case there are goroutines waiting for available workers.
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.exit:
|
||||
return
|
||||
case arg := <-w.arg:
|
||||
w.pool.fn(arg)
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) finish() {
|
||||
w.exit <- struct{}{}
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
@ -12,6 +34,9 @@ type loopQueue struct {
|
||||
}
|
||||
|
||||
func newWorkerLoopQueue(size int) *loopQueue {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &loopQueue{
|
||||
items: make([]worker, size),
|
||||
size: size,
|
||||
@ -39,10 +64,6 @@ func (wq *loopQueue) isEmpty() bool {
|
||||
}
|
||||
|
||||
func (wq *loopQueue) insert(w worker) error {
|
||||
if wq.size == 0 {
|
||||
return errQueueIsReleased
|
||||
}
|
||||
|
||||
if wq.isFull {
|
||||
return errQueueIsFull
|
||||
}
|
||||
|
||||
@ -1,21 +1,43 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewLoopQueue(t *testing.T) {
|
||||
size := 100
|
||||
q := newWorkerLoopQueue(size)
|
||||
assert.EqualValues(t, 0, q.len(), "Len error")
|
||||
assert.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
assert.Nil(t, q.detach(), "Dequeue error")
|
||||
require.EqualValues(t, 0, q.len(), "Len error")
|
||||
require.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
require.Nil(t, q.detach(), "Dequeue error")
|
||||
|
||||
require.Nil(t, newWorkerLoopQueue(0))
|
||||
}
|
||||
|
||||
func TestLoopQueue(t *testing.T) {
|
||||
@ -28,9 +50,9 @@ func TestLoopQueue(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 5, q.len(), "Len error")
|
||||
require.EqualValues(t, 5, q.len(), "Len error")
|
||||
_ = q.detach()
|
||||
assert.EqualValues(t, 4, q.len(), "Len error")
|
||||
require.EqualValues(t, 4, q.len(), "Len error")
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@ -40,16 +62,20 @@ func TestLoopQueue(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 10, q.len(), "Len error")
|
||||
require.EqualValues(t, 10, q.len(), "Len error")
|
||||
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
assert.Error(t, err, "Enqueue, error")
|
||||
require.Error(t, err, "Enqueue, error")
|
||||
|
||||
q.refresh(time.Second)
|
||||
assert.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
|
||||
require.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
|
||||
}
|
||||
|
||||
func TestRotatedQueueSearch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
|
||||
t.Skip("Skip this test on Windows platform")
|
||||
}
|
||||
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
|
||||
@ -58,18 +84,18 @@ func TestRotatedQueueSearch(t *testing.T) {
|
||||
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
assert.EqualValues(t, 0, q.binarySearch(time.Now()), "index should be 0")
|
||||
assert.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
require.EqualValues(t, 0, q.binarySearch(time.Now()), "index should be 0")
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
|
||||
// 2
|
||||
expiry2 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
assert.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
|
||||
assert.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0")
|
||||
require.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0")
|
||||
|
||||
assert.EqualValues(t, 1, q.binarySearch(time.Now()), "index should be 1")
|
||||
require.EqualValues(t, 1, q.binarySearch(time.Now()), "index should be 1")
|
||||
|
||||
// more
|
||||
for i := 0; i < 5; i++ {
|
||||
@ -84,7 +110,7 @@ func TestRotatedQueueSearch(t *testing.T) {
|
||||
err = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7")
|
||||
require.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7")
|
||||
|
||||
// rotate
|
||||
for i := 0; i < 6; i++ {
|
||||
@ -99,7 +125,7 @@ func TestRotatedQueueSearch(t *testing.T) {
|
||||
}
|
||||
// head = 6, tail = 5, insert direction ->
|
||||
// [expiry4, time, time, time, time, nil/tail, time/head, time, time, time]
|
||||
assert.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0")
|
||||
require.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.detach()
|
||||
@ -109,17 +135,17 @@ func TestRotatedQueueSearch(t *testing.T) {
|
||||
|
||||
// head = 6, tail = 5, insert direction ->
|
||||
// [expiry4, time, time, time, time, expiry5, nil/tail, nil, nil, time/head]
|
||||
assert.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5")
|
||||
require.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
// head = 9, tail = 9, insert direction ->
|
||||
// [expiry4, time, time, time, time, expiry5, time, time, time, time/head/tail]
|
||||
assert.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1")
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1")
|
||||
|
||||
assert.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9")
|
||||
assert.EqualValues(t, 8, q.binarySearch(time.Now()), "index should be 8")
|
||||
require.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9")
|
||||
require.EqualValues(t, 8, q.binarySearch(time.Now()), "index should be 8")
|
||||
}
|
||||
|
||||
func TestRetrieveExpiry(t *testing.T) {
|
||||
@ -140,7 +166,7 @@ func TestRetrieveExpiry(t *testing.T) {
|
||||
}
|
||||
workers := q.refresh(u)
|
||||
|
||||
assert.EqualValues(t, expirew, workers, "expired workers aren't right")
|
||||
require.EqualValues(t, expirew, workers, "expired workers aren't right")
|
||||
|
||||
// test [ time, time, time, time, time, time+1s, time+1s, time+1s, time+1s, time+1s]
|
||||
time.Sleep(u)
|
||||
@ -153,7 +179,7 @@ func TestRetrieveExpiry(t *testing.T) {
|
||||
|
||||
workers2 := q.refresh(u)
|
||||
|
||||
assert.EqualValues(t, expirew, workers2, "expired workers aren't right")
|
||||
require.EqualValues(t, expirew, workers2, "expired workers aren't right")
|
||||
|
||||
// test [ time+1s, time+1s, time+1s, nil, nil, time+1s, time+1s, time+1s, time+1s, time+1s]
|
||||
for i := 0; i < size/2; i++ {
|
||||
@ -173,5 +199,5 @@ func TestRetrieveExpiry(t *testing.T) {
|
||||
|
||||
workers3 := q.refresh(u)
|
||||
|
||||
assert.EqualValues(t, expirew, workers3, "expired workers aren't right")
|
||||
require.EqualValues(t, expirew, workers3, "expired workers aren't right")
|
||||
}
|
||||
|
||||
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
@ -5,20 +27,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// errQueueIsFull will be returned when the worker queue is full.
|
||||
errQueueIsFull = errors.New("the queue is full")
|
||||
|
||||
// errQueueIsReleased will be returned when trying to insert item to a released worker queue.
|
||||
errQueueIsReleased = errors.New("the queue length is zero")
|
||||
)
|
||||
// errQueueIsFull will be returned when the worker queue is full.
|
||||
var errQueueIsFull = errors.New("the queue is full")
|
||||
|
||||
type worker interface {
|
||||
run()
|
||||
finish()
|
||||
lastUsedTime() time.Time
|
||||
setLastUsedTime(t time.Time)
|
||||
inputFunc(func())
|
||||
inputParam(interface{})
|
||||
inputArg(any)
|
||||
}
|
||||
|
||||
type workerQueue interface {
|
||||
|
||||
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
@ -13,57 +35,57 @@ func newWorkerStack(size int) *workerStack {
|
||||
}
|
||||
}
|
||||
|
||||
func (wq *workerStack) len() int {
|
||||
return len(wq.items)
|
||||
func (ws *workerStack) len() int {
|
||||
return len(ws.items)
|
||||
}
|
||||
|
||||
func (wq *workerStack) isEmpty() bool {
|
||||
return len(wq.items) == 0
|
||||
func (ws *workerStack) isEmpty() bool {
|
||||
return len(ws.items) == 0
|
||||
}
|
||||
|
||||
func (wq *workerStack) insert(w worker) error {
|
||||
wq.items = append(wq.items, w)
|
||||
func (ws *workerStack) insert(w worker) error {
|
||||
ws.items = append(ws.items, w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wq *workerStack) detach() worker {
|
||||
l := wq.len()
|
||||
func (ws *workerStack) detach() worker {
|
||||
l := ws.len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := wq.items[l-1]
|
||||
wq.items[l-1] = nil // avoid memory leaks
|
||||
wq.items = wq.items[:l-1]
|
||||
w := ws.items[l-1]
|
||||
ws.items[l-1] = nil // avoid memory leaks
|
||||
ws.items = ws.items[:l-1]
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (wq *workerStack) refresh(duration time.Duration) []worker {
|
||||
n := wq.len()
|
||||
func (ws *workerStack) refresh(duration time.Duration) []worker {
|
||||
n := ws.len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
expiryTime := time.Now().Add(-duration)
|
||||
index := wq.binarySearch(0, n-1, expiryTime)
|
||||
index := ws.binarySearch(0, n-1, expiryTime)
|
||||
|
||||
wq.expiry = wq.expiry[:0]
|
||||
ws.expiry = ws.expiry[:0]
|
||||
if index != -1 {
|
||||
wq.expiry = append(wq.expiry, wq.items[:index+1]...)
|
||||
m := copy(wq.items, wq.items[index+1:])
|
||||
ws.expiry = append(ws.expiry, ws.items[:index+1]...)
|
||||
m := copy(ws.items, ws.items[index+1:])
|
||||
for i := m; i < n; i++ {
|
||||
wq.items[i] = nil
|
||||
ws.items[i] = nil
|
||||
}
|
||||
wq.items = wq.items[:m]
|
||||
ws.items = ws.items[:m]
|
||||
}
|
||||
return wq.expiry
|
||||
return ws.expiry
|
||||
}
|
||||
|
||||
func (wq *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
|
||||
func (ws *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
|
||||
for l <= r {
|
||||
mid := l + ((r - l) >> 1) // avoid overflow when computing mid
|
||||
if expiryTime.Before(wq.items[mid].lastUsedTime()) {
|
||||
if expiryTime.Before(ws.items[mid].lastUsedTime()) {
|
||||
r = mid - 1
|
||||
} else {
|
||||
l = mid + 1
|
||||
@ -72,10 +94,10 @@ func (wq *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
|
||||
return r
|
||||
}
|
||||
|
||||
func (wq *workerStack) reset() {
|
||||
for i := 0; i < wq.len(); i++ {
|
||||
wq.items[i].finish()
|
||||
wq.items[i] = nil
|
||||
func (ws *workerStack) reset() {
|
||||
for i := 0; i < ws.len(); i++ {
|
||||
ws.items[i].finish()
|
||||
ws.items[i] = nil
|
||||
}
|
||||
wq.items = wq.items[:0]
|
||||
ws.items = ws.items[:0]
|
||||
}
|
||||
|
||||
@ -1,21 +1,41 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewWorkerStack(t *testing.T) {
|
||||
size := 100
|
||||
q := newWorkerStack(size)
|
||||
assert.EqualValues(t, 0, q.len(), "Len error")
|
||||
assert.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
assert.Nil(t, q.detach(), "Dequeue error")
|
||||
require.EqualValues(t, 0, q.len(), "Len error")
|
||||
require.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
require.Nil(t, q.detach(), "Dequeue error")
|
||||
}
|
||||
|
||||
func TestWorkerStack(t *testing.T) {
|
||||
@ -27,7 +47,7 @@ func TestWorkerStack(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 5, q.len(), "Len error")
|
||||
require.EqualValues(t, 5, q.len(), "Len error")
|
||||
|
||||
expired := time.Now()
|
||||
|
||||
@ -44,14 +64,18 @@ func TestWorkerStack(t *testing.T) {
|
||||
t.Fatal("Enqueue error")
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 12, q.len(), "Len error")
|
||||
require.EqualValues(t, 12, q.len(), "Len error")
|
||||
q.refresh(time.Second)
|
||||
assert.EqualValues(t, 6, q.len(), "Len error")
|
||||
require.EqualValues(t, 6, q.len(), "Len error")
|
||||
}
|
||||
|
||||
// It seems that something wrong with time.Now() on Windows, not sure whether it is a bug on Windows,
|
||||
// so exclude this test from Windows platform temporarily.
|
||||
func TestSearch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
|
||||
t.Skip("Skip this test on Windows platform")
|
||||
}
|
||||
|
||||
q := newWorkerStack(0)
|
||||
|
||||
// 1
|
||||
@ -59,18 +83,18 @@ func TestSearch(t *testing.T) {
|
||||
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
assert.EqualValues(t, 0, q.binarySearch(0, q.len()-1, time.Now()), "index should be 0")
|
||||
assert.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, time.Now()), "index should be 0")
|
||||
require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
|
||||
// 2
|
||||
expiry2 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
assert.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
|
||||
assert.EqualValues(t, 0, q.binarySearch(0, q.len()-1, expiry2), "index should be 0")
|
||||
require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, expiry2), "index should be 0")
|
||||
|
||||
assert.EqualValues(t, 1, q.binarySearch(0, q.len()-1, time.Now()), "index should be 1")
|
||||
require.EqualValues(t, 1, q.binarySearch(0, q.len()-1, time.Now()), "index should be 1")
|
||||
|
||||
// more
|
||||
for i := 0; i < 5; i++ {
|
||||
@ -85,5 +109,5 @@ func TestSearch(t *testing.T) {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 7, q.binarySearch(0, q.len()-1, expiry3), "index should be 7")
|
||||
require.EqualValues(t, 7, q.binarySearch(0, q.len()-1, expiry3), "index should be 7")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user