mirror of
https://github.com/panjf2000/ants.git
synced 2025-12-16 09:51:02 +00:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0e137a51c | ||
|
|
49d5ce6567 | ||
|
|
d12e26cb9e | ||
|
|
0de04f1c99 | ||
|
|
a44594205e | ||
|
|
1bf9cfdd1b | ||
|
|
6eb0590bc2 | ||
|
|
3120dab1dd | ||
|
|
160ee0a8b2 | ||
|
|
60bd4c42f9 | ||
|
|
9a1446b823 | ||
|
|
4f33c6ef27 | ||
|
|
d4218913f7 | ||
|
|
df029e6411 | ||
|
|
4acc96973c | ||
|
|
0ee85b0a1e | ||
|
|
99121e2404 | ||
|
|
e7e3c844aa | ||
|
|
2d40f3041b | ||
|
|
2a562a7c2a | ||
|
|
d85919e716 | ||
|
|
9df432d040 | ||
|
|
4d0ebb896a | ||
|
|
6169763f4f | ||
|
|
1e73dc2c7b | ||
|
|
29210901a0 | ||
|
|
313f136d00 | ||
|
|
b40e489286 | ||
|
|
da22980e2c | ||
|
|
95dad45c7d | ||
|
|
1933478e2e | ||
|
|
15e896153d | ||
|
|
3ffd3daa37 | ||
|
|
b2374d5ae4 | ||
|
|
0d650f5c1e | ||
|
|
ee5a7183d9 | ||
|
|
0729518fc6 | ||
|
|
34ff2c2282 | ||
|
|
83817c11bb | ||
|
|
9df33f340c | ||
|
|
ce28ca17d1 | ||
|
|
10d9975f10 | ||
|
|
0454cab594 | ||
|
|
1eb46544fb | ||
|
|
5c07bca122 | ||
|
|
74f5b2d330 | ||
|
|
306f027948 | ||
|
|
8b0eb06e60 | ||
|
|
1dbe4629aa | ||
|
|
fb82167503 | ||
|
|
19bd1ea02b | ||
|
|
27685ba408 | ||
|
|
d9a08d1309 | ||
|
|
f0b98c348a | ||
|
|
c8b4646d89 | ||
|
|
2ce8d85f28 | ||
|
|
45bc4f51ba | ||
|
|
aee9c2e2da | ||
|
|
1ce814699d | ||
|
|
16771ceb8f | ||
|
|
1da45fef96 | ||
|
|
2806c4af7c | ||
|
|
45a0390a9f | ||
|
|
a66958c9d3 | ||
|
|
d3b35b8db2 | ||
|
|
89ecc3ff68 | ||
|
|
46f9b68028 | ||
|
|
2c599b83a1 | ||
|
|
daef17d647 | ||
|
|
7be597c9e8 | ||
|
|
67b3a7a2c3 | ||
|
|
9fdd99a7b4 | ||
|
|
b9ac4d4730 | ||
|
|
bca5b3a7d6 | ||
|
|
b32591f8bd | ||
|
|
73defa0289 | ||
|
|
650c9db322 | ||
|
|
4b495fd500 | ||
|
|
55e222d20f | ||
|
|
e425c7b917 | ||
|
|
7a56a5c082 | ||
|
|
3110e41921 | ||
|
|
0313effc53 | ||
|
|
b880b659f5 | ||
|
|
22139c0295 | ||
|
|
33c77540bd | ||
|
|
711cad9624 | ||
|
|
88d2454bbb | ||
|
|
b6eaea118b | ||
|
|
858f91f48b | ||
|
|
4e0cb8cd03 | ||
|
|
b1b2df0c10 | ||
|
|
23c4f48d0d | ||
|
|
3fbd9567c9 | ||
|
|
7b1e246b0e | ||
|
|
846d76a437 | ||
|
|
03011bc512 | ||
|
|
668e945f4c |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [panjf2000]
|
||||
patreon: panjf2000
|
||||
open_collective: panjf2000
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
22
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@ -11,7 +11,7 @@ body:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Gitter](https://gitter.im/ants-pool/ants).
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
@ -64,6 +64,22 @@ body:
|
||||
- BSD
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS version
|
||||
description: What's the specific version of OS?
|
||||
placeholder: "Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: go-version
|
||||
attributes:
|
||||
label: Go version
|
||||
description: What's the specific version of Go?
|
||||
placeholder: "Run `go version` command to get the info, for example: go1.20.5 linux/amd64"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
@ -75,8 +91,8 @@ body:
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Code snippets (optional)
|
||||
description: Helpful code snippets can really speed up the process of locating root cause and fixing the bug.
|
||||
label: Reproducer
|
||||
description: Please provide the minimal code to reproduce the bug.
|
||||
render: go
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@ -11,7 +11,7 @@ body:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Gitter](https://gitter.im/ants-pool/ants).
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: textarea
|
||||
id: feature-request
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/question.yaml
vendored
2
.github/ISSUE_TEMPLATE/question.yaml
vendored
@ -9,7 +9,7 @@ body:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Gitter](https://gitter.im/ants-pool/ants).
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
|
||||
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@ -1,13 +1,5 @@
|
||||
---
|
||||
name: Pull request
|
||||
about: Propose changes to the code
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for contributing to `ants`! Please fill this out to help us make the most of your pull request.
|
||||
Thank you for contributing to `ants`! Please fill this out to help us review your pull request more efficiently.
|
||||
|
||||
Was this change discussed in an issue first? That can help save time in case the change is not a good fit for the project. Not all pull requests get merged.
|
||||
|
||||
|
||||
106
.github/release-drafter.yml
vendored
Normal file
106
.github/release-drafter.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name-template: Ants v$RESOLVED_VERSION
|
||||
tag-template: v$RESOLVED_VERSION
|
||||
categories:
|
||||
- title: 🧨 Breaking changes
|
||||
labels:
|
||||
- breaking changes
|
||||
- title: 🚀 Features
|
||||
labels:
|
||||
- proposal
|
||||
- new feature
|
||||
- title: 🛩 Enhancements
|
||||
labels:
|
||||
- enhancement
|
||||
- optimization
|
||||
- title: 🐛 Bugfixes
|
||||
labels:
|
||||
- bug
|
||||
- title: 📚 Documentation
|
||||
labels:
|
||||
- doc
|
||||
- docs
|
||||
- title: 🗃 Misc
|
||||
labels:
|
||||
- chores
|
||||
change-template: '- $TITLE (#$NUMBER)'
|
||||
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- major
|
||||
minor:
|
||||
labels:
|
||||
- minor
|
||||
- new feature
|
||||
- proposal
|
||||
patch:
|
||||
labels:
|
||||
- patch
|
||||
- bug
|
||||
- dependencies
|
||||
default: patch
|
||||
autolabeler:
|
||||
- label: bug
|
||||
title:
|
||||
- /fix/i
|
||||
- /bug/i
|
||||
- /resolve/i
|
||||
- label: docs
|
||||
files:
|
||||
- '*.md'
|
||||
title:
|
||||
- /doc/i
|
||||
- /README/i
|
||||
- label: enhancement
|
||||
title:
|
||||
- /opt:/i
|
||||
- /refactor/i
|
||||
- /optimize/i
|
||||
- /improve/i
|
||||
- /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
|
||||
- /feature/i
|
||||
- /implement/i
|
||||
- /add/i
|
||||
- /minor/i
|
||||
- label: dependencies
|
||||
title:
|
||||
- /dependencies/i
|
||||
- /upgrade/i
|
||||
- /bump up/i
|
||||
- label: chores
|
||||
title:
|
||||
- /chore/i
|
||||
- /misc/i
|
||||
- /cleanup/i
|
||||
- /clean up/i
|
||||
- label: major
|
||||
title:
|
||||
- /major:/i
|
||||
- label: minor
|
||||
title:
|
||||
- /minor:/i
|
||||
- label: patch
|
||||
title:
|
||||
- /patch:/i
|
||||
template: |
|
||||
## Changelogs
|
||||
|
||||
$CHANGES
|
||||
|
||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
||||
|
||||
Thanks to all these contributors: $CONTRIBUTORS for making this release possible.
|
||||
18
.github/workflows/codeql.yml
vendored
18
.github/workflows/codeql.yml
vendored
@ -5,10 +5,20 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- '!.github/workflows/codeql.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- '!.github/workflows/codeql.yml'
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
@ -32,20 +42,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below).
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@ -59,4 +69,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
19
.github/workflows/pull-request.yml
vendored
Normal file
19
.github/workflows/pull-request.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Check pull request target
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
check-branches:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check target branch
|
||||
run: |
|
||||
if [ ${{ github.head_ref }} != "dev" ]; then
|
||||
echo "Only pull requests from dev branch are only allowed to be merged into master branch."
|
||||
exit 1
|
||||
fi
|
||||
41
.github/workflows/release-drafter.yml
vendored
Normal file
41
.github/workflows/release-drafter.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
# branches to consider in the event; optional, defaults to all
|
||||
branches:
|
||||
- master
|
||||
# pull_request event is required only for autolabeler
|
||||
pull_request:
|
||||
# Only following types are handled by the action, but one can default to all as well
|
||||
types: [opened, reopened, synchronize]
|
||||
# pull_request_target event is required for autolabeler to support PRs from forks
|
||||
# pull_request_target:
|
||||
# types: [opened, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
# write permission is required to create a github release
|
||||
contents: write
|
||||
# write permission is required for autolabeler
|
||||
# otherwise, read permission is required at least
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# (Optional) GitHub Enterprise requires GHE_HOST variable set
|
||||
#- name: Set GHE_HOST
|
||||
# run: |
|
||||
# echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV
|
||||
|
||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
|
||||
# with:
|
||||
# config-name: my-config.yml
|
||||
# disable-autolabeler: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
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 }}
|
||||
56
.github/workflows/test.yml
vendored
56
.github/workflows/test.yml
vendored
@ -5,14 +5,22 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
- test
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- 'examples/*'
|
||||
- '!.github/workflows/test.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- 'examples/*'
|
||||
- '!.github/workflows/test.yml'
|
||||
|
||||
env:
|
||||
GO111MODULE: on
|
||||
@ -28,38 +36,39 @@ jobs:
|
||||
name: Run golangci-lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
|
||||
- name: Setup and run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.50.1
|
||||
args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot
|
||||
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.16, 1.17, 1.18, 1.19]
|
||||
go: [1.18, 1.23]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
name: Go ${{ matrix.go }} @ ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Print Go environment
|
||||
id: go-env
|
||||
@ -74,25 +83,16 @@ jobs:
|
||||
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache go modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ steps.go-env.outputs.GO_CACHE }}
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go }}-go-ci
|
||||
|
||||
- name: Run unit tests
|
||||
- name: Run unit tests and integrated tests
|
||||
run: go test -v -race -coverprofile="codecov.report" -covermode=atomic
|
||||
|
||||
- name: Upload code coverage report to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./codecov.report
|
||||
files: ./codecov.report
|
||||
flags: unittests
|
||||
name: codecov-ants
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
48
.travis.yml
48
.travis.yml
@ -1,48 +0,0 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
dist: bionic
|
||||
|
||||
go:
|
||||
# - 1.11.x
|
||||
# - 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
|
||||
go_import_path: github.com/panjf2000/ants
|
||||
|
||||
before_install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go";
|
||||
export PATH="$GOPATH/bin:$PATH"; fi
|
||||
- mkdir -p ~/bin/ && export PATH="~/bin/:$PATH"
|
||||
|
||||
install:
|
||||
- go get -u golang.org/x/lint/golint
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.26.0
|
||||
- curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b ~/bin
|
||||
|
||||
script:
|
||||
- |-
|
||||
case $TRAVIS_OS_NAME in
|
||||
linux|osx)
|
||||
golint ./... | reviewdog -f=golint -reporter=github-check
|
||||
golangci-lint run --out-format=line-number -E goimports -E misspell -E godot -E lll | reviewdog -f=golangci-lint -reporter=github-check
|
||||
golint ./... | reviewdog -f=golint -reporter=github-pr-review
|
||||
golangci-lint run --out-format=line-number -E goimports -E misspell -E godot -E lll | reviewdog -f=golangci-lint -reporter=github-pr-review
|
||||
;;
|
||||
esac
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic -v
|
||||
|
||||
after_success:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then curl -s https://codecov.io/bash >
|
||||
.codecov && chmod +x .codecov && ./.codecov; else bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
- secure: ajez2Mpm5Txz1bg1eG7qxZinuyC99IUTXDzlAHHqkkPxai8Pa95Zv5v540aRz3oC+TF4Nu9r+qxkn4VIgOc6GxE8j9ySQs5QflbvH74loNlkpWkOrSsimnYaFYvJQQZjbb1P0L29L9p+bZ72ZlDAjFaEBYeFH4MwG4qotVxepLmyOghaCsXzQNt9a0ETJ9o/0gnKpdd7W2Tqz1LBEwEZrunF0z8HQtHS1/HK9YA5Anxv4S5hchkKAQtaXg6y26dDW5ZEPAd1Uw3dTv1CHn35WBeI/l2wKNq1Xfq1gl2mKzKyxWSk1sbeHOYMMICB64YakAZWv6FYPuK1qiFGe0/LYrFc+//I0T7+1zuY9ZV692TbuPB+qkNp325Ma6eadU3tD9rhaXSizLFiLch5W7V1VYORRoeNXq+SThXr3gAJ+PwLNKca++jbiFi/WotO9/3RhiBtJHWIXTUejD028C6hGV0xgt5GtWyktdBITOkWCr7vZjM0oQEeSpXqySbnRkIuBsInEDW4bUbsyXpeZf/Ftj8OaEJFKtAgJkj8uR6ErfianorEIgGfmydyInooIxViW2C4eC3IrS3EgdMCdXsg1bxC6j5p5z7Nz7eQIRYk+ClhVSrGV7XJS6N/HmQw5dpK51iAp+rLjJsRHdprGHxZeaPSIYrGuBERwb0mvncCwFk=
|
||||
@ -6,7 +6,7 @@
|
||||
- Review existing issues and provide feedback or react to them.
|
||||
|
||||
## With pull requests:
|
||||
- Open your pull request against `master`.
|
||||
- Open your pull request against `dev`.
|
||||
- Open one pull request for only one feature/proposal, if you have several those, please put them into different PRs, whereas you are allowed to open one pull request with several bug-fixs.
|
||||
- Your pull request should have no more than two commits, if not, you should squash them.
|
||||
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||
|
||||
441
README.md
441
README.md
@ -2,12 +2,12 @@
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
|
||||
<b>A goroutine pool for Go</b>
|
||||
<br/><br/>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/workflow/status/panjf2000/ants/Tests?style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
|
||||
<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="Chat Room" target="_blank" href="https://gitter.im/ants-pool/ants?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/ants-pool/ants.svg" /></a>
|
||||
<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>
|
||||
@ -23,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 capacity of pool dynamically, releasing pool, rebooting 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
|
||||
|
||||
@ -61,200 +62,63 @@ go get -u github.com/panjf2000/ants/v2
|
||||
```
|
||||
|
||||
## 🛠 How to use
|
||||
Just take a imagination 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.
|
||||
|
||||
### Functional options for pool
|
||||
|
||||
`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.
|
||||
|
||||
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.
|
||||
|
||||
### Customize pool capacity
|
||||
|
||||
`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
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 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 pool. You can invoke the `NewPool` method to instantiate a pool with a given capacity, as following:
|
||||
|
||||
``` go
|
||||
// Set 10000 the size of goroutine pool
|
||||
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
|
||||
pool.Tune(100000) // Tune its capacity to 100000
|
||||
```
|
||||
|
||||
Don't worry about the synchronous problems in this case, the method here is thread-safe (or should be called goroutine-safe).
|
||||
Don't worry about the contention problems in this case, the method here is thread-safe (or should be called goroutine-safe).
|
||||
|
||||
### Pre-malloc goroutine queue in pool
|
||||
|
||||
`ants` allows you to pre-allocate memory of goroutine queue in 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.
|
||||
`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()
|
||||
```
|
||||
|
||||
@ -262,56 +126,17 @@ pool.Reboot()
|
||||
|
||||
All tasks submitted to `ants` pool will not be guaranteed to be addressed in order, because those tasks scatter among a series of concurrent workers, thus those tasks would be executed concurrently.
|
||||
|
||||
## 🧲 Benchmarks
|
||||
|
||||
<div align="center"><img src="https://user-images.githubusercontent.com/7496278/51515466-c7ce9e00-1e4e-11e9-89c4-bd3785b3c667.png"/></div>
|
||||
In this benchmark result, the first and second benchmarks performed test cases with 1M tasks and the rest of benchmarks performed test cases with 10M tasks, both in unlimited goroutines and `ants` pool, and the capacity of this `ants` goroutine-pool was limited to 50K.
|
||||
|
||||
- BenchmarkGoroutine-4 represents the benchmarks with unlimited goroutines in golang.
|
||||
|
||||
- BenchmarkPoolGroutine-4 represents the benchmarks with a `ants` pool.
|
||||
|
||||
### Benchmarks with Pool
|
||||
|
||||

|
||||
|
||||
In above benchmark result, the first and second benchmarks performed test cases with 1M tasks and the rest of benchmarks performed test cases with 10M tasks, both in unlimited goroutines and `ants` pool, and the capacity of this `ants` goroutine-pool was limited to 50K.
|
||||
|
||||
**As you can see, `ants` performs 2 times faster than goroutines without pool (10M tasks) and it only consumes half the memory comparing with goroutines without pool. (both in 1M and 10M tasks)**
|
||||
|
||||
### Benchmarks with PoolWithFunc
|
||||
|
||||

|
||||
|
||||
### Throughput (it is suitable for scenarios where tasks are submitted asynchronously without waiting for the final results)
|
||||
|
||||
#### 100K tasks
|
||||
|
||||

|
||||
|
||||
#### 1M tasks
|
||||
|
||||

|
||||
|
||||
#### 10M tasks
|
||||
|
||||

|
||||
|
||||
## 📊 Performance Summary
|
||||
|
||||

|
||||
|
||||
**In conclusion, `ants` performs 2~6 times faster than goroutines without a pool and the memory consumption is reduced by 10 to 20 times.**
|
||||
|
||||
## 👏 Contributors
|
||||
|
||||
Please read our [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `ants`!
|
||||
|
||||
[](https://github.com/panjf2000/ants/graphs/contributors)
|
||||
<a href="https://github.com/panjf2000/ants/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=panjf2000/ants" />
|
||||
</a>
|
||||
|
||||
## 📄 License
|
||||
|
||||
Source code in `ants` is available under the [MIT License](/LICENSE).
|
||||
The source code in `ants` is available under the [MIT License](/LICENSE).
|
||||
|
||||
## 📚 Relevant Articles
|
||||
|
||||
@ -322,23 +147,163 @@ Source code in `ants` is available under the [MIT License](/LICENSE).
|
||||
|
||||
## 🖥 Use cases
|
||||
|
||||
### business companies
|
||||
### business corporations & open-source organizations
|
||||
|
||||
The following companies/organizations use `ants` in production.
|
||||
Trusted by the following corporations/organizations.
|
||||
|
||||
<a href="https://www.tencent.com"><img src="http://img.taohuawu.club/gallery/tencent_logo.png" width="250" align="middle"/></a> <a href="https://www.bytedance.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/ByteDance_Logo.png" width="250" align="middle"/></a> <a href="https://tieba.baidu.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/baidu-tieba-logo.png" width="300" align="middle"/></a> <a href="https://www.sina.com.cn/" target="_blank"><img src="http://img.taohuawu.club/gallery/sina-logo.png" width="200" align="middle"/></a> <a href="https://www.163.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/netease-logo.png" width="150" align="middle"/></a> <a href="https://www.tencentmusic.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/tencent-music-logo.png" width="250" align="middle"/></a> <a href="https://www.futuhk.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/futu-logo.png" width="250" align="middle"/></a> <a href="https://www.shopify.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/shopify-logo.png" width="250" align="middle"/></a> <a href="https://www.wechat.com/en/" target="_blank"><img src="http://img.taohuawu.club/gallery/wechat-logo.png" width="250" align="middle"/></a><a href="https://www.baidu.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/baidu-mobile.png" width="250" align="middle"/></a>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<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">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tieba.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weibo.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<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/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.shopify.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.wechat.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<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-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<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/intl/en-us/" target="_blank">
|
||||
<img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/%E7%BB%84%E4%BB%B6%E9%AA%8C%E8%AF%81/pep-common-header/logo-en.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<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">
|
||||
<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">
|
||||
<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-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<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://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>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.apache.org/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.coze.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/coze-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`.
|
||||
|
||||
- [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
|
||||
- [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
|
||||
- [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.
|
||||
- [BillionMail](https://github.com/aaPanel/BillionMail): A future open-source Mail server, Email marketing platform designed to help businesses and individuals manage their email campaigns with ease.
|
||||
- [WeKnora](https://github.com/Tencent/WeKnora): An LLM-powered framework designed for deep document understanding and semantic retrieval, especially for handling complex, heterogeneous documents.
|
||||
- [coze-loop](https://github.com/coze-dev/coze-loop): A developer-oriented, platform-level solution focused on the development and operation of AI agents.
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu): 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.
|
||||
- [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.
|
||||
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
|
||||
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
|
||||
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
|
||||
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
|
||||
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
|
||||
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne is a future-oriented hyper-converged cloud and edge native DBMS that supports transactional, analytical, and streaming workloads with a simplified and distributed database engine, across multiple data centers, clouds, edges and other heterogeneous infrastructures.
|
||||
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): BlueKing Container Service (BCS, same below) is a container management and orchestration platform for the micro-services under the BlueKing ecosystem.
|
||||
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
|
||||
- [openGemini](https://github.com/openGemini/openGemini): openGemini is an open-source,cloud-native time-series database(TSDB) that can be widely used in IoT, Internet of Vehicles(IoV), O&M monitoring, and industrial Internet scenarios.
|
||||
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
|
||||
- [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): 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:
|
||||
|
||||
@ -350,33 +315,35 @@ 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>
|
||||
|
||||
## 💰 Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities.
|
||||
|
||||
<a href="https://opencollective.com/ants#backers" target="_blank"><img src="https://opencollective.com/ants/backers.svg"></a>
|
||||
|
||||
## 💎 Sponsors
|
||||
|
||||
Become a bronze sponsor with a monthly donation of $10 and get your logo on our README on Github.
|
||||
|
||||
<a href="https://opencollective.com/ants#sponsors" target="_blank"><img src="https://opencollective.com/ants/sponsors.svg"></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>
|
||||
|
||||
## ☕️ Buy me a coffee
|
||||
|
||||
> Please be sure to leave your name, GitHub account or other social media accounts when you donate by the following means so that I can add it to the list of donors as a token of my appreciation.
|
||||
> Please be sure to leave your name, GitHub account, or other social media accounts when you donate by the following means so that I can add it to the list of donors as a token of my appreciation.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/WeChatPay.JPG" width="250" align="middle"/>
|
||||
<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
|
||||
|
||||
<a target="_blank" href="https://github.com/patrick-othmer"><img src="https://avatars1.githubusercontent.com/u/8964313" width="100" alt="Patrick Othmer" /></a> <a target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://avatars2.githubusercontent.com/u/50285334" width="100" alt="Jimmy" /></a> <a target="_blank" href="https://github.com/cafra"><img src="https://avatars0.githubusercontent.com/u/13758306" width="100" alt="ChenZhen" /></a> <a target="_blank" href="https://github.com/yangwenmai"><img src="https://avatars0.githubusercontent.com/u/1710912" width="100" alt="Mai Yang" /></a> <a target="_blank" href="https://github.com/BeijingWks"><img src="https://avatars3.githubusercontent.com/u/33656339" width="100" alt="王开帅" /></a> <a target="_blank" href="https://github.com/refs"><img src="https://avatars3.githubusercontent.com/u/6905948" width="100" alt="Unger Alejandro" /></a> <a target="_blank" href="https://github.com/Wuvist"><img src="https://avatars.githubusercontent.com/u/657796" width="100" alt="Weng Wei" /></a>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://buymeacoffee.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="By me coffee" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://www.patreon.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://opencollective.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔋 Sponsorship
|
||||
|
||||
|
||||
413
README_ZH.md
413
README_ZH.md
@ -2,12 +2,12 @@
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
|
||||
<b>Go 语言的 goroutine 池</b>
|
||||
<br/><br/>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/workflow/status/panjf2000/ants/Tests?style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
|
||||
<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="Chat Room" target="_blank" href="https://gitter.im/ants-pool/ants?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/ants-pool/ants.svg" /></a>
|
||||
<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>
|
||||
@ -23,10 +23,11 @@
|
||||
|
||||
- 自动调度海量的 goroutines,复用 goroutines
|
||||
- 定期清理过期的 goroutines,进一步节省资源
|
||||
- 提供了大量有用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool
|
||||
- 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等
|
||||
- 优雅处理 panic,防止程序崩溃
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下比原生 goroutine 并发具有[更高的性能](#-性能小结)
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能***
|
||||
- 非阻塞机制
|
||||
- 预分配内存 (环形队列,可选)
|
||||
|
||||
## 💡 `ants` 是如何运行的
|
||||
|
||||
@ -61,174 +62,31 @@ 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)
|
||||
}
|
||||
```
|
||||
基本的使用请查看[示例](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
|
||||
// Set 10000 the size of goroutine pool
|
||||
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
|
||||
@ -239,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))
|
||||
```
|
||||
|
||||
@ -252,6 +110,12 @@ p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
pool.Release()
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```go
|
||||
pool.ReleaseTimeout(time.Second * 3)
|
||||
```
|
||||
|
||||
### 重启 Pool
|
||||
|
||||
```go
|
||||
@ -263,52 +127,13 @@ pool.Reboot()
|
||||
|
||||
`ants` 并不保证提交的任务被执行的顺序,执行的顺序也不是和提交的顺序保持一致,因为在 `ants` 是并发地处理所有提交的任务,提交的任务会被分派到正在并发运行的 workers 上去,因此那些任务将会被并发且无序地被执行。
|
||||
|
||||
## 🧲 Benchmarks
|
||||
|
||||
<div align="center"><img src="https://user-images.githubusercontent.com/7496278/51515466-c7ce9e00-1e4e-11e9-89c4-bd3785b3c667.png"/></div>
|
||||
上图中的前两个 benchmark 测试结果是基于100w 任务量的条件,剩下的几个是基于 1000w 任务量的测试结果,`ants` 的默认池容量是 5w。
|
||||
|
||||
- BenchmarkGoroutine-4 代表原生 goroutine
|
||||
|
||||
- BenchmarkPoolGroutine-4 代表使用 goroutine 池 `ants`
|
||||
|
||||
### Benchmarks with Pool
|
||||
|
||||

|
||||
|
||||
**这里为了模拟大规模 goroutine 的场景,两次测试的并发次数分别是 100w 和 1000w,前两个测试分别是执行 100w 个并发任务不使用 Pool 和使用了`ants`的 Goroutine Pool 的性能,后两个则是 1000w 个任务下的表现,可以直观的看出在执行速度和内存使用上,`ants`的 Pool 都占有明显的优势。100w 的任务量,使用`ants`,执行速度与原生 goroutine 相当甚至略快,但只实际使用了不到 5w 个 goroutine 完成了全部任务,且内存消耗仅为原生并发的 40%;而当任务量达到 1000w,优势则更加明显了:用了 70w 左右的 goroutine 完成全部任务,执行速度比原生 goroutine 提高了 100%,且内存消耗依旧保持在不使用 Pool 的 40% 左右。**
|
||||
|
||||
### Benchmarks with PoolWithFunc
|
||||
|
||||

|
||||
|
||||
**因为`PoolWithFunc`这个 Pool 只绑定一个任务函数,也即所有任务都是运行同一个函数,所以相较于`Pool`对原生 goroutine 在执行速度和内存消耗的优势更大,上面的结果可以看出,执行速度可以达到原生 goroutine 的 300%,而内存消耗的优势已经达到了两位数的差距,原生 goroutine 的内存消耗达到了`ants`的35倍且原生 goroutine 的每次执行的内存分配次数也达到了`ants`45倍,1000w 的任务量,`ants`的初始分配容量是 5w,因此它完成了所有的任务依旧只使用了 5w 个 goroutine!事实上,`ants`的 Goroutine Pool 的容量是可以自定义的,也就是说使用者可以根据不同场景对这个参数进行调优直至达到最高性能。**
|
||||
|
||||
### 吞吐量测试(适用于那种只管提交异步任务而无须关心结果的场景)
|
||||
|
||||
#### 10w 任务量
|
||||
|
||||

|
||||
|
||||
#### 100w 任务量
|
||||
|
||||

|
||||
|
||||
#### 1000w 任务量
|
||||
|
||||

|
||||
|
||||
## 📊 性能小结
|
||||
|
||||

|
||||
|
||||
**从该 demo 测试吞吐性能对比可以看出,使用`ants`的吞吐性能相较于原生 goroutine 可以保持在 2-6 倍的性能压制,而内存消耗则可以达到 10-20 倍的节省优势。**
|
||||
|
||||
## 👏 贡献者
|
||||
|
||||
请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `ants` 贡献过代码的开发者!
|
||||
|
||||
[](https://github.com/panjf2000/ants/graphs/contributors)
|
||||
<a href="https://github.com/panjf2000/ants/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=panjf2000/ants" />
|
||||
</a>
|
||||
|
||||
## 📄 证书
|
||||
|
||||
@ -323,23 +148,161 @@ pool.Reboot()
|
||||
|
||||
## 🖥 用户案例
|
||||
|
||||
### 商业公司
|
||||
### 商业公司和开源组织
|
||||
|
||||
以下公司/组织在生产环境上使用了 `ants`。
|
||||
|
||||
<a href="https://www.tencent.com"><img src="http://img.taohuawu.club/gallery/tencent_logo.png" width="250" align="middle"/></a> <a href="https://www.bytedance.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/ByteDance_Logo.png" width="250" align="middle"/></a> <a href="https://tieba.baidu.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/baidu-tieba-logo.png" width="300" align="middle"/></a> <a href="https://www.sina.com.cn/" target="_blank"><img src="http://img.taohuawu.club/gallery/sina-logo.png" width="200" align="middle"/></a> <a href="https://www.163.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/netease-logo.png" width="150" align="middle"/></a> <a href="https://www.tencentmusic.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/tencent-music-logo.png" width="250" align="middle"/></a> <a href="https://www.futuhk.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/futu-logo.png" width="250" align="middle"/></a> <a href="https://www.shopify.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/shopify-logo.png" width="250" align="middle"/></a> <a href="https://weixin.qq.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/wechat-logo.png" width="250" align="middle"/></a><a href="https://www.baidu.com/" target="_blank"><img src="http://img.taohuawu.club/gallery/baidu-mobile.png" width="250" align="middle"/></a>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<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/zh/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tieba.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weibo.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencentmusic.com/" 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">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.shopify.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weixin.qq.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<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-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<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://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>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.apache.org/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.coze.cn/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/coze-logo-cn.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。
|
||||
|
||||
### 开源软件
|
||||
|
||||
- [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
|
||||
- [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
|
||||
- [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications.
|
||||
这些开源项目借助 `ants` 进行并发编程。
|
||||
|
||||
- [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): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
|
||||
- [BillionMail](https://github.com/aaPanel/BillionMail): BillionMail 是一个未来的开源邮件服务器和电子邮件营销平台,旨在帮助企业和个人轻松管理他们的电子邮件营销活动。
|
||||
- [WeKnora](https://github.com/Tencent/WeKnora): 一款基于大语言模型(LLM)的文档理解与语义检索框架,专为结构复杂、内容异构的文档场景而打造。
|
||||
- [coze-loop](https://github.com/coze-dev/coze-loop): Coze Loop 是一个面向开发者,专注于 AI Agent 开发与运维的平台级解决方案。
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu): 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.
|
||||
- [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.
|
||||
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
|
||||
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
|
||||
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
|
||||
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
|
||||
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
|
||||
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne 是一款面向未来的超融合异构云原生数据库,通过超融合数据引擎支持事务/分析/流处理等混合工作负载,通过异构云原生架构支持跨机房协同/多地协同/云边协同。简化开发运维,消简数据碎片,打破数据的系统、位置和创新边界。
|
||||
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): 蓝鲸容器管理平台(Blueking Container Service)定位于打造云原生技术和业务实际应用场景之间的桥梁;聚焦于复杂应用场景的容器化部署技术方案的研发、整合和产品化;致力于为游戏等复杂应用提供一站式、低门槛的容器编排和服务治理服务。
|
||||
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
|
||||
- [openGemini](https://github.com/openGemini/openGemini): openGemini 是华为云开源的一款云原生分布式时序数据库,可广泛应用于物联网、车联网、运维监控、工业互联网等业务场景,具备卓越的读写性能和高效的数据分析能力,采用类SQL查询语言,无第三方软件依赖、安装简单、部署灵活、运维便捷。
|
||||
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
|
||||
- [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): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。
|
||||
|
||||
#### 所有案例:
|
||||
|
||||
@ -353,31 +316,33 @@ 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://opencollective.com/ants#backers" target="_blank"><img src="https://opencollective.com/ants/backers.svg"></a>
|
||||
|
||||
## 💎 赞助
|
||||
|
||||
每月定量捐赠 10 刀即可成为本项目的赞助者,届时您的 logo 或者 link 可以展示在本项目的 README 上。
|
||||
|
||||
<a href="https://opencollective.com/ants#sponsors" target="_blank"><img src="https://opencollective.com/ants/sponsors.svg"></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>
|
||||
|
||||
## ☕️ 打赏
|
||||
|
||||
> 当您通过以下方式进行捐赠时,请务必留下姓名、GitHub 账号或其他社交媒体账号,以便我将其添加到捐赠者名单中,以表谢意。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/payments/WeChatPay.JPG" width="250" align="middle"/>
|
||||
<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>
|
||||
|
||||
## 资助者
|
||||
|
||||
<a target="_blank" href="https://github.com/patrick-othmer"><img src="https://avatars1.githubusercontent.com/u/8964313" width="100" alt="Patrick Othmer" /></a> <a target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://avatars2.githubusercontent.com/u/50285334" width="100" alt="Jimmy" /></a> <a target="_blank" href="https://github.com/cafra"><img src="https://avatars0.githubusercontent.com/u/13758306" width="100" alt="ChenZhen" /></a> <a target="_blank" href="https://github.com/yangwenmai"><img src="https://avatars0.githubusercontent.com/u/1710912" width="100" alt="Mai Yang" /></a> <a target="_blank" href="https://github.com/BeijingWks"><img src="https://avatars3.githubusercontent.com/u/33656339" width="100" alt="王开帅" /></a> <a target="_blank" href="https://github.com/refs"><img src="https://avatars3.githubusercontent.com/u/6905948" width="100" alt="Unger Alejandro" /></a> <a target="_blank" href="https://github.com/Wuvist"><img src="https://avatars.githubusercontent.com/u/657796" width="100" alt="Weng Wei" /></a>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://buymeacoffee.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="By me coffee" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://www.patreon.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://opencollective.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔋 赞助商
|
||||
|
||||
|
||||
414
ants.go
414
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 (
|
||||
@ -66,6 +78,15 @@ var (
|
||||
// ErrTimeout will be returned after the operations timed out.
|
||||
ErrTimeout = errors.New("operation timed out")
|
||||
|
||||
// ErrInvalidPoolIndex will be returned when trying to retrieve a pool with an invalid index.
|
||||
ErrInvalidPoolIndex = errors.New("invalid pool index")
|
||||
|
||||
// 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
|
||||
@ -82,18 +103,12 @@ var (
|
||||
return 1
|
||||
}()
|
||||
|
||||
defaultLogger = Logger(log.New(os.Stderr, "", log.LstdFlags))
|
||||
defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds))
|
||||
|
||||
// Init an instance pool when importing ants.
|
||||
defaultAntsPool, _ = NewPool(DefaultAntsPoolSize)
|
||||
)
|
||||
|
||||
// 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)
|
||||
@ -119,7 +134,392 @@ func Release() {
|
||||
defaultAntsPool.Release()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func ReleaseTimeout(timeout time.Duration) error {
|
||||
return defaultAntsPool.ReleaseTimeout(timeout)
|
||||
}
|
||||
|
||||
// Reboot reboots the default pool.
|
||||
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,19 +20,24 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
package ants_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
RunTimes = 1000000
|
||||
RunTimes = 1e6
|
||||
PoolCap = 5e4
|
||||
BenchParam = 10
|
||||
BenchAntsSize = 200000
|
||||
DefaultExpiredTime = 10 * time.Second
|
||||
)
|
||||
|
||||
@ -40,25 +45,29 @@ 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() {
|
||||
for {
|
||||
for atomic.LoadInt32(&stopLongRunningFunc) == 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func longRunningPoolFunc(arg interface{}) {
|
||||
if ch, ok := arg.(chan struct{}); ok {
|
||||
func longRunningPoolFunc(arg any) {
|
||||
<-arg.(chan struct{})
|
||||
}
|
||||
|
||||
func longRunningPoolFuncCh(ch chan struct{}) {
|
||||
<-ch
|
||||
return
|
||||
}
|
||||
for {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoroutines(b *testing.B) {
|
||||
@ -75,10 +84,11 @@ func BenchmarkGoroutines(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSemaphore(b *testing.B) {
|
||||
func BenchmarkChannel(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
sema := make(chan struct{}, BenchAntsSize)
|
||||
sema := make(chan struct{}, PoolCap)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
@ -93,12 +103,49 @@ func BenchmarkSemaphore(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkErrGroup(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
var pool errgroup.Group
|
||||
pool.SetLimit(PoolCap)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
pool.Go(func() error {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAntsPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := NewPool(BenchAntsSize, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.StartTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAntsMultiPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
@ -109,7 +156,6 @@ func BenchmarkAntsPool(b *testing.B) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkGoroutinesThroughput(b *testing.B) {
|
||||
@ -121,7 +167,7 @@ func BenchmarkGoroutinesThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkSemaphoreThroughput(b *testing.B) {
|
||||
sema := make(chan struct{}, BenchAntsSize)
|
||||
sema := make(chan struct{}, PoolCap)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
sema <- struct{}{}
|
||||
@ -134,13 +180,49 @@ func BenchmarkSemaphoreThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := NewPool(BenchAntsSize, WithExpiryDuration(DefaultExpiredTime))
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
b.StartTimer()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
1110
ants_test.go
1110
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
|
||||
}
|
||||
@ -1,84 +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 method,
|
||||
// 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!!!")
|
||||
}
|
||||
}
|
||||
7
go.mod
7
go.mod
@ -2,10 +2,13 @@ module github.com/panjf2000/ants/v2
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/stretchr/testify v1.7.1
|
||||
require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/sync v0.11.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.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
13
go.sum
13
go.sum
@ -1,13 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
229
multipool.go
Normal file
229
multipool.go
Normal file
@ -0,0 +1,229 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2023 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"
|
||||
)
|
||||
|
||||
// LoadBalancingStrategy represents the type of load-balancing algorithm.
|
||||
type LoadBalancingStrategy int
|
||||
|
||||
const (
|
||||
// RoundRobin distributes task to a list of pools in rotation.
|
||||
RoundRobin LoadBalancingStrategy = 1 << (iota + 1)
|
||||
|
||||
// LeastTasks always selects the pool with the least number of pending tasks.
|
||||
LeastTasks
|
||||
)
|
||||
|
||||
// MultiPool consists of multiple pools, from which you will benefit the
|
||||
// performance improvement on basis of the fine-grained locking that reduces
|
||||
// the lock contention.
|
||||
// MultiPool is a good fit for the scenario where you have a large number of
|
||||
// tasks to submit, and you don't want the single pool to be the bottleneck.
|
||||
type MultiPool struct {
|
||||
pools []*Pool
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
pools := make([]*Pool, size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPool(sizePerPool, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPool) 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
|
||||
}
|
||||
|
||||
// Submit submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPool) Submit(task func()) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
||||
return mp.pools[mp.next(LeastTasks)].Submit(task)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running workers across all pools.
|
||||
func (mp *MultiPool) 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 *MultiPool) 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 *MultiPool) 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 *MultiPool) 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 *MultiPool) 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 *MultiPool) 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 *MultiPool) 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 *MultiPool) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPool) 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 *MultiPool) 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 *Pool, 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 *MultiPool) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
219
multipool_func.go
Normal file
219
multipool_func.go
Normal file
@ -0,0 +1,219 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2023 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"
|
||||
)
|
||||
|
||||
// MultiPoolWithFunc consists of multiple pools, from which you will benefit the
|
||||
// performance improvement on basis of the fine-grained locking that reduces
|
||||
// the lock contention.
|
||||
// MultiPoolWithFunc is a good fit for the scenario where you have a large number of
|
||||
// tasks to submit, and you don't want the single pool to be the bottleneck.
|
||||
type MultiPoolWithFunc struct {
|
||||
pools []*PoolWithFunc
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// 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(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
pools := make([]*PoolWithFunc, size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPoolWithFunc(sizePerPool, fn, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPoolWithFunc) 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 *MultiPoolWithFunc) Invoke(args any) (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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *MultiPoolWithFunc) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPoolWithFunc) 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 *MultiPoolWithFunc) 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 *PoolWithFunc, 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 *MultiPoolWithFunc) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
32
options.go
32
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"
|
||||
@ -33,8 +55,10 @@ type Options struct {
|
||||
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{})
|
||||
// If nil, the default behavior is to capture the value given to panic
|
||||
// and resume normal execution and print that value along with the
|
||||
// stack trace of the goroutine
|
||||
PanicHandler func(any)
|
||||
|
||||
// Logger is the customized logger for logging info, if it is not set,
|
||||
// default standard logger from log package is used.
|
||||
@ -44,7 +68,7 @@ type Options struct {
|
||||
DisablePurge bool
|
||||
}
|
||||
|
||||
// WithOptions accepts the whole options config.
|
||||
// WithOptions accepts the whole Options config.
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
@ -80,7 +104,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
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
package sync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
package sync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
@ -14,12 +14,11 @@ import (
|
||||
/*
|
||||
Benchmark result for three types of locks:
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
pkg: github.com/panjf2000/ants/v2/internal
|
||||
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
|
||||
BenchmarkMutex-12 20549502 71.84 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSpinLock-12 58629697 20.02 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBackOffSpinLock-12 72523454 15.74 ns/op 0 B/op 0 allocs/op
|
||||
goarch: arm64
|
||||
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
|
||||
*/
|
||||
|
||||
type originSpinLock uint32
|
||||
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
|
||||
344
pool.go
344
pool.go
@ -22,342 +22,44 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2/internal"
|
||||
)
|
||||
|
||||
// Pool accepts the tasks from client, 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 {
|
||||
// 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 workerArray
|
||||
|
||||
// state is used to notice the pool to closed itself.
|
||||
state int32
|
||||
|
||||
// cond for waiting to get an idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// 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
|
||||
|
||||
heartbeatDone int32
|
||||
stopHeartbeat context.CancelFunc
|
||||
|
||||
options *Options
|
||||
*poolCommon
|
||||
}
|
||||
|
||||
// purgePeriodically clears expired workers periodically which runs in an individual goroutine, as a scavenger.
|
||||
func (p *Pool) purgePeriodically(ctx context.Context) {
|
||||
heartbeat := time.NewTicker(p.options.ExpiryDuration)
|
||||
|
||||
defer func() {
|
||||
heartbeat.Stop()
|
||||
atomic.StoreInt32(&p.heartbeatDone, 1)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeat.C:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
expiredWorkers := p.workers.retrieveExpiry(p.options.ExpiryDuration)
|
||||
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 expiredWorkers {
|
||||
expiredWorkers[i].task <- nil
|
||||
expiredWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up(no worker is running),
|
||||
// or another case where the pool capacity has been Tuned up,
|
||||
// while some invokers still get stuck in "p.cond.Wait()",
|
||||
// then it ought to wake all those invokers.
|
||||
if p.Running() == 0 || (p.Waiting() > 0 && p.Free() > 0) {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewPool generates an instance of ants pool.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
opts := loadOptions(options...)
|
||||
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
}
|
||||
|
||||
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{
|
||||
capacity: int32(size),
|
||||
lock: internal.NewSpinLock(),
|
||||
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 = newWorkerArray(loopQueueType, size)
|
||||
} else {
|
||||
p.workers = newWorkerArray(stackType, 0)
|
||||
}
|
||||
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
var ctx context.Context
|
||||
ctx, p.stopHeartbeat = context.WithCancel(context.Background())
|
||||
if !p.options.DisablePurge {
|
||||
go p.purgePeriodically(ctx)
|
||||
}
|
||||
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 latest
|
||||
// but what calls for special attention is that you will get blocked with the last
|
||||
// Pool.Submit() call once the current Pool runs out of its capacity, and to avoid this,
|
||||
// you should instantiate a Pool with ants.WithNonblocking(true).
|
||||
func (p *Pool) Submit(task func()) error {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
var w *goWorker
|
||||
if w = p.retrieveWorker(); w == nil {
|
||||
return ErrPoolOverload
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.inputFunc(task)
|
||||
}
|
||||
w.task <- task
|
||||
return nil
|
||||
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 goroutines to work, -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 which are waiting 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
|
||||
}
|
||||
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.stopHeartbeat == nil {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
p.stopHeartbeat()
|
||||
p.stopHeartbeat = nil
|
||||
p.Release()
|
||||
|
||||
endTime := time.Now().Add(timeout)
|
||||
for time.Now().Before(endTime) {
|
||||
if p.Running() == 0 && (p.options.DisablePurge || atomic.LoadInt32(&p.heartbeatDone) == 1) {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool.
|
||||
func (p *Pool) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.heartbeatDone, 0)
|
||||
var ctx context.Context
|
||||
ctx, p.stopHeartbeat = context.WithCancel(context.Background())
|
||||
if !p.options.DisablePurge {
|
||||
go p.purgePeriodically(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (p *Pool) addRunning(delta 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 *goWorker) {
|
||||
spawnWorker := func() {
|
||||
w = p.workerCache.Get().(*goWorker)
|
||||
w.run()
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
|
||||
w = p.workers.detach()
|
||||
if w != nil { // first try to fetch the worker from the queue
|
||||
p.lock.Unlock()
|
||||
} else if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
// if the worker queue is empty and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
} else { // otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
if p.options.Nonblocking {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
retry:
|
||||
if p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var nw int
|
||||
if nw = p.Running(); nw == 0 { // awakened by the scavenger
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return
|
||||
}
|
||||
if w = p.workers.detach(); w == nil {
|
||||
if nw < p.Cap() {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return
|
||||
}
|
||||
goto retry
|
||||
}
|
||||
p.lock.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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.recycleTime = time.Now()
|
||||
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
|
||||
}
|
||||
|
||||
err := p.workers.insert(worker)
|
||||
// NewPool instantiates a Pool with customized options.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
return true
|
||||
pool := &Pool{poolCommon: pc}
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorker{
|
||||
pool: pool,
|
||||
task: make(chan func(), workerChanCap),
|
||||
}
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
368
pool_func.go
368
pool_func.go
@ -22,366 +22,54 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2/internal"
|
||||
)
|
||||
|
||||
// PoolWithFunc accepts the tasks from client,
|
||||
// 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 {
|
||||
// capacity of the pool.
|
||||
capacity int32
|
||||
*poolCommon
|
||||
|
||||
// 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 []*goWorkerWithFunc
|
||||
|
||||
// state is used to notice the pool to closed itself.
|
||||
state int32
|
||||
|
||||
// cond for waiting to get an idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// poolFunc is the function for processing tasks.
|
||||
poolFunc func(interface{})
|
||||
|
||||
// workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// waiting is the number of the goroutines already been blocked on pool.Invoke(), protected by pool.lock
|
||||
waiting int32
|
||||
|
||||
heartbeatDone int32
|
||||
stopHeartbeat context.CancelFunc
|
||||
|
||||
options *Options
|
||||
}
|
||||
|
||||
// purgePeriodically clears expired workers periodically which runs in an individual goroutine, as a scavenger.
|
||||
func (p *PoolWithFunc) purgePeriodically(ctx context.Context) {
|
||||
heartbeat := time.NewTicker(p.options.ExpiryDuration)
|
||||
defer func() {
|
||||
heartbeat.Stop()
|
||||
atomic.StoreInt32(&p.heartbeatDone, 1)
|
||||
}()
|
||||
|
||||
var expiredWorkers []*goWorkerWithFunc
|
||||
for {
|
||||
select {
|
||||
case <-heartbeat.C:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
// fn is the unified function for processing tasks.
|
||||
fn func(any)
|
||||
}
|
||||
|
||||
// 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(arg any) error {
|
||||
if p.IsClosed() {
|
||||
break
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers)
|
||||
var i int
|
||||
for i = 0; i < n && currentTime.Sub(idleWorkers[i].recycleTime) > p.options.ExpiryDuration; i++ {
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.inputArg(arg)
|
||||
}
|
||||
expiredWorkers = append(expiredWorkers[:0], idleWorkers[:i]...)
|
||||
if i > 0 {
|
||||
m := copy(idleWorkers, idleWorkers[i:])
|
||||
for i = m; i < n; i++ {
|
||||
idleWorkers[i] = nil
|
||||
}
|
||||
p.workers = idleWorkers[:m]
|
||||
}
|
||||
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, w := range expiredWorkers {
|
||||
w.args <- nil
|
||||
expiredWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up(no worker is running),
|
||||
// or another case where the pool capacity has been Tuned up,
|
||||
// while some invokers still get stuck in "p.cond.Wait()",
|
||||
// then it ought to wake all those invokers.
|
||||
if p.Running() == 0 || (p.Waiting() > 0 && p.Free() > 0) {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewPoolWithFunc generates an instance of ants pool with a specific function.
|
||||
func NewPoolWithFunc(size int, pf func(interface{}), options ...Option) (*PoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPoolWithFunc instantiates a PoolWithFunc with customized options.
|
||||
func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) {
|
||||
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
|
||||
}
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = defaultLogger
|
||||
pool := &PoolWithFunc{
|
||||
poolCommon: pc,
|
||||
fn: pf,
|
||||
}
|
||||
|
||||
p := &PoolWithFunc{
|
||||
capacity: int32(size),
|
||||
poolFunc: pf,
|
||||
lock: internal.NewSpinLock(),
|
||||
options: opts,
|
||||
}
|
||||
p.workerCache.New = func() interface{} {
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorkerWithFunc{
|
||||
pool: p,
|
||||
args: make(chan interface{}, workerChanCap),
|
||||
}
|
||||
}
|
||||
if p.options.PreAlloc {
|
||||
if size == -1 {
|
||||
return nil, ErrInvalidPreAllocSize
|
||||
}
|
||||
p.workers = make([]*goWorkerWithFunc, 0, size)
|
||||
}
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
var ctx context.Context
|
||||
ctx, p.stopHeartbeat = context.WithCancel(context.Background())
|
||||
if !p.options.DisablePurge {
|
||||
go p.purgePeriodically(ctx)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Invoke submits a task to 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 latest
|
||||
// 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 {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
var w *goWorkerWithFunc
|
||||
if w = p.retrieveWorker(); w == nil {
|
||||
return ErrPoolOverload
|
||||
}
|
||||
w.args <- args
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 goroutines to work, -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 which are waiting 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()
|
||||
pool: pool,
|
||||
arg: make(chan any, workerChanCap),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
for _, w := range idleWorkers {
|
||||
w.args <- nil
|
||||
}
|
||||
p.workers = nil
|
||||
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.stopHeartbeat == nil {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
p.stopHeartbeat()
|
||||
p.stopHeartbeat = nil
|
||||
p.Release()
|
||||
|
||||
endTime := time.Now().Add(timeout)
|
||||
for time.Now().Before(endTime) {
|
||||
if p.Running() == 0 && (p.options.DisablePurge || atomic.LoadInt32(&p.heartbeatDone) == 1) {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool.
|
||||
func (p *PoolWithFunc) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.heartbeatDone, 0)
|
||||
var ctx context.Context
|
||||
ctx, p.stopHeartbeat = context.WithCancel(context.Background())
|
||||
if !p.options.DisablePurge {
|
||||
go p.purgePeriodically(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
func (p *PoolWithFunc) addRunning(delta 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 *goWorkerWithFunc) {
|
||||
spawnWorker := func() {
|
||||
w = p.workerCache.Get().(*goWorkerWithFunc)
|
||||
w.run()
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers) - 1
|
||||
if n >= 0 { // first try to fetch the worker from the queue
|
||||
w = idleWorkers[n]
|
||||
idleWorkers[n] = nil
|
||||
p.workers = idleWorkers[:n]
|
||||
p.lock.Unlock()
|
||||
} else if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
// if the worker queue is empty and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
} else { // otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
if p.options.Nonblocking {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
retry:
|
||||
if p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var nw int
|
||||
if nw = p.Running(); nw == 0 { // awakened by the scavenger
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return
|
||||
}
|
||||
l := len(p.workers) - 1
|
||||
if l < 0 {
|
||||
if nw < p.Cap() {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return
|
||||
}
|
||||
goto retry
|
||||
}
|
||||
w = p.workers[l]
|
||||
p.workers[l] = nil
|
||||
p.workers = p.workers[:l]
|
||||
p.lock.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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.recycleTime = time.Now()
|
||||
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
|
||||
}
|
||||
|
||||
p.workers = append(p.workers, worker)
|
||||
|
||||
// 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
|
||||
}
|
||||
41
worker.go
41
worker.go
@ -23,7 +23,7 @@
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -31,14 +31,16 @@ import (
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorker struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *Pool
|
||||
|
||||
// task is a job should be done.
|
||||
task chan func()
|
||||
|
||||
// recycleTime will be updated when putting a worker back into queue.
|
||||
recycleTime time.Time
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
@ -47,30 +49,47 @@ func (w *goWorker) run() {
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
w.pool.addRunning(-1)
|
||||
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 a panic: %v\n", p)
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
w.pool.options.Logger.Printf("worker exits from panic: %s\n", string(buf[:n]))
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorker) finish() {
|
||||
w.task <- nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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")
|
||||
)
|
||||
|
||||
type workerArray interface {
|
||||
len() int
|
||||
isEmpty() bool
|
||||
insert(worker *goWorker) error
|
||||
detach() *goWorker
|
||||
retrieveExpiry(duration time.Duration) []*goWorker
|
||||
reset()
|
||||
}
|
||||
|
||||
type arrayType int
|
||||
|
||||
const (
|
||||
stackType arrayType = 1 << iota
|
||||
loopQueueType
|
||||
)
|
||||
|
||||
func newWorkerArray(aType arrayType, size int) workerArray {
|
||||
switch aType {
|
||||
case stackType:
|
||||
return newWorkerStack(size)
|
||||
case loopQueueType:
|
||||
return newWorkerLoopQueue(size)
|
||||
default:
|
||||
return newWorkerStack(size)
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -31,14 +31,16 @@ 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
|
||||
|
||||
// recycleTime will be updated when putting a worker back into queue.
|
||||
recycleTime time.Time
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
@ -47,30 +49,47 @@ func (w *goWorkerWithFunc) run() {
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
w.pool.addRunning(-1)
|
||||
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 with func exits from a panic: %v\n", p)
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
w.pool.options.Logger.Printf("worker with func exits from panic: %s\n", string(buf[:n]))
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) finish() {
|
||||
w.arg <- nil
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
|
||||
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,10 +1,32 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
type loopQueue struct {
|
||||
items []*goWorker
|
||||
expiry []*goWorker
|
||||
items []worker
|
||||
expiry []worker
|
||||
head int
|
||||
tail int
|
||||
size int
|
||||
@ -12,23 +34,23 @@ type loopQueue struct {
|
||||
}
|
||||
|
||||
func newWorkerLoopQueue(size int) *loopQueue {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &loopQueue{
|
||||
items: make([]*goWorker, size),
|
||||
items: make([]worker, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (wq *loopQueue) len() int {
|
||||
if wq.size == 0 {
|
||||
if wq.size == 0 || wq.isEmpty() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if wq.head == wq.tail {
|
||||
if wq.isFull {
|
||||
if wq.head == wq.tail && wq.isFull {
|
||||
return wq.size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if wq.tail > wq.head {
|
||||
return wq.tail - wq.head
|
||||
@ -41,20 +63,13 @@ func (wq *loopQueue) isEmpty() bool {
|
||||
return wq.head == wq.tail && !wq.isFull
|
||||
}
|
||||
|
||||
func (wq *loopQueue) insert(worker *goWorker) error {
|
||||
if wq.size == 0 {
|
||||
return errQueueIsReleased
|
||||
}
|
||||
|
||||
func (wq *loopQueue) insert(w worker) error {
|
||||
if wq.isFull {
|
||||
return errQueueIsFull
|
||||
}
|
||||
wq.items[wq.tail] = worker
|
||||
wq.tail++
|
||||
wq.items[wq.tail] = w
|
||||
wq.tail = (wq.tail + 1) % wq.size
|
||||
|
||||
if wq.tail == wq.size {
|
||||
wq.tail = 0
|
||||
}
|
||||
if wq.tail == wq.head {
|
||||
wq.isFull = true
|
||||
}
|
||||
@ -62,23 +77,21 @@ func (wq *loopQueue) insert(worker *goWorker) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wq *loopQueue) detach() *goWorker {
|
||||
func (wq *loopQueue) detach() worker {
|
||||
if wq.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := wq.items[wq.head]
|
||||
wq.items[wq.head] = nil
|
||||
wq.head++
|
||||
if wq.head == wq.size {
|
||||
wq.head = 0
|
||||
}
|
||||
wq.head = (wq.head + 1) % wq.size
|
||||
|
||||
wq.isFull = false
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (wq *loopQueue) retrieveExpiry(duration time.Duration) []*goWorker {
|
||||
func (wq *loopQueue) refresh(duration time.Duration) []worker {
|
||||
expiryTime := time.Now().Add(-duration)
|
||||
index := wq.binarySearch(expiryTime)
|
||||
if index == -1 {
|
||||
@ -115,7 +128,7 @@ func (wq *loopQueue) binarySearch(expiryTime time.Time) int {
|
||||
nlen = len(wq.items)
|
||||
|
||||
// if no need to remove work, return -1
|
||||
if wq.isEmpty() || expiryTime.Before(wq.items[wq.head].recycleTime) {
|
||||
if wq.isEmpty() || expiryTime.Before(wq.items[wq.head].lastUsedTime()) {
|
||||
return -1
|
||||
}
|
||||
|
||||
@ -134,10 +147,10 @@ func (wq *loopQueue) binarySearch(expiryTime time.Time) int {
|
||||
basel = wq.head
|
||||
l := 0
|
||||
for l <= r {
|
||||
mid = l + ((r - l) >> 1)
|
||||
mid = l + ((r - l) >> 1) // avoid overflow when computing mid
|
||||
// calculate true mid position from mapped mid position
|
||||
tmid = (mid + basel + nlen) % nlen
|
||||
if expiryTime.Before(wq.items[tmid].recycleTime) {
|
||||
if expiryTime.Before(wq.items[tmid].lastUsedTime()) {
|
||||
r = mid - 1
|
||||
} else {
|
||||
l = mid + 1
|
||||
@ -152,13 +165,11 @@ func (wq *loopQueue) reset() {
|
||||
return
|
||||
}
|
||||
|
||||
Releasing:
|
||||
retry:
|
||||
if w := wq.detach(); w != nil {
|
||||
w.task <- nil
|
||||
goto Releasing
|
||||
w.finish()
|
||||
goto retry
|
||||
}
|
||||
wq.items = wq.items[:0]
|
||||
wq.size = 0
|
||||
wq.head = 0
|
||||
wq.tail = 0
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
@ -23,68 +45,72 @@ func TestLoopQueue(t *testing.T) {
|
||||
q := newWorkerLoopQueue(size)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err := q.insert(&goWorker{recycleTime: time.Now()})
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
err := q.insert(&goWorker{recycleTime: time.Now()})
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 10, q.len(), "Len error")
|
||||
require.EqualValues(t, 10, q.len(), "Len error")
|
||||
|
||||
err := q.insert(&goWorker{recycleTime: time.Now()})
|
||||
assert.Error(t, err, "Enqueue, error")
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
require.Error(t, err, "Enqueue, error")
|
||||
|
||||
q.retrieveExpiry(time.Second)
|
||||
assert.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
|
||||
q.refresh(time.Second)
|
||||
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")
|
||||
}
|
||||
|
||||
func TestRotatedArraySearch(t *testing.T) {
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
|
||||
// 1
|
||||
expiry1 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = 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{recycleTime: 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++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
expiry3 := time.Now()
|
||||
_ = q.insert(&goWorker{recycleTime: expiry3})
|
||||
_ = q.insert(&goWorker{lastUsed: expiry3})
|
||||
|
||||
var err error
|
||||
for err != errQueueIsFull {
|
||||
err = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
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++ {
|
||||
@ -92,78 +118,78 @@ func TestRotatedArraySearch(t *testing.T) {
|
||||
}
|
||||
|
||||
expiry4 := time.Now()
|
||||
_ = q.insert(&goWorker{recycleTime: expiry4})
|
||||
_ = q.insert(&goWorker{lastUsed: expiry4})
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
expiry5 := time.Now()
|
||||
_ = q.insert(&goWorker{recycleTime: expiry5})
|
||||
_ = q.insert(&goWorker{lastUsed: expiry5})
|
||||
|
||||
// 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{recycleTime: time.Now()})
|
||||
_ = 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].recycleTime), "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) {
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
expirew := make([]*goWorker, 0)
|
||||
expirew := make([]worker, 0)
|
||||
u, _ := time.ParseDuration("1s")
|
||||
|
||||
// test [ time+1s, time+1s, time+1s, time+1s, time+1s, time, time, time, time, time]
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
expirew = append(expirew, q.items[:size/2]...)
|
||||
time.Sleep(u)
|
||||
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
workers := q.retrieveExpiry(u)
|
||||
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)
|
||||
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
expirew = expirew[:0]
|
||||
expirew = append(expirew, q.items[size/2:]...)
|
||||
|
||||
workers2 := q.retrieveExpiry(u)
|
||||
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++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.detach()
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
time.Sleep(u)
|
||||
|
||||
@ -171,7 +197,7 @@ func TestRetrieveExpiry(t *testing.T) {
|
||||
expirew = append(expirew, q.items[0:3]...)
|
||||
expirew = append(expirew, q.items[size/2:]...)
|
||||
|
||||
workers3 := q.retrieveExpiry(u)
|
||||
workers3 := q.refresh(u)
|
||||
|
||||
assert.EqualValues(t, expirew, workers3, "expired workers aren't right")
|
||||
require.EqualValues(t, expirew, workers3, "expired workers aren't right")
|
||||
}
|
||||
|
||||
67
worker_queue.go
Normal file
67
worker_queue.go
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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())
|
||||
inputArg(any)
|
||||
}
|
||||
|
||||
type workerQueue interface {
|
||||
len() int
|
||||
isEmpty() bool
|
||||
insert(worker) error
|
||||
detach() worker
|
||||
refresh(duration time.Duration) []worker // clean up the stale workers and return them
|
||||
reset()
|
||||
}
|
||||
|
||||
type queueType int
|
||||
|
||||
const (
|
||||
queueTypeStack queueType = 1 << iota
|
||||
queueTypeLoopQueue
|
||||
)
|
||||
|
||||
func newWorkerQueue(qType queueType, size int) workerQueue {
|
||||
switch qType {
|
||||
case queueTypeStack:
|
||||
return newWorkerStack(size)
|
||||
case queueTypeLoopQueue:
|
||||
return newWorkerLoopQueue(size)
|
||||
default:
|
||||
return newWorkerStack(size)
|
||||
}
|
||||
}
|
||||
@ -1,70 +1,91 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
type workerStack struct {
|
||||
items []*goWorker
|
||||
expiry []*goWorker
|
||||
items []worker
|
||||
expiry []worker
|
||||
}
|
||||
|
||||
func newWorkerStack(size int) *workerStack {
|
||||
return &workerStack{
|
||||
items: make([]*goWorker, 0, size),
|
||||
items: make([]worker, 0, size),
|
||||
}
|
||||
}
|
||||
|
||||
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(worker *goWorker) error {
|
||||
wq.items = append(wq.items, worker)
|
||||
func (ws *workerStack) insert(w worker) error {
|
||||
ws.items = append(ws.items, w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wq *workerStack) detach() *goWorker {
|
||||
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) retrieveExpiry(duration time.Duration) []*goWorker {
|
||||
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 {
|
||||
var mid int
|
||||
func (ws *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
|
||||
for l <= r {
|
||||
mid = (l + r) / 2
|
||||
if expiryTime.Before(wq.items[mid].recycleTime) {
|
||||
mid := l + ((r - l) >> 1) // avoid overflow when computing mid
|
||||
if expiryTime.Before(ws.items[mid].lastUsedTime()) {
|
||||
r = mid - 1
|
||||
} else {
|
||||
l = mid + 1
|
||||
@ -73,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].task <- nil
|
||||
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,37 +1,57 @@
|
||||
//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) {
|
||||
q := newWorkerArray(arrayType(-1), 0)
|
||||
q := newWorkerQueue(queueType(-1), 0)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err := q.insert(&goWorker{recycleTime: time.Now()})
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 5, q.len(), "Len error")
|
||||
require.EqualValues(t, 5, q.len(), "Len error")
|
||||
|
||||
expired := time.Now()
|
||||
|
||||
err := q.insert(&goWorker{recycleTime: expired})
|
||||
err := q.insert(&goWorker{lastUsed: expired})
|
||||
if err != nil {
|
||||
t.Fatal("Enqueue error")
|
||||
}
|
||||
@ -39,51 +59,55 @@ func TestWorkerStack(t *testing.T) {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
err := q.insert(&goWorker{recycleTime: time.Now()})
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
t.Fatal("Enqueue error")
|
||||
}
|
||||
}
|
||||
assert.EqualValues(t, 12, q.len(), "Len error")
|
||||
q.retrieveExpiry(time.Second)
|
||||
assert.EqualValues(t, 6, q.len(), "Len error")
|
||||
require.EqualValues(t, 12, q.len(), "Len error")
|
||||
q.refresh(time.Second)
|
||||
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
|
||||
expiry1 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = 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{recycleTime: 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++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
expiry3 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{recycleTime: expiry3})
|
||||
_ = q.insert(&goWorker{lastUsed: expiry3})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = q.insert(&goWorker{recycleTime: time.Now()})
|
||||
_ = 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