├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.yaml
│ ├── feature-request.yaml
│ └── question.yaml
├── pull_request_template.md
├── release-drafter.yml
└── workflows
│ ├── codeql.yml
│ ├── pull-request.yml
│ ├── release-drafter.yml
│ ├── stale-bot.yml
│ └── test.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── README_ZH.md
├── ants.go
├── ants_benchmark_test.go
├── ants_test.go
├── example_test.go
├── go.mod
├── go.sum
├── multipool.go
├── multipool_func.go
├── multipool_func_generic.go
├── options.go
├── pkg
└── sync
│ ├── spinlock.go
│ ├── spinlock_test.go
│ └── sync.go
├── pool.go
├── pool_func.go
├── pool_func_generic.go
├── worker.go
├── worker_func.go
├── worker_func_generic.go
├── worker_loop_queue.go
├── worker_loop_queue_test.go
├── worker_queue.go
├── worker_stack.go
└── worker_stack_test.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [panjf2000]
4 | patreon: panjf2000
5 | open_collective: panjf2000
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Oops!..., it's a bug.
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | assignees:
6 | - panjf2000
7 | body:
8 | - type: markdown
9 | id: tips
10 | attributes:
11 | value: |
12 | ## Before you go any further
13 | - Please read [*How To Ask Questions The Smart Way*](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.
14 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
15 | - type: checkboxes
16 | id: checklist
17 | attributes:
18 | label: Actions I've taken before I'm here
19 | description: Make sure you have tried the following ways but still the problem has not been solved.
20 | options:
21 | - label: I've thoroughly read the documentations on this issue but still have no clue.
22 | required: true
23 | - label: I've searched the current list of Github issues but didn't find any duplicate issues that have been solved.
24 | required: true
25 | - label: I've searched the internet with this issue, but haven't found anything helpful.
26 | required: true
27 | validations:
28 | required: true
29 | - type: textarea
30 | id: bug-report
31 | attributes:
32 | label: What happened?
33 | description: Describe (and illustrate) the bug that you encountered precisely.
34 | placeholder: Please describe what happened and how it happened, the more details you provide, the faster the bug gets fixed.
35 | validations:
36 | required: true
37 | - type: dropdown
38 | id: major-version
39 | attributes:
40 | label: Major version of ants
41 | description: What major version of ants are you running?
42 | options:
43 | - v2
44 | - v1
45 | validations:
46 | required: true
47 | - type: input
48 | id: specific-version
49 | attributes:
50 | label: Specific version of ants
51 | description: What's the specific version of ants?
52 | placeholder: "For example: v2.6.0"
53 | validations:
54 | required: true
55 | - type: dropdown
56 | id: os
57 | attributes:
58 | label: Operating system
59 | multiple: true
60 | options:
61 | - Linux
62 | - macOS
63 | - Windows
64 | - BSD
65 | validations:
66 | required: true
67 | - type: input
68 | id: os-version
69 | attributes:
70 | label: OS version
71 | description: What's the specific version of OS?
72 | placeholder: "Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64"
73 | validations:
74 | required: true
75 | - type: input
76 | id: go-version
77 | attributes:
78 | label: Go version
79 | description: What's the specific version of Go?
80 | placeholder: "Run `go version` command to get the info, for example: go1.20.5 linux/amd64"
81 | validations:
82 | required: true
83 | - type: textarea
84 | id: logs
85 | attributes:
86 | label: Relevant log output
87 | description: Please copy and paste any relevant log output.
88 | render: shell
89 | validations:
90 | required: true
91 | - type: textarea
92 | id: code
93 | attributes:
94 | label: Reproducer
95 | description: Please provide the minimal code to reproduce the bug.
96 | render: go
97 | - type: textarea
98 | id: how-to-reproduce
99 | attributes:
100 | label: How to Reproduce
101 | description: Steps to reproduce the result.
102 | placeholder: Tell us step by step how we can replicate this bug and what we should see in the end.
103 | value: |
104 | Steps to reproduce the behavior:
105 | 1. Go to '....'
106 | 2. Click on '....'
107 | 3. Do '....'
108 | 4. See '....'
109 | validations:
110 | required: true
111 | - type: dropdown
112 | id: bug-in-latest-code
113 | attributes:
114 | label: Does this issue reproduce with the latest release?
115 | description: Is this bug still present in the latest version?
116 | options:
117 | - It can reproduce with the latest release
118 | - It gets fixed in the latest release
119 | - I haven't verified it with the latest release
120 | validations:
121 | required: true
122 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Propose an idea to make ants even better.
3 | title: "[Feature]: "
4 | labels: ["proposal", "enhancement"]
5 | assignees:
6 | - panjf2000
7 | body:
8 | - type: markdown
9 | id: tips
10 | attributes:
11 | value: |
12 | ## Before you go any further
13 | - Please read [*How To Ask Questions The Smart Way*](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.
14 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
15 | - type: textarea
16 | id: feature-request
17 | attributes:
18 | label: Description of new feature
19 | description: Make a concise but clear description about this new feature.
20 | placeholder: Describe this new feature with critical details.
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: feature-scenario
25 | attributes:
26 | label: Scenarios for new feature
27 | description: Explain why you need this feature and what scenarios can benefit from it.
28 | placeholder: Please try to convince us that this new feature makes sense, also it will improve ants.
29 | validations:
30 | required: true
31 | - type: dropdown
32 | id: breaking-changes
33 | attributes:
34 | label: Breaking changes or not?
35 | description: Is this new feature going to break the backward compatibility of the existing APIs?
36 | options:
37 | - "Yes"
38 | - "No"
39 | validations:
40 | required: true
41 | - type: textarea
42 | id: code
43 | attributes:
44 | label: Code snippets (optional)
45 | description: Illustrate this new feature with source code, what it looks like in code.
46 | render: go
47 | - type: textarea
48 | id: feature-alternative
49 | attributes:
50 | label: Alternatives for new feature
51 | description: Alternatives in your mind in case that the feature can't be done for some reasons.
52 | placeholder: A concise but clear description of any alternative solutions or features you had in mind.
53 | value: None.
54 | - type: textarea
55 | id: additional-context
56 | attributes:
57 | label: Additional context (optional)
58 | description: Any additional context about this new feature we should know.
59 | placeholder: What else should we know before we start discussing this new feature?
60 | value: None.
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.yaml:
--------------------------------------------------------------------------------
1 | name: Question
2 | description: Ask questions about ants.
3 | title: "[Question]: "
4 | labels: ["question", "help wanted"]
5 | body:
6 | - type: markdown
7 | id: tips
8 | attributes:
9 | value: |
10 | ## Before you go any further
11 | - Please read [*How To Ask Questions The Smart Way*](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.
12 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
13 | - type: textarea
14 | id: question
15 | attributes:
16 | label: Questions with details
17 | description: What do you want to know?
18 | placeholder: Describe your question with critical details here.
19 | validations:
20 | required: true
21 | - type: textarea
22 | id: code
23 | attributes:
24 | label: Code snippets (optional)
25 | description: Illustrate your question with source code if needed.
26 | render: go
27 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ## 1. Are you opening this pull request for bug-fixs, optimizations or new feature?
10 |
11 |
12 |
13 | ## 2. Please describe how these code changes achieve your intention.
14 |
15 |
16 |
17 |
18 | ## 3. Please link to the relevant issues (if any).
19 |
20 |
21 |
22 |
23 | ## 4. Which documentation changes (if any) need to be made/updated because of this PR?
24 |
25 |
26 |
27 |
28 | ## 4. Checklist
29 |
30 | - [ ] I have squashed all insignificant commits.
31 | - [ ] I have commented my code for explaining package types, values, functions, and non-obvious lines.
32 | - [ ] I have written unit tests and verified that all tests passes (if needed).
33 | - [ ] I have documented feature info on the README (only when this PR is adding a new feature).
34 | - [ ] (optional) I am willing to help maintain this change if there are issues with it later.
35 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: Ants v$RESOLVED_VERSION
2 | tag-template: v$RESOLVED_VERSION
3 | categories:
4 | - title: 🧨 Breaking changes
5 | labels:
6 | - breaking changes
7 | - title: 🚀 Features
8 | labels:
9 | - proposal
10 | - new feature
11 | - title: 🛩 Enhancements
12 | labels:
13 | - enhancement
14 | - optimization
15 | - title: 🐛 Bugfixes
16 | labels:
17 | - bug
18 | - title: 📚 Documentation
19 | labels:
20 | - doc
21 | - docs
22 | - title: 🗃 Misc
23 | labels:
24 | - chores
25 | change-template: '- $TITLE (#$NUMBER)'
26 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
27 | version-resolver:
28 | major:
29 | labels:
30 | - major
31 | minor:
32 | labels:
33 | - minor
34 | - new feature
35 | - proposal
36 | patch:
37 | labels:
38 | - patch
39 | - bug
40 | - dependencies
41 | default: patch
42 | autolabeler:
43 | - label: bug
44 | title:
45 | - /fix/i
46 | - /bug/i
47 | - /resolve/i
48 | - label: docs
49 | files:
50 | - '*.md'
51 | title:
52 | - /doc/i
53 | - /README/i
54 | - label: enhancement
55 | title:
56 | - /opt:/i
57 | - /refactor/i
58 | - /optimize/i
59 | - /improve/i
60 | - /update/i
61 | - /remove/i
62 | - /delete/i
63 | - label: optimization
64 | title:
65 | - /opt:/i
66 | - /refactor/i
67 | - /optimize/i
68 | - /improve/i
69 | - /update/i
70 | - /remove/i
71 | - /delete/i
72 | - label: new feature
73 | title:
74 | - /feat:/i
75 | - /feature/i
76 | - /implement/i
77 | - /add/i
78 | - /minor/i
79 | - label: dependencies
80 | title:
81 | - /dependencies/i
82 | - /upgrade/i
83 | - /bump up/i
84 | - label: chores
85 | title:
86 | - /chore/i
87 | - /misc/i
88 | - /cleanup/i
89 | - /clean up/i
90 | - label: major
91 | title:
92 | - /major:/i
93 | - label: minor
94 | title:
95 | - /minor:/i
96 | - label: patch
97 | title:
98 | - /patch:/i
99 | template: |
100 | ## Changelogs
101 |
102 | $CHANGES
103 |
104 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
105 |
106 | Thanks to all these contributors: $CONTRIBUTORS for making this release possible.
107 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "Code scanning"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | paths-ignore:
9 | - '**.md'
10 | - '**.yml'
11 | - '**.yaml'
12 | - '!.github/workflows/codeql.yml'
13 | pull_request:
14 | branches:
15 | - master
16 | - dev
17 | paths-ignore:
18 | - '**.md'
19 | - '**.yml'
20 | - '**.yaml'
21 | - '!.github/workflows/codeql.yml'
22 | schedule:
23 | # ┌───────────── minute (0 - 59)
24 | # │ ┌───────────── hour (0 - 23)
25 | # │ │ ┌───────────── day of the month (1 - 31)
26 | # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
27 | # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
28 | # │ │ │ │ │
29 | # │ │ │ │ │
30 | # │ │ │ │ │
31 | # * * * * *
32 | - cron: '30 4 * * 6'
33 |
34 | jobs:
35 | CodeQL-Build:
36 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
37 | runs-on: ubuntu-latest
38 |
39 | permissions:
40 | # required for all workflows
41 | security-events: write
42 |
43 | steps:
44 | - name: Checkout repository
45 | uses: actions/checkout@v4
46 | with:
47 | fetch-depth: 2
48 |
49 | # Initializes the CodeQL tools for scanning.
50 | - name: Initialize CodeQL
51 | uses: github/codeql-action/init@v3
52 | with:
53 | languages: go
54 |
55 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
56 | # If this step fails, then you should remove it and run the build manually (see below).
57 | - name: Autobuild
58 | uses: github/codeql-action/autobuild@v3
59 |
60 | # ℹ️ Command-line programs to run using the OS shell.
61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
62 |
63 | # ✏️ If the Autobuild fails above, remove it and uncomment the following
64 | # three lines and modify them (or add more) to build your code if your
65 | # project uses a compiled language
66 |
67 | #- run: |
68 | # make bootstrap
69 | # make release
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v3
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Check pull request target
2 | on:
3 | pull_request:
4 | types:
5 | - opened
6 | - reopened
7 | - synchronize
8 | branches:
9 | - master
10 | jobs:
11 | check-branches:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check target branch
15 | run: |
16 | if [ ${{ github.head_ref }} != "dev" ]; then
17 | echo "Only pull requests from dev branch are only allowed to be merged into master branch."
18 | exit 1
19 | fi
20 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | # branches to consider in the event; optional, defaults to all
6 | branches:
7 | - master
8 | # pull_request event is required only for autolabeler
9 | pull_request:
10 | # Only following types are handled by the action, but one can default to all as well
11 | types: [opened, reopened, synchronize]
12 | # pull_request_target event is required for autolabeler to support PRs from forks
13 | # pull_request_target:
14 | # types: [opened, reopened, synchronize]
15 |
16 | permissions:
17 | contents: read
18 |
19 | jobs:
20 | update_release_draft:
21 | permissions:
22 | # write permission is required to create a github release
23 | contents: write
24 | # write permission is required for autolabeler
25 | # otherwise, read permission is required at least
26 | pull-requests: write
27 | runs-on: ubuntu-latest
28 | steps:
29 | # (Optional) GitHub Enterprise requires GHE_HOST variable set
30 | #- name: Set GHE_HOST
31 | # run: |
32 | # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV
33 |
34 | # Drafts your next Release notes as Pull Requests are merged into "master"
35 | - uses: release-drafter/release-drafter@v6
36 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
37 | # with:
38 | # config-name: my-config.yml
39 | # disable-autolabeler: true
40 | env:
41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/stale-bot.yml:
--------------------------------------------------------------------------------
1 | name: Monitor inactive issues and PRs
2 | on:
3 | schedule:
4 | - cron: '0 0 * * *'
5 | workflow_dispatch:
6 |
7 | jobs:
8 | stale-issues:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | actions: write
12 | issues: write
13 | pull-requests: write
14 | steps:
15 | - uses: actions/stale@v9
16 | with:
17 | operations-per-run: 50
18 | days-before-issue-stale: 30
19 | days-before-issue-close: 7
20 | stale-issue-label: 'stale'
21 | stale-issue-message: |
22 | This issue is marked as stale because it has been open for 30 days with no activity.
23 |
24 | You should take one of the following actions:
25 | - Manually close this issue if it is no longer relevant
26 | - Comment if you have more information to share
27 |
28 | This issue will be automatically closed in 7 days if no further activity occurs.
29 | close-issue-message: |
30 | This issue was closed because it has been inactive for 7 days since being marked as stale.
31 |
32 | 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.
33 | days-before-pr-stale: 21
34 | days-before-pr-close: 7
35 | stale-pr-label: 'stale'
36 | stale-pr-message: |
37 | This PR is marked as stale because it has been open for 21 days with no activity.
38 |
39 | You should take one of the following actions:
40 | - Manually close this PR if it is no longer relevant
41 | - Push new commits or comment if you have more information to share
42 |
43 | This PR will be automatically closed in 7 days if no further activity occurs.
44 | close-pr-message: |
45 | This PR was closed because it has been inactive for 7 days since being marked as stale.
46 |
47 | If you believe this is a false alarm, feel free to reopen this PR or create a new one.
48 | repo-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | paths-ignore:
9 | - '**.md'
10 | - '**.yml'
11 | - '**.yaml'
12 | - 'examples/*'
13 | - '!.github/workflows/test.yml'
14 | pull_request:
15 | branches:
16 | - master
17 | - dev
18 | paths-ignore:
19 | - '**.md'
20 | - '**.yml'
21 | - '**.yaml'
22 | - 'examples/*'
23 | - '!.github/workflows/test.yml'
24 |
25 | env:
26 | GO111MODULE: on
27 | GOPROXY: "https://proxy.golang.org"
28 |
29 | jobs:
30 | lint:
31 | strategy:
32 | matrix:
33 | os:
34 | - ubuntu-latest
35 | - macos-latest
36 | name: Run golangci-lint
37 | runs-on: ${{ matrix.os }}
38 | steps:
39 | - name: Checkout repository
40 | uses: actions/checkout@v4
41 |
42 | - name: Setup Go
43 | uses: actions/setup-go@v5
44 | with:
45 | go-version: '^1.20'
46 | cache: false
47 |
48 | - name: Setup and run golangci-lint
49 | uses: golangci/golangci-lint-action@v6
50 | with:
51 | version: v1.62.2
52 | args: --timeout 5m -v -E gofumpt -E gocritic -E misspell -E revive -E godot
53 | test:
54 | needs: lint
55 | strategy:
56 | fail-fast: false
57 | matrix:
58 | go: [1.18, 1.23]
59 | os: [ubuntu-latest, macos-latest, windows-latest]
60 | name: Go ${{ matrix.go }} @ ${{ matrix.os }}
61 | runs-on: ${{ matrix.os}}
62 | steps:
63 | - name: Checkout repository
64 | uses: actions/checkout@v4
65 | with:
66 | ref: ${{ github.ref }}
67 |
68 | - name: Setup Go
69 | uses: actions/setup-go@v5
70 | with:
71 | go-version: ${{ matrix.go }}
72 |
73 | - name: Print Go environment
74 | id: go-env
75 | run: |
76 | printf "Using go at: $(which go)\n"
77 | printf "Go version: $(go version)\n"
78 | printf "\n\nGo environment:\n\n"
79 | go env
80 | printf "\n\nSystem environment:\n\n"
81 | env
82 | # Calculate the short SHA1 hash of the git commit
83 | echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
84 | echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
85 |
86 | - name: Run unit tests and integrated tests
87 | run: go test -v -race -coverprofile="codecov.report" -covermode=atomic
88 |
89 | - name: Upload code coverage report to Codecov
90 | uses: codecov/codecov-action@v5
91 | with:
92 | files: ./codecov.report
93 | flags: unittests
94 | name: codecov-ants
95 | fail_ci_if_error: true
96 | verbose: true
97 | env:
98 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
99 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | .idea
18 |
19 | .DS_Store
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at panjf2000@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## With issues:
4 | - Use the search tool before opening a new issue.
5 | - Please provide source code and commit sha if you found a bug.
6 | - Review existing issues and provide feedback or react to them.
7 |
8 | ## With pull requests:
9 | - Open your pull request against `dev`.
10 | - 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.
11 | - Your pull request should have no more than two commits, if not, you should squash them.
12 | - It should pass all tests in the available continuous integrations systems such as TravisCI.
13 | - You should add/modify tests to cover your proposed code changes.
14 | - If your pull request contains a new feature, please document it on the README.
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Andy Pan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | A goroutine pool for Go
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | English | [中文](README_ZH.md)
17 |
18 | ## 📖 Introduction
19 |
20 | Library `ants` implements a goroutine pool with fixed capacity, managing and recycling a massive number of goroutines, allowing developers to limit the number of goroutines in your concurrent programs.
21 |
22 | ## 🚀 Features:
23 |
24 | - Managing and recycling a massive number of goroutines automatically
25 | - Purging overdue goroutines periodically
26 | - Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool, etc.
27 | - Handle panic gracefully to prevent programs from crash
28 | - Efficient in memory usage and it may even achieve ***higher performance*** than unlimited goroutines in Go
29 | - Nonblocking mechanism
30 | - Preallocated memory (ring buffer, optional)
31 |
32 | ## 💡 How `ants` works
33 |
34 | ### Flow Diagram
35 |
36 |
37 |
38 |
39 |
40 | ### Activity Diagrams
41 |
42 | 
43 |
44 | 
45 |
46 | 
47 |
48 | 
49 |
50 | ## 🧰 How to install
51 |
52 | ### For `ants` v1
53 |
54 | ``` powershell
55 | go get -u github.com/panjf2000/ants
56 | ```
57 |
58 | ### For `ants` v2 (with GO111MODULE=on)
59 |
60 | ```powershell
61 | go get -u github.com/panjf2000/ants/v2
62 | ```
63 |
64 | ## 🛠 How to use
65 | Check out [the examples](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples) for basic usage.
66 |
67 | ### Functional options for pool
68 |
69 | `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.
70 |
71 | 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.
72 |
73 | ### Customize pool capacity
74 |
75 | `ants` supports customizing the capacity of the pool. You can call the `NewPool` method to instantiate a `Pool` with a given capacity, as follows:
76 |
77 | ``` go
78 | p, _ := ants.NewPool(10000)
79 | ```
80 |
81 | ### Submit tasks
82 | Tasks can be submitted by calling `ants.Submit`
83 | ```go
84 | ants.Submit(func(){})
85 | ```
86 |
87 | ### Tune pool capacity at runtime
88 | You can tune the capacity of `ants` pool at runtime with `ants.Tune`:
89 |
90 | ``` go
91 | pool.Tune(1000) // Tune its capacity to 1000
92 | pool.Tune(100000) // Tune its capacity to 100000
93 | ```
94 |
95 | Don't worry about the contention problems in this case, the method here is thread-safe (or should be called goroutine-safe).
96 |
97 | ### Pre-malloc goroutine queue in pool
98 |
99 | `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.
100 |
101 | ```go
102 | // ants will pre-malloc the whole capacity of pool when calling ants.NewPool.
103 | p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
104 | ```
105 |
106 | ### Release pool
107 |
108 | ```go
109 | pool.Release()
110 | ```
111 |
112 | or
113 |
114 | ```go
115 | pool.ReleaseTimeout(time.Second * 3)
116 | ```
117 |
118 | ### Reboot pool
119 |
120 | ```go
121 | // A pool that has been released can be still used after calling the Reboot().
122 | pool.Reboot()
123 | ```
124 |
125 | ## ⚙️ About sequence
126 |
127 | 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.
128 |
129 | ## 👏 Contributors
130 |
131 | Please read our [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `ants`!
132 |
133 |
134 |
135 |
136 |
137 | ## 📄 License
138 |
139 | The source code in `ants` is available under the [MIT License](/LICENSE).
140 |
141 | ## 📚 Relevant Articles
142 |
143 | - [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
144 | - [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
145 | - [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
146 | - [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)
147 |
148 | ## 🖥 Use cases
149 |
150 | ### business corporations
151 |
152 | Trusted by the following corporations/organizations.
153 |
154 |
264 |
265 | If you're also using `ants` in production, please help us enrich this list by opening a pull request.
266 |
267 | ### open-source software
268 |
269 | The open-source projects below do concurrent programming with the help of `ants`.
270 |
271 | - [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
272 | - [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications.
273 | - [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
274 | - [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.
275 | - [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.
276 | - [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
277 | - [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.
278 | - [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
279 | - [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
280 | - [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
281 | - [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
282 | - [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
283 | - [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.
284 | - [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.
285 | - [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.
286 | - [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.
287 | - [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
288 | - [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
289 | - [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.
290 | - [trpc-go](https://github.com/trpc-group/trpc-go): A pluggable, high-performance RPC framework written in Golang.
291 | - [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.
292 |
293 | #### All use cases:
294 |
295 | - [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)
296 |
297 | - [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)
298 |
299 | If you have `ants` integrated into projects, feel free to open a pull request refreshing this list of use cases.
300 |
301 | ## 🔋 JetBrains OS licenses
302 |
303 | `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.
304 |
305 |
306 |
307 | ## 💰 Backers
308 |
309 | Support us with a monthly donation and help us continue our activities.
310 |
311 |
312 |
313 | ## 💎 Sponsors
314 |
315 | Become a bronze sponsor with a monthly donation of $10 and get your logo on our README on GitHub.
316 |
317 |
318 |
319 | ## ☕️ Buy me a coffee
320 |
321 | > 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.
322 |
323 |
324 |
325 |
326 |
327 | ## 🔋 Sponsorship
328 |
329 |
330 |
331 |
332 |
333 |
334 |
--------------------------------------------------------------------------------
/README_ZH.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Go 语言的 goroutine 池
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | [英文](README.md) | 中文
17 |
18 | ## 📖 简介
19 |
20 | `ants` 是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。
21 |
22 | ## 🚀 功能:
23 |
24 | - 自动调度海量的 goroutines,复用 goroutines
25 | - 定期清理过期的 goroutines,进一步节省资源
26 | - 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等
27 | - 优雅处理 panic,防止程序崩溃
28 | - 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能***
29 | - 非阻塞机制
30 | - 预分配内存 (环形队列,可选)
31 |
32 | ## 💡 `ants` 是如何运行的
33 |
34 | ### 流程图
35 |
36 |
37 |
38 |
39 |
40 | ### 动态图
41 |
42 | 
43 |
44 | 
45 |
46 | 
47 |
48 | 
49 |
50 | ## 🧰 安装
51 |
52 | ### 使用 `ants` v1 版本:
53 |
54 | ``` powershell
55 | go get -u github.com/panjf2000/ants
56 | ```
57 |
58 | ### 使用 `ants` v2 版本 (开启 GO111MODULE=on):
59 |
60 | ```powershell
61 | go get -u github.com/panjf2000/ants/v2
62 | ```
63 |
64 | ## 🛠 使用
65 | 基本的使用请查看[示例](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples).
66 |
67 | ### Pool 配置
68 |
69 | 通过在调用 `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` 之时使用各种 optional function,可以设置 `ants.Options` 中各个配置项的值,然后用它来定制化 goroutine pool。
70 |
71 | 更多细节请查看 [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) 和 [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option)
72 |
73 |
74 | ### 自定义 pool 容量
75 | `ants` 支持实例化使用者自己的一个 Pool,指定具体的 pool 容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 `Pool`,如下:
76 |
77 | ``` go
78 | p, _ := ants.NewPool(10000)
79 | ```
80 |
81 | ### 任务提交
82 |
83 | 提交任务通过调用 `ants.Submit` 方法:
84 | ```go
85 | ants.Submit(func(){})
86 | ```
87 |
88 | ### 动态调整 goroutine 池容量
89 | 需要动态调整 pool 容量可以通过调用 `ants.Tune`:
90 |
91 | ``` go
92 | pool.Tune(1000) // Tune its capacity to 1000
93 | pool.Tune(100000) // Tune its capacity to 100000
94 | ```
95 |
96 | 该方法是线程安全的。
97 |
98 | ### 预先分配 goroutine 队列内存
99 |
100 | `ants` 支持预先为 pool 分配容量的内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。
101 |
102 | ```go
103 | // 提前分配的 pool 容量的内存空间
104 | p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
105 | ```
106 |
107 | ### 释放 Pool
108 |
109 | ```go
110 | pool.Release()
111 | ```
112 |
113 | 或者
114 |
115 | ```go
116 | pool.ReleaseTimeout(time.Second * 3)
117 | ```
118 |
119 | ### 重启 Pool
120 |
121 | ```go
122 | // 只要调用 Reboot() 方法,就可以重新激活一个之前已经被销毁掉的池,并且投入使用。
123 | pool.Reboot()
124 | ```
125 |
126 | ## ⚙️ 关于任务执行顺序
127 |
128 | `ants` 并不保证提交的任务被执行的顺序,执行的顺序也不是和提交的顺序保持一致,因为在 `ants` 是并发地处理所有提交的任务,提交的任务会被分派到正在并发运行的 workers 上去,因此那些任务将会被并发且无序地被执行。
129 |
130 | ## 👏 贡献者
131 |
132 | 请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `ants` 贡献过代码的开发者!
133 |
134 |
135 |
136 |
137 |
138 | ## 📄 证书
139 |
140 | `ants` 的源码允许用户在遵循 [MIT 开源证书](/LICENSE) 规则的前提下使用。
141 |
142 | ## 📚 相关文章
143 |
144 | - [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
145 | - [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
146 | - [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
147 | - [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)
148 |
149 | ## 🖥 用户案例
150 |
151 | ### 商业公司
152 |
153 | 以下公司/组织在生产环境上使用了 `ants`。
154 |
155 |
264 |
265 | 如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。
266 |
267 | ### 开源软件
268 |
269 | 这些开源项目借助 `ants` 进行并发编程。
270 |
271 | - [gnet](https://github.com/panjf2000/gnet): gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。
272 | - [milvus](https://github.com/milvus-io/milvus): 一个高度灵活、可靠且速度极快的云原生开源向量数据库。
273 | - [nps](https://github.com/ehang-io/nps): 一款轻量级、高性能、功能强大的内网穿透代理服务器。
274 | - [TDengine](https://github.com/taosdata/TDengine): TDengine 是一款开源、高性能、云原生的时序数据库 (Time-Series Database, TSDB)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。
275 | - [siyuan](https://github.com/siyuan-note/siyuan): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
276 | - [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
277 | - [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.
278 | - [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
279 | - [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
280 | - [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
281 | - [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
282 | - [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
283 | - [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne 是一款面向未来的超融合异构云原生数据库,通过超融合数据引擎支持事务/分析/流处理等混合工作负载,通过异构云原生架构支持跨机房协同/多地协同/云边协同。简化开发运维,消简数据碎片,打破数据的系统、位置和创新边界。
284 | - [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): 蓝鲸容器管理平台(Blueking Container Service)定位于打造云原生技术和业务实际应用场景之间的桥梁;聚焦于复杂应用场景的容器化部署技术方案的研发、整合和产品化;致力于为游戏等复杂应用提供一站式、低门槛的容器编排和服务治理服务。
285 | - [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.
286 | - [openGemini](https://github.com/openGemini/openGemini): openGemini 是华为云开源的一款云原生分布式时序数据库,可广泛应用于物联网、车联网、运维监控、工业互联网等业务场景,具备卓越的读写性能和高效的数据分析能力,采用类SQL查询语言,无第三方软件依赖、安装简单、部署灵活、运维便捷。
287 | - [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
288 | - [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
289 | - [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.
290 | - [trpc-go](https://github.com/trpc-group/trpc-go): 一个 Go 实现的可插拔的高性能 RPC 框架。
291 | - [motan-go](https://github.com/weibocom/motan-go): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。
292 |
293 | #### 所有案例:
294 |
295 | - [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)
296 |
297 | - [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)
298 |
299 | 如果你的项目也在使用 `ants`,欢迎给我提 Pull Request 来更新这份用户案例列表。
300 |
301 | ## 🔋 JetBrains 开源证书支持
302 |
303 | `ants` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
304 |
305 |
306 |
307 | ## 💰 支持
308 |
309 | 如果有意向,可以通过每个月定量的少许捐赠来支持这个项目。
310 |
311 |
312 |
313 | ## 💎 赞助
314 |
315 | 每月定量捐赠 10 刀即可成为本项目的赞助者,届时您的 logo 或者 link 可以展示在本项目的 README 上。
316 |
317 |
318 |
319 | ## ☕️ 打赏
320 |
321 | > 当您通过以下方式进行捐赠时,请务必留下姓名、GitHub 账号或其他社交媒体账号,以便我将其添加到捐赠者名单中,以表谢意。
322 |
323 |
324 |
325 |
326 |
327 | ## 🔋 赞助商
328 |
329 |
330 |
331 |
332 |
333 |
334 |
--------------------------------------------------------------------------------
/ants.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package ants implements an efficient and reliable goroutine pool for Go.
24 | //
25 | // With ants, Go applications are able to limit the number of active goroutines,
26 | // recycle goroutines efficiently, and reduce the memory footprint significantly.
27 | // Package ants is extremely useful in the scenarios where a massive number of
28 | // goroutines are created and destroyed frequently, such as highly-concurrent
29 | // batch processing systems, HTTP servers, services of asynchronous tasks, etc.
30 | package ants
31 |
32 | import (
33 | "context"
34 | "errors"
35 | "log"
36 | "math"
37 | "os"
38 | "runtime"
39 | "sync"
40 | "sync/atomic"
41 | "time"
42 |
43 | syncx "github.com/panjf2000/ants/v2/pkg/sync"
44 | )
45 |
46 | const (
47 | // DefaultAntsPoolSize is the default capacity for a default goroutine pool.
48 | DefaultAntsPoolSize = math.MaxInt32
49 |
50 | // DefaultCleanIntervalTime is the interval time to clean up goroutines.
51 | DefaultCleanIntervalTime = time.Second
52 | )
53 |
54 | const (
55 | // OPENED represents that the pool is opened.
56 | OPENED = iota
57 |
58 | // CLOSED represents that the pool is closed.
59 | CLOSED
60 | )
61 |
62 | var (
63 | // ErrLackPoolFunc will be returned when invokers don't provide function for pool.
64 | ErrLackPoolFunc = errors.New("must provide function for pool")
65 |
66 | // ErrInvalidPoolExpiry will be returned when setting a negative number as the periodic duration to purge goroutines.
67 | ErrInvalidPoolExpiry = errors.New("invalid expiry for pool")
68 |
69 | // ErrPoolClosed will be returned when submitting task to a closed pool.
70 | ErrPoolClosed = errors.New("this pool has been closed")
71 |
72 | // ErrPoolOverload will be returned when the pool is full and no workers available.
73 | ErrPoolOverload = errors.New("too many goroutines blocked on submit or Nonblocking is set")
74 |
75 | // ErrInvalidPreAllocSize will be returned when trying to set up a negative capacity under PreAlloc mode.
76 | ErrInvalidPreAllocSize = errors.New("can not set up a negative capacity under PreAlloc mode")
77 |
78 | // ErrTimeout will be returned after the operations timed out.
79 | ErrTimeout = errors.New("operation timed out")
80 |
81 | // ErrInvalidPoolIndex will be returned when trying to retrieve a pool with an invalid index.
82 | ErrInvalidPoolIndex = errors.New("invalid pool index")
83 |
84 | // ErrInvalidLoadBalancingStrategy will be returned when trying to create a MultiPool with an invalid load-balancing strategy.
85 | ErrInvalidLoadBalancingStrategy = errors.New("invalid load-balancing strategy")
86 |
87 | // ErrInvalidMultiPoolSize will be returned when trying to create a MultiPool with an invalid size.
88 | ErrInvalidMultiPoolSize = errors.New("invalid size for multiple pool")
89 |
90 | // workerChanCap determines whether the channel of a worker should be a buffered channel
91 | // to get the best performance. Inspired by fasthttp at
92 | // https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139
93 | workerChanCap = func() int {
94 | // Use blocking channel if GOMAXPROCS=1.
95 | // This switches context from sender to receiver immediately,
96 | // which results in higher performance (under go1.5 at least).
97 | if runtime.GOMAXPROCS(0) == 1 {
98 | return 0
99 | }
100 |
101 | // Use non-blocking workerChan if GOMAXPROCS>1,
102 | // since otherwise the sender might be dragged down if the receiver is CPU-bound.
103 | return 1
104 | }()
105 |
106 | defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds))
107 |
108 | // Init an instance pool when importing ants.
109 | defaultAntsPool, _ = NewPool(DefaultAntsPoolSize)
110 | )
111 |
112 | // Submit submits a task to pool.
113 | func Submit(task func()) error {
114 | return defaultAntsPool.Submit(task)
115 | }
116 |
117 | // Running returns the number of the currently running goroutines.
118 | func Running() int {
119 | return defaultAntsPool.Running()
120 | }
121 |
122 | // Cap returns the capacity of this default pool.
123 | func Cap() int {
124 | return defaultAntsPool.Cap()
125 | }
126 |
127 | // Free returns the available goroutines to work.
128 | func Free() int {
129 | return defaultAntsPool.Free()
130 | }
131 |
132 | // Release Closes the default pool.
133 | func Release() {
134 | defaultAntsPool.Release()
135 | }
136 |
137 | // ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
138 | func ReleaseTimeout(timeout time.Duration) error {
139 | return defaultAntsPool.ReleaseTimeout(timeout)
140 | }
141 |
142 | // Reboot reboots the default pool.
143 | func Reboot() {
144 | defaultAntsPool.Reboot()
145 | }
146 |
147 | // Logger is used for logging formatted messages.
148 | type Logger interface {
149 | // Printf must have the same semantics as log.Printf.
150 | Printf(format string, args ...any)
151 | }
152 |
153 | // poolCommon contains all common fields for other sophisticated pools.
154 | type poolCommon struct {
155 | // capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to
156 | // avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool
157 | // which submits a new task to the same pool.
158 | capacity int32
159 |
160 | // running is the number of the currently running goroutines.
161 | running int32
162 |
163 | // lock for protecting the worker queue.
164 | lock sync.Locker
165 |
166 | // workers is a slice that store the available workers.
167 | workers workerQueue
168 |
169 | // state is used to notice the pool to closed itself.
170 | state int32
171 |
172 | // cond for waiting to get an idle worker.
173 | cond *sync.Cond
174 |
175 | // done is used to indicate that all workers are done.
176 | allDone chan struct{}
177 | // once is used to make sure the pool is closed just once.
178 | once *sync.Once
179 |
180 | // workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
181 | workerCache sync.Pool
182 |
183 | // waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock
184 | waiting int32
185 |
186 | purgeDone int32
187 | purgeCtx context.Context
188 | stopPurge context.CancelFunc
189 |
190 | ticktockDone int32
191 | ticktockCtx context.Context
192 | stopTicktock context.CancelFunc
193 |
194 | now atomic.Value
195 |
196 | options *Options
197 | }
198 |
199 | func newPool(size int, options ...Option) (*poolCommon, error) {
200 | if size <= 0 {
201 | size = -1
202 | }
203 |
204 | opts := loadOptions(options...)
205 |
206 | if !opts.DisablePurge {
207 | if expiry := opts.ExpiryDuration; expiry < 0 {
208 | return nil, ErrInvalidPoolExpiry
209 | } else if expiry == 0 {
210 | opts.ExpiryDuration = DefaultCleanIntervalTime
211 | }
212 | }
213 |
214 | if opts.Logger == nil {
215 | opts.Logger = defaultLogger
216 | }
217 |
218 | p := &poolCommon{
219 | capacity: int32(size),
220 | allDone: make(chan struct{}),
221 | lock: syncx.NewSpinLock(),
222 | once: &sync.Once{},
223 | options: opts,
224 | }
225 | if p.options.PreAlloc {
226 | if size == -1 {
227 | return nil, ErrInvalidPreAllocSize
228 | }
229 | p.workers = newWorkerQueue(queueTypeLoopQueue, size)
230 | } else {
231 | p.workers = newWorkerQueue(queueTypeStack, 0)
232 | }
233 |
234 | p.cond = sync.NewCond(p.lock)
235 |
236 | p.goPurge()
237 | p.goTicktock()
238 |
239 | return p, nil
240 | }
241 |
242 | // purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
243 | func (p *poolCommon) purgeStaleWorkers() {
244 | ticker := time.NewTicker(p.options.ExpiryDuration)
245 |
246 | defer func() {
247 | ticker.Stop()
248 | atomic.StoreInt32(&p.purgeDone, 1)
249 | }()
250 |
251 | purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
252 | for {
253 | select {
254 | case <-purgeCtx.Done():
255 | return
256 | case <-ticker.C:
257 | }
258 |
259 | if p.IsClosed() {
260 | break
261 | }
262 |
263 | var isDormant bool
264 | p.lock.Lock()
265 | staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
266 | n := p.Running()
267 | isDormant = n == 0 || n == len(staleWorkers)
268 | p.lock.Unlock()
269 |
270 | // Clean up the stale workers.
271 | for i := range staleWorkers {
272 | staleWorkers[i].finish()
273 | staleWorkers[i] = nil
274 | }
275 |
276 | // There might be a situation where all workers have been cleaned up (no worker is running),
277 | // while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
278 | if isDormant && p.Waiting() > 0 {
279 | p.cond.Broadcast()
280 | }
281 | }
282 | }
283 |
284 | const nowTimeUpdateInterval = 500 * time.Millisecond
285 |
286 | // ticktock is a goroutine that updates the current time in the pool regularly.
287 | func (p *poolCommon) ticktock() {
288 | ticker := time.NewTicker(nowTimeUpdateInterval)
289 | defer func() {
290 | ticker.Stop()
291 | atomic.StoreInt32(&p.ticktockDone, 1)
292 | }()
293 |
294 | ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
295 | for {
296 | select {
297 | case <-ticktockCtx.Done():
298 | return
299 | case <-ticker.C:
300 | }
301 |
302 | if p.IsClosed() {
303 | break
304 | }
305 |
306 | p.now.Store(time.Now())
307 | }
308 | }
309 |
310 | func (p *poolCommon) goPurge() {
311 | if p.options.DisablePurge {
312 | return
313 | }
314 |
315 | // Start a goroutine to clean up expired workers periodically.
316 | p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
317 | go p.purgeStaleWorkers()
318 | }
319 |
320 | func (p *poolCommon) goTicktock() {
321 | p.now.Store(time.Now())
322 | p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
323 | go p.ticktock()
324 | }
325 |
326 | func (p *poolCommon) nowTime() time.Time {
327 | return p.now.Load().(time.Time)
328 | }
329 |
330 | // Running returns the number of workers currently running.
331 | func (p *poolCommon) Running() int {
332 | return int(atomic.LoadInt32(&p.running))
333 | }
334 |
335 | // Free returns the number of available workers, -1 indicates this pool is unlimited.
336 | func (p *poolCommon) Free() int {
337 | c := p.Cap()
338 | if c < 0 {
339 | return -1
340 | }
341 | return c - p.Running()
342 | }
343 |
344 | // Waiting returns the number of tasks waiting to be executed.
345 | func (p *poolCommon) Waiting() int {
346 | return int(atomic.LoadInt32(&p.waiting))
347 | }
348 |
349 | // Cap returns the capacity of this pool.
350 | func (p *poolCommon) Cap() int {
351 | return int(atomic.LoadInt32(&p.capacity))
352 | }
353 |
354 | // Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
355 | func (p *poolCommon) Tune(size int) {
356 | capacity := p.Cap()
357 | if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
358 | return
359 | }
360 | atomic.StoreInt32(&p.capacity, int32(size))
361 | if size > capacity {
362 | if size-capacity == 1 {
363 | p.cond.Signal()
364 | return
365 | }
366 | p.cond.Broadcast()
367 | }
368 | }
369 |
370 | // IsClosed indicates whether the pool is closed.
371 | func (p *poolCommon) IsClosed() bool {
372 | return atomic.LoadInt32(&p.state) == CLOSED
373 | }
374 |
375 | // Release closes this pool and releases the worker queue.
376 | func (p *poolCommon) Release() {
377 | if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
378 | return
379 | }
380 |
381 | if p.stopPurge != nil {
382 | p.stopPurge()
383 | p.stopPurge = nil
384 | }
385 | if p.stopTicktock != nil {
386 | p.stopTicktock()
387 | p.stopTicktock = nil
388 | }
389 |
390 | p.lock.Lock()
391 | p.workers.reset()
392 | p.lock.Unlock()
393 |
394 | // There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
395 | // those callers blocking infinitely.
396 | p.cond.Broadcast()
397 | }
398 |
399 | // ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
400 | func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error {
401 | if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
402 | return ErrPoolClosed
403 | }
404 |
405 | p.Release()
406 |
407 | var purgeCh <-chan struct{}
408 | if !p.options.DisablePurge {
409 | purgeCh = p.purgeCtx.Done()
410 | } else {
411 | purgeCh = p.allDone
412 | }
413 |
414 | if p.Running() == 0 {
415 | p.once.Do(func() {
416 | close(p.allDone)
417 | })
418 | }
419 |
420 | timer := time.NewTimer(timeout)
421 | defer timer.Stop()
422 | for {
423 | select {
424 | case <-timer.C:
425 | return ErrTimeout
426 | case <-p.allDone:
427 | <-purgeCh
428 | <-p.ticktockCtx.Done()
429 | if p.Running() == 0 &&
430 | (p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
431 | atomic.LoadInt32(&p.ticktockDone) == 1 {
432 | return nil
433 | }
434 | }
435 | }
436 | }
437 |
438 | // Reboot reboots a closed pool, it does nothing if the pool is not closed.
439 | // If you intend to reboot a closed pool, use ReleaseTimeout() instead of
440 | // Release() to ensure that all workers are stopped and resource are released
441 | // before rebooting, otherwise you may run into data race.
442 | func (p *poolCommon) Reboot() {
443 | if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
444 | atomic.StoreInt32(&p.purgeDone, 0)
445 | p.goPurge()
446 | atomic.StoreInt32(&p.ticktockDone, 0)
447 | p.goTicktock()
448 | p.allDone = make(chan struct{})
449 | p.once = &sync.Once{}
450 | }
451 | }
452 |
453 | func (p *poolCommon) addRunning(delta int) int {
454 | return int(atomic.AddInt32(&p.running, int32(delta)))
455 | }
456 |
457 | func (p *poolCommon) addWaiting(delta int) {
458 | atomic.AddInt32(&p.waiting, int32(delta))
459 | }
460 |
461 | // retrieveWorker returns an available worker to run the tasks.
462 | func (p *poolCommon) retrieveWorker() (w worker, err error) {
463 | p.lock.Lock()
464 |
465 | retry:
466 | // First try to fetch the worker from the queue.
467 | if w = p.workers.detach(); w != nil {
468 | p.lock.Unlock()
469 | return
470 | }
471 |
472 | // If the worker queue is empty, and we don't run out of the pool capacity,
473 | // then just spawn a new worker goroutine.
474 | if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
475 | p.lock.Unlock()
476 | w = p.workerCache.Get().(worker)
477 | w.run()
478 | return
479 | }
480 |
481 | // Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
482 | if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
483 | p.lock.Unlock()
484 | return nil, ErrPoolOverload
485 | }
486 |
487 | // Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
488 | p.addWaiting(1)
489 | p.cond.Wait() // block and wait for an available worker
490 | p.addWaiting(-1)
491 |
492 | if p.IsClosed() {
493 | p.lock.Unlock()
494 | return nil, ErrPoolClosed
495 | }
496 |
497 | goto retry
498 | }
499 |
500 | // revertWorker puts a worker back into free pool, recycling the goroutines.
501 | func (p *poolCommon) revertWorker(worker worker) bool {
502 | if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
503 | p.cond.Broadcast()
504 | return false
505 | }
506 |
507 | worker.setLastUsedTime(p.nowTime())
508 |
509 | p.lock.Lock()
510 | // To avoid memory leaks, add a double check in the lock scope.
511 | // Issue: https://github.com/panjf2000/ants/issues/113
512 | if p.IsClosed() {
513 | p.lock.Unlock()
514 | return false
515 | }
516 | if err := p.workers.insert(worker); err != nil {
517 | p.lock.Unlock()
518 | return false
519 | }
520 | // Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
521 | p.cond.Signal()
522 | p.lock.Unlock()
523 |
524 | return true
525 | }
526 |
--------------------------------------------------------------------------------
/ants_benchmark_test.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants_test
24 |
25 | import (
26 | "runtime"
27 | "sync"
28 | "sync/atomic"
29 | "testing"
30 | "time"
31 |
32 | "golang.org/x/sync/errgroup"
33 |
34 | "github.com/panjf2000/ants/v2"
35 | )
36 |
37 | const (
38 | RunTimes = 1e6
39 | PoolCap = 5e4
40 | BenchParam = 10
41 | DefaultExpiredTime = 10 * time.Second
42 | )
43 |
44 | func demoFunc() {
45 | time.Sleep(time.Duration(BenchParam) * time.Millisecond)
46 | }
47 |
48 | func demoPoolFunc(args any) {
49 | n := args.(int)
50 | time.Sleep(time.Duration(n) * time.Millisecond)
51 | }
52 |
53 | func demoPoolFuncInt(n int) {
54 | time.Sleep(time.Duration(n) * time.Millisecond)
55 | }
56 |
57 | var stopLongRunningFunc int32
58 |
59 | func longRunningFunc() {
60 | for atomic.LoadInt32(&stopLongRunningFunc) == 0 {
61 | runtime.Gosched()
62 | }
63 | }
64 |
65 | func longRunningPoolFunc(arg any) {
66 | <-arg.(chan struct{})
67 | }
68 |
69 | func longRunningPoolFuncCh(ch chan struct{}) {
70 | <-ch
71 | }
72 |
73 | func BenchmarkGoroutines(b *testing.B) {
74 | var wg sync.WaitGroup
75 | for i := 0; i < b.N; i++ {
76 | wg.Add(RunTimes)
77 | for j := 0; j < RunTimes; j++ {
78 | go func() {
79 | demoFunc()
80 | wg.Done()
81 | }()
82 | }
83 | wg.Wait()
84 | }
85 | }
86 |
87 | func BenchmarkChannel(b *testing.B) {
88 | var wg sync.WaitGroup
89 | sema := make(chan struct{}, PoolCap)
90 |
91 | b.ResetTimer()
92 | for i := 0; i < b.N; i++ {
93 | wg.Add(RunTimes)
94 | for j := 0; j < RunTimes; j++ {
95 | sema <- struct{}{}
96 | go func() {
97 | demoFunc()
98 | <-sema
99 | wg.Done()
100 | }()
101 | }
102 | wg.Wait()
103 | }
104 | }
105 |
106 | func BenchmarkErrGroup(b *testing.B) {
107 | var wg sync.WaitGroup
108 | var pool errgroup.Group
109 | pool.SetLimit(PoolCap)
110 |
111 | b.ResetTimer()
112 | for i := 0; i < b.N; i++ {
113 | wg.Add(RunTimes)
114 | for j := 0; j < RunTimes; j++ {
115 | pool.Go(func() error {
116 | demoFunc()
117 | wg.Done()
118 | return nil
119 | })
120 | }
121 | wg.Wait()
122 | }
123 | }
124 |
125 | func BenchmarkAntsPool(b *testing.B) {
126 | var wg sync.WaitGroup
127 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
128 | defer p.Release()
129 |
130 | b.ResetTimer()
131 | for i := 0; i < b.N; i++ {
132 | wg.Add(RunTimes)
133 | for j := 0; j < RunTimes; j++ {
134 | _ = p.Submit(func() {
135 | demoFunc()
136 | wg.Done()
137 | })
138 | }
139 | wg.Wait()
140 | }
141 | }
142 |
143 | func BenchmarkAntsMultiPool(b *testing.B) {
144 | var wg sync.WaitGroup
145 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
146 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
147 |
148 | b.ResetTimer()
149 | for i := 0; i < b.N; i++ {
150 | wg.Add(RunTimes)
151 | for j := 0; j < RunTimes; j++ {
152 | _ = p.Submit(func() {
153 | demoFunc()
154 | wg.Done()
155 | })
156 | }
157 | wg.Wait()
158 | }
159 | }
160 |
161 | func BenchmarkGoroutinesThroughput(b *testing.B) {
162 | for i := 0; i < b.N; i++ {
163 | for j := 0; j < RunTimes; j++ {
164 | go demoFunc()
165 | }
166 | }
167 | }
168 |
169 | func BenchmarkSemaphoreThroughput(b *testing.B) {
170 | sema := make(chan struct{}, PoolCap)
171 | for i := 0; i < b.N; i++ {
172 | for j := 0; j < RunTimes; j++ {
173 | sema <- struct{}{}
174 | go func() {
175 | demoFunc()
176 | <-sema
177 | }()
178 | }
179 | }
180 | }
181 |
182 | func BenchmarkAntsPoolThroughput(b *testing.B) {
183 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
184 | defer p.Release()
185 |
186 | b.ResetTimer()
187 | for i := 0; i < b.N; i++ {
188 | for j := 0; j < RunTimes; j++ {
189 | _ = p.Submit(demoFunc)
190 | }
191 | }
192 | }
193 |
194 | func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
195 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
196 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
197 |
198 | b.ResetTimer()
199 | for i := 0; i < b.N; i++ {
200 | for j := 0; j < RunTimes; j++ {
201 | _ = p.Submit(demoFunc)
202 | }
203 | }
204 | }
205 |
206 | func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
207 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
208 | defer p.Release()
209 |
210 | b.ResetTimer()
211 | b.RunParallel(func(pb *testing.PB) {
212 | for pb.Next() {
213 | _ = p.Submit(demoFunc)
214 | }
215 | })
216 | }
217 |
218 | func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {
219 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
220 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
221 |
222 | b.ResetTimer()
223 | b.RunParallel(func(pb *testing.PB) {
224 | for pb.Next() {
225 | _ = p.Submit(demoFunc)
226 | }
227 | })
228 | }
229 |
--------------------------------------------------------------------------------
/ants_test.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants_test
24 |
25 | import (
26 | "log"
27 | "os"
28 | "runtime"
29 | "sync"
30 | "sync/atomic"
31 | "testing"
32 | "time"
33 |
34 | "github.com/stretchr/testify/require"
35 |
36 | "github.com/panjf2000/ants/v2"
37 | )
38 |
39 | const (
40 | _ = 1 << (10 * iota)
41 | KiB // 1024
42 | MiB // 1048576
43 | )
44 |
45 | const (
46 | Param = 100
47 | AntsSize = 1000
48 | TestSize = 10000
49 | n = 100000
50 | )
51 |
52 | var curMem uint64
53 |
54 | // TestAntsPoolWaitToGetWorker is used to test waiting to get worker.
55 | func TestAntsPoolWaitToGetWorker(t *testing.T) {
56 | var wg sync.WaitGroup
57 | p, _ := ants.NewPool(AntsSize)
58 | defer p.Release()
59 |
60 | for i := 0; i < n; i++ {
61 | wg.Add(1)
62 | _ = p.Submit(func() {
63 | demoPoolFunc(Param)
64 | wg.Done()
65 | })
66 | }
67 | wg.Wait()
68 | t.Logf("pool, running workers number:%d", p.Running())
69 | mem := runtime.MemStats{}
70 | runtime.ReadMemStats(&mem)
71 | curMem = mem.TotalAlloc/MiB - curMem
72 | t.Logf("memory usage:%d MB", curMem)
73 | }
74 |
75 | func TestAntsPoolWaitToGetWorkerPreMalloc(t *testing.T) {
76 | var wg sync.WaitGroup
77 | p, _ := ants.NewPool(AntsSize, ants.WithPreAlloc(true))
78 | defer p.Release()
79 |
80 | for i := 0; i < n; i++ {
81 | wg.Add(1)
82 | _ = p.Submit(func() {
83 | demoPoolFunc(Param)
84 | wg.Done()
85 | })
86 | }
87 | wg.Wait()
88 | t.Logf("pool, running workers number:%d", p.Running())
89 | mem := runtime.MemStats{}
90 | runtime.ReadMemStats(&mem)
91 | curMem = mem.TotalAlloc/MiB - curMem
92 | t.Logf("memory usage:%d MB", curMem)
93 | }
94 |
95 | // TestAntsPoolWithFuncWaitToGetWorker is used to test waiting to get worker.
96 | func TestAntsPoolWithFuncWaitToGetWorker(t *testing.T) {
97 | var wg sync.WaitGroup
98 | p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) {
99 | demoPoolFunc(i)
100 | wg.Done()
101 | })
102 | defer p.Release()
103 |
104 | for i := 0; i < n; i++ {
105 | wg.Add(1)
106 | _ = p.Invoke(Param)
107 | }
108 | wg.Wait()
109 | t.Logf("pool with func, running workers number:%d", p.Running())
110 | mem := runtime.MemStats{}
111 | runtime.ReadMemStats(&mem)
112 | curMem = mem.TotalAlloc/MiB - curMem
113 | t.Logf("memory usage:%d MB", curMem)
114 | }
115 |
116 | // TestAntsPoolWithFuncGenericWaitToGetWorker is used to test waiting to get worker.
117 | func TestAntsPoolWithFuncGenericWaitToGetWorker(t *testing.T) {
118 | var wg sync.WaitGroup
119 | p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) {
120 | demoPoolFuncInt(i)
121 | wg.Done()
122 | })
123 | defer p.Release()
124 |
125 | for i := 0; i < n; i++ {
126 | wg.Add(1)
127 | _ = p.Invoke(Param)
128 | }
129 | wg.Wait()
130 | t.Logf("pool with func, running workers number:%d", p.Running())
131 | mem := runtime.MemStats{}
132 | runtime.ReadMemStats(&mem)
133 | curMem = mem.TotalAlloc/MiB - curMem
134 | t.Logf("memory usage:%d MB", curMem)
135 | }
136 |
137 | func TestAntsPoolWithFuncWaitToGetWorkerPreMalloc(t *testing.T) {
138 | var wg sync.WaitGroup
139 | p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) {
140 | demoPoolFunc(i)
141 | wg.Done()
142 | }, ants.WithPreAlloc(true))
143 | defer p.Release()
144 |
145 | for i := 0; i < n; i++ {
146 | wg.Add(1)
147 | _ = p.Invoke(Param)
148 | }
149 | wg.Wait()
150 | t.Logf("pool with func, running workers number:%d", p.Running())
151 | mem := runtime.MemStats{}
152 | runtime.ReadMemStats(&mem)
153 | curMem = mem.TotalAlloc/MiB - curMem
154 | t.Logf("memory usage:%d MB", curMem)
155 | }
156 |
157 | func TestAntsPoolWithFuncGenericWaitToGetWorkerPreMalloc(t *testing.T) {
158 | var wg sync.WaitGroup
159 | p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) {
160 | demoPoolFuncInt(i)
161 | wg.Done()
162 | }, ants.WithPreAlloc(true))
163 | defer p.Release()
164 |
165 | for i := 0; i < n; i++ {
166 | wg.Add(1)
167 | _ = p.Invoke(Param)
168 | }
169 | wg.Wait()
170 | t.Logf("pool with func, running workers number:%d", p.Running())
171 | mem := runtime.MemStats{}
172 | runtime.ReadMemStats(&mem)
173 | curMem = mem.TotalAlloc/MiB - curMem
174 | t.Logf("memory usage:%d MB", curMem)
175 | }
176 |
177 | // TestAntsPoolGetWorkerFromCache is used to test getting worker from sync.Pool.
178 | func TestAntsPoolGetWorkerFromCache(t *testing.T) {
179 | p, _ := ants.NewPool(TestSize)
180 | defer p.Release()
181 |
182 | for i := 0; i < AntsSize; i++ {
183 | _ = p.Submit(demoFunc)
184 | }
185 | time.Sleep(2 * ants.DefaultCleanIntervalTime)
186 | _ = p.Submit(demoFunc)
187 | t.Logf("pool, running workers number:%d", p.Running())
188 | mem := runtime.MemStats{}
189 | runtime.ReadMemStats(&mem)
190 | curMem = mem.TotalAlloc/MiB - curMem
191 | t.Logf("memory usage:%d MB", curMem)
192 | }
193 |
194 | // TestAntsPoolWithFuncGetWorkerFromCache is used to test getting worker from sync.Pool.
195 | func TestAntsPoolWithFuncGetWorkerFromCache(t *testing.T) {
196 | dur := 10
197 | p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc)
198 | defer p.Release()
199 |
200 | for i := 0; i < AntsSize; i++ {
201 | _ = p.Invoke(dur)
202 | }
203 | time.Sleep(2 * ants.DefaultCleanIntervalTime)
204 | _ = p.Invoke(dur)
205 | t.Logf("pool with func, running workers number:%d", p.Running())
206 | mem := runtime.MemStats{}
207 | runtime.ReadMemStats(&mem)
208 | curMem = mem.TotalAlloc/MiB - curMem
209 | t.Logf("memory usage:%d MB", curMem)
210 | }
211 |
212 | // TestAntsPoolWithFuncGenericGetWorkerFromCache is used to test getting worker from sync.Pool.
213 | func TestAntsPoolWithFuncGenericGetWorkerFromCache(t *testing.T) {
214 | dur := 10
215 | p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt)
216 | defer p.Release()
217 |
218 | for i := 0; i < AntsSize; i++ {
219 | _ = p.Invoke(dur)
220 | }
221 | time.Sleep(2 * ants.DefaultCleanIntervalTime)
222 | _ = p.Invoke(dur)
223 | t.Logf("pool with func, running workers number:%d", p.Running())
224 | mem := runtime.MemStats{}
225 | runtime.ReadMemStats(&mem)
226 | curMem = mem.TotalAlloc/MiB - curMem
227 | t.Logf("memory usage:%d MB", curMem)
228 | }
229 |
230 | func TestAntsPoolWithFuncGetWorkerFromCachePreMalloc(t *testing.T) {
231 | dur := 10
232 | p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true))
233 | defer p.Release()
234 |
235 | for i := 0; i < AntsSize; i++ {
236 | _ = p.Invoke(dur)
237 | }
238 | time.Sleep(2 * ants.DefaultCleanIntervalTime)
239 | _ = p.Invoke(dur)
240 | t.Logf("pool with func, running workers number:%d", p.Running())
241 | mem := runtime.MemStats{}
242 | runtime.ReadMemStats(&mem)
243 | curMem = mem.TotalAlloc/MiB - curMem
244 | t.Logf("memory usage:%d MB", curMem)
245 | }
246 |
247 | func TestAntsPoolWithFuncGenericGetWorkerFromCachePreMalloc(t *testing.T) {
248 | dur := 10
249 | p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true))
250 | defer p.Release()
251 |
252 | for i := 0; i < AntsSize; i++ {
253 | _ = p.Invoke(dur)
254 | }
255 | time.Sleep(2 * ants.DefaultCleanIntervalTime)
256 | _ = p.Invoke(dur)
257 | t.Logf("pool with func, running workers number:%d", p.Running())
258 | mem := runtime.MemStats{}
259 | runtime.ReadMemStats(&mem)
260 | curMem = mem.TotalAlloc/MiB - curMem
261 | t.Logf("memory usage:%d MB", curMem)
262 | }
263 |
264 | // Contrast between goroutines without a pool and goroutines with ants pool.
265 |
266 | func TestNoPool(t *testing.T) {
267 | var wg sync.WaitGroup
268 | for i := 0; i < n; i++ {
269 | wg.Add(1)
270 | go func() {
271 | demoFunc()
272 | wg.Done()
273 | }()
274 | }
275 |
276 | wg.Wait()
277 | mem := runtime.MemStats{}
278 | runtime.ReadMemStats(&mem)
279 | curMem = mem.TotalAlloc/MiB - curMem
280 | t.Logf("memory usage:%d MB", curMem)
281 | }
282 |
283 | func TestAntsPool(t *testing.T) {
284 | defer ants.Release()
285 | var wg sync.WaitGroup
286 | for i := 0; i < n; i++ {
287 | wg.Add(1)
288 | _ = ants.Submit(func() {
289 | demoFunc()
290 | wg.Done()
291 | })
292 | }
293 | wg.Wait()
294 |
295 | t.Logf("pool, capacity:%d", ants.Cap())
296 | t.Logf("pool, running workers number:%d", ants.Running())
297 | t.Logf("pool, free workers number:%d", ants.Free())
298 |
299 | mem := runtime.MemStats{}
300 | runtime.ReadMemStats(&mem)
301 | curMem = mem.TotalAlloc/MiB - curMem
302 | t.Logf("memory usage:%d MB", curMem)
303 | }
304 |
305 | func TestPanicHandler(t *testing.T) {
306 | var panicCounter int64
307 | var wg sync.WaitGroup
308 | p0, err := ants.NewPool(10, ants.WithPanicHandler(func(p any) {
309 | defer wg.Done()
310 | atomic.AddInt64(&panicCounter, 1)
311 | t.Logf("catch panic with PanicHandler: %v", p)
312 | }))
313 | require.NoErrorf(t, err, "create new pool failed: %v", err)
314 | defer p0.Release()
315 | wg.Add(1)
316 | _ = p0.Submit(func() {
317 | panic("Oops!")
318 | })
319 | wg.Wait()
320 | c := atomic.LoadInt64(&panicCounter)
321 | require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c)
322 | require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic")
323 |
324 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPanicHandler(func(_ any) {
325 | defer wg.Done()
326 | atomic.AddInt64(&panicCounter, 1)
327 | }))
328 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
329 | defer p1.Release()
330 | wg.Add(1)
331 | _ = p1.Invoke("Oops!")
332 | wg.Wait()
333 | c = atomic.LoadInt64(&panicCounter)
334 | require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c)
335 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")
336 |
337 | p2, err := ants.NewPoolWithFuncGeneric(10, func(s string) { panic(s) }, ants.WithPanicHandler(func(_ any) {
338 | defer wg.Done()
339 | atomic.AddInt64(&panicCounter, 1)
340 | }))
341 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
342 | defer p2.Release()
343 | wg.Add(1)
344 | _ = p2.Invoke("Oops!")
345 | wg.Wait()
346 | c = atomic.LoadInt64(&panicCounter)
347 | require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c)
348 | require.EqualValues(t, 0, p2.Running(), "pool should be empty after panic")
349 | }
350 |
351 | func TestPanicHandlerPreMalloc(t *testing.T) {
352 | var panicCounter int64
353 | var wg sync.WaitGroup
354 | p0, err := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithPanicHandler(func(p any) {
355 | defer wg.Done()
356 | atomic.AddInt64(&panicCounter, 1)
357 | t.Logf("catch panic with PanicHandler: %v", p)
358 | }))
359 | require.NoErrorf(t, err, "create new pool failed: %v", err)
360 | defer p0.Release()
361 | wg.Add(1)
362 | _ = p0.Submit(func() {
363 | panic("Oops!")
364 | })
365 | wg.Wait()
366 | c := atomic.LoadInt64(&panicCounter)
367 | require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c)
368 | require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic")
369 |
370 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) {
371 | defer wg.Done()
372 | atomic.AddInt64(&panicCounter, 1)
373 | }))
374 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
375 | defer p1.Release()
376 | wg.Add(1)
377 | _ = p1.Invoke("Oops!")
378 | wg.Wait()
379 | c = atomic.LoadInt64(&panicCounter)
380 | require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c)
381 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")
382 |
383 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) {
384 | defer wg.Done()
385 | atomic.AddInt64(&panicCounter, 1)
386 | }))
387 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
388 | defer p2.Release()
389 | wg.Add(1)
390 | _ = p2.Invoke("Oops!")
391 | wg.Wait()
392 | c = atomic.LoadInt64(&panicCounter)
393 | require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c)
394 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")
395 | }
396 |
397 | func TestPoolPanicWithoutHandler(t *testing.T) {
398 | p0, err := ants.NewPool(10)
399 | require.NoErrorf(t, err, "create new pool failed: %v", err)
400 | defer p0.Release()
401 | _ = p0.Submit(func() {
402 | panic("Oops!")
403 | })
404 |
405 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) })
406 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
407 | defer p1.Release()
408 | _ = p1.Invoke("Oops!")
409 |
410 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) })
411 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
412 | defer p2.Release()
413 | _ = p2.Invoke("Oops!")
414 | }
415 |
416 | func TestPoolPanicWithoutHandlerPreMalloc(t *testing.T) {
417 | p0, err := ants.NewPool(10, ants.WithPreAlloc(true))
418 | require.NoErrorf(t, err, "create new pool failed: %v", err)
419 | defer p0.Release()
420 | _ = p0.Submit(func() {
421 | panic("Oops!")
422 | })
423 |
424 | p1, err := ants.NewPoolWithFunc(10, func(p any) {
425 | panic(p)
426 | })
427 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
428 | defer p1.Release()
429 | _ = p1.Invoke("Oops!")
430 |
431 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p any) {
432 | panic(p)
433 | })
434 | require.NoErrorf(t, err, "create new pool with func failed: %v", err)
435 | defer p2.Release()
436 | _ = p2.Invoke("Oops!")
437 | }
438 |
439 | func TestPurgePool(t *testing.T) {
440 | size := 500
441 | ch := make(chan struct{})
442 |
443 | p, err := ants.NewPool(size)
444 | require.NoErrorf(t, err, "create TimingPool failed: %v", err)
445 | defer p.Release()
446 |
447 | for i := 0; i < size; i++ {
448 | j := i + 1
449 | _ = p.Submit(func() {
450 | <-ch
451 | d := j % 100
452 | time.Sleep(time.Duration(d) * time.Millisecond)
453 | })
454 | }
455 | require.EqualValuesf(t, size, p.Running(), "pool should be full, expected: %d, but got: %d", size, p.Running())
456 |
457 | close(ch)
458 | time.Sleep(5 * ants.DefaultCleanIntervalTime)
459 | require.EqualValuesf(t, 0, p.Running(), "pool should be empty after purge, but got %d", p.Running())
460 |
461 | ch = make(chan struct{})
462 | f := func(i any) {
463 | <-ch
464 | d := i.(int) % 100
465 | time.Sleep(time.Duration(d) * time.Millisecond)
466 | }
467 |
468 | p1, err := ants.NewPoolWithFunc(size, f)
469 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
470 | defer p1.Release()
471 |
472 | for i := 0; i < size; i++ {
473 | _ = p1.Invoke(i)
474 | }
475 | require.EqualValuesf(t, size, p1.Running(), "pool should be full, expected: %d, but got: %d", size, p1.Running())
476 |
477 | close(ch)
478 | time.Sleep(5 * ants.DefaultCleanIntervalTime)
479 | require.EqualValuesf(t, 0, p1.Running(), "pool should be empty after purge, but got %d", p1.Running())
480 |
481 | ch = make(chan struct{})
482 | f1 := func(i int) {
483 | <-ch
484 | d := i % 100
485 | time.Sleep(time.Duration(d) * time.Millisecond)
486 | }
487 |
488 | p2, err := ants.NewPoolWithFuncGeneric(size, f1)
489 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
490 | defer p2.Release()
491 |
492 | for i := 0; i < size; i++ {
493 | _ = p2.Invoke(i)
494 | }
495 | require.EqualValuesf(t, size, p2.Running(), "pool should be full, expected: %d, but got: %d", size, p2.Running())
496 |
497 | close(ch)
498 | time.Sleep(5 * ants.DefaultCleanIntervalTime)
499 | require.EqualValuesf(t, 0, p2.Running(), "pool should be empty after purge, but got %d", p2.Running())
500 | }
501 |
502 | func TestPurgePreMallocPool(t *testing.T) {
503 | p, err := ants.NewPool(10, ants.WithPreAlloc(true))
504 | require.NoErrorf(t, err, "create TimingPool failed: %v", err)
505 | defer p.Release()
506 | _ = p.Submit(demoFunc)
507 | time.Sleep(3 * ants.DefaultCleanIntervalTime)
508 | require.EqualValues(t, 0, p.Running(), "all p should be purged")
509 |
510 | p1, err := ants.NewPoolWithFunc(10, demoPoolFunc)
511 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
512 | defer p1.Release()
513 | _ = p1.Invoke(1)
514 | time.Sleep(3 * ants.DefaultCleanIntervalTime)
515 | require.EqualValues(t, 0, p1.Running(), "all p should be purged")
516 |
517 | p2, err := ants.NewPoolWithFuncGeneric(10, demoPoolFuncInt)
518 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
519 | defer p2.Release()
520 | _ = p2.Invoke(1)
521 | time.Sleep(3 * ants.DefaultCleanIntervalTime)
522 | require.EqualValues(t, 0, p2.Running(), "all p should be purged")
523 | }
524 |
525 | func TestNonblockingSubmit(t *testing.T) {
526 | poolSize := 10
527 | p, err := ants.NewPool(poolSize, ants.WithNonblocking(true))
528 | require.NoErrorf(t, err, "create TimingPool failed: %v", err)
529 | defer p.Release()
530 | for i := 0; i < poolSize-1; i++ {
531 | require.NoError(t, p.Submit(longRunningFunc), "nonblocking submit when pool is not full shouldn't return error")
532 | }
533 | ch := make(chan struct{})
534 | ch1 := make(chan struct{})
535 | f := func() {
536 | <-ch
537 | close(ch1)
538 | }
539 | // p is full now.
540 | require.NoError(t, p.Submit(f), "nonblocking submit when pool is not full shouldn't return error")
541 | require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload,
542 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload")
543 | // interrupt f to get an available worker
544 | close(ch)
545 | <-ch1
546 | require.NoError(t, p.Submit(demoFunc), "nonblocking submit when pool is not full shouldn't return error")
547 | }
548 |
549 | func TestMaxBlockingSubmit(t *testing.T) {
550 | poolSize := 10
551 | p, err := ants.NewPool(poolSize, ants.WithMaxBlockingTasks(1))
552 | require.NoErrorf(t, err, "create TimingPool failed: %v", err)
553 | defer p.Release()
554 | for i := 0; i < poolSize-1; i++ {
555 | require.NoError(t, p.Submit(longRunningFunc), "submit when pool is not full shouldn't return error")
556 | }
557 | ch := make(chan struct{})
558 | f := func() {
559 | <-ch
560 | }
561 | // p is full now.
562 | require.NoError(t, p.Submit(f), "submit when pool is not full shouldn't return error")
563 | var wg sync.WaitGroup
564 | wg.Add(1)
565 | errCh := make(chan error, 1)
566 | go func() {
567 | // should be blocked. blocking num == 1
568 | if err := p.Submit(demoFunc); err != nil {
569 | errCh <- err
570 | }
571 | wg.Done()
572 | }()
573 | time.Sleep(1 * time.Second)
574 | // already reached max blocking limit
575 | require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload,
576 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload")
577 | // interrupt f to make blocking submit successful.
578 | close(ch)
579 | wg.Wait()
580 | select {
581 | case <-errCh:
582 | t.Fatalf("blocking submit when pool is full should not return error")
583 | default:
584 | }
585 | }
586 |
587 | func TestNonblockingSubmitWithFunc(t *testing.T) {
588 | poolSize := 10
589 | ch := make(chan struct{})
590 | var wg sync.WaitGroup
591 | p, err := ants.NewPoolWithFunc(poolSize, func(i any) {
592 | longRunningPoolFunc(i)
593 | wg.Done()
594 | }, ants.WithNonblocking(true))
595 | require.NoError(t, err, "create TimingPool failed: %v", err)
596 | defer p.Release()
597 | wg.Add(poolSize)
598 | for i := 0; i < poolSize-1; i++ {
599 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
600 | }
601 | // p is full now.
602 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
603 | require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload,
604 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload")
605 | // interrupt f to get an available worker
606 | close(ch)
607 | wg.Wait()
608 | wg.Add(1)
609 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
610 | wg.Wait()
611 | }
612 |
613 | func TestNonblockingSubmitWithFuncGeneric(t *testing.T) {
614 | poolSize := 10
615 | var wg sync.WaitGroup
616 | p, err := ants.NewPoolWithFuncGeneric(poolSize, func(ch chan struct{}) {
617 | longRunningPoolFuncCh(ch)
618 | wg.Done()
619 | }, ants.WithNonblocking(true))
620 | require.NoError(t, err, "create TimingPool failed: %v", err)
621 | defer p.Release()
622 | ch := make(chan struct{})
623 | wg.Add(poolSize)
624 | for i := 0; i < poolSize-1; i++ {
625 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
626 | }
627 | // p is full now.
628 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
629 | require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload,
630 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload")
631 | // interrupt f to get an available worker
632 | close(ch)
633 | wg.Wait()
634 | wg.Add(1)
635 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
636 | wg.Wait()
637 | }
638 |
639 | func TestMaxBlockingSubmitWithFunc(t *testing.T) {
640 | ch := make(chan struct{})
641 | poolSize := 10
642 | p, err := ants.NewPoolWithFunc(poolSize, longRunningPoolFunc, ants.WithMaxBlockingTasks(1))
643 | require.NoError(t, err, "create TimingPool failed: %v", err)
644 | defer p.Release()
645 | for i := 0; i < poolSize-1; i++ {
646 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
647 | }
648 | // p is full now.
649 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
650 | var wg sync.WaitGroup
651 | wg.Add(1)
652 | errCh := make(chan error, 1)
653 | go func() {
654 | // should be blocked. blocking num == 1
655 | if err := p.Invoke(ch); err != nil {
656 | errCh <- err
657 | }
658 | wg.Done()
659 | }()
660 | time.Sleep(1 * time.Second)
661 | // already reached max blocking limit
662 | require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload,
663 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err)
664 | // interrupt one func to make blocking submit successful.
665 | close(ch)
666 | wg.Wait()
667 | select {
668 | case <-errCh:
669 | t.Fatalf("blocking submit when pool is full should not return error")
670 | default:
671 | }
672 | }
673 |
674 | func TestMaxBlockingSubmitWithFuncGeneric(t *testing.T) {
675 | poolSize := 10
676 | p, err := ants.NewPoolWithFuncGeneric(poolSize, longRunningPoolFuncCh, ants.WithMaxBlockingTasks(1))
677 | require.NoError(t, err, "create TimingPool failed: %v", err)
678 | defer p.Release()
679 | ch := make(chan struct{})
680 | for i := 0; i < poolSize-1; i++ {
681 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
682 | }
683 | // p is full now.
684 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
685 | var wg sync.WaitGroup
686 | wg.Add(1)
687 | errCh := make(chan error, 1)
688 | go func() {
689 | // should be blocked. blocking num == 1
690 | if err := p.Invoke(ch); err != nil {
691 | errCh <- err
692 | }
693 | wg.Done()
694 | }()
695 | time.Sleep(1 * time.Second)
696 | // already reached max blocking limit
697 | require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload,
698 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err)
699 | // interrupt one func to make blocking submit successful.
700 | close(ch)
701 | wg.Wait()
702 | select {
703 | case <-errCh:
704 | t.Fatalf("blocking submit when pool is full should not return error")
705 | default:
706 | }
707 | }
708 |
709 | func TestRebootDefaultPool(t *testing.T) {
710 | defer ants.Release()
711 | ants.Reboot() // should do nothing inside
712 | var wg sync.WaitGroup
713 | wg.Add(1)
714 | _ = ants.Submit(func() {
715 | demoFunc()
716 | wg.Done()
717 | })
718 | wg.Wait()
719 | require.NoError(t, ants.ReleaseTimeout(time.Second))
720 | require.ErrorIsf(t, ants.Submit(nil), ants.ErrPoolClosed, "pool should be closed")
721 | ants.Reboot()
722 | wg.Add(1)
723 | require.NoError(t, ants.Submit(func() { wg.Done() }), "pool should be rebooted")
724 | wg.Wait()
725 | }
726 |
727 | func TestRebootNewPool(t *testing.T) {
728 | var wg sync.WaitGroup
729 | p, err := ants.NewPool(10)
730 | require.NoErrorf(t, err, "create Pool failed: %v", err)
731 | defer p.Release()
732 | wg.Add(1)
733 | _ = p.Submit(func() {
734 | demoFunc()
735 | wg.Done()
736 | })
737 | wg.Wait()
738 | require.NoError(t, p.ReleaseTimeout(time.Second))
739 | require.ErrorIsf(t, p.Submit(nil), ants.ErrPoolClosed, "pool should be closed")
740 | p.Reboot()
741 | wg.Add(1)
742 | require.NoError(t, p.Submit(func() { wg.Done() }), "pool should be rebooted")
743 | wg.Wait()
744 |
745 | p1, err := ants.NewPoolWithFunc(10, func(i any) {
746 | demoPoolFunc(i)
747 | wg.Done()
748 | })
749 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
750 | defer p1.Release()
751 | wg.Add(1)
752 | _ = p1.Invoke(1)
753 | wg.Wait()
754 | require.NoError(t, p1.ReleaseTimeout(time.Second))
755 | require.ErrorIsf(t, p1.Invoke(nil), ants.ErrPoolClosed, "pool should be closed")
756 | p1.Reboot()
757 | wg.Add(1)
758 | require.NoError(t, p1.Invoke(1), "pool should be rebooted")
759 | wg.Wait()
760 |
761 | p2, err := ants.NewPoolWithFuncGeneric(10, func(i int) {
762 | demoPoolFuncInt(i)
763 | wg.Done()
764 | })
765 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
766 | defer p2.Release()
767 | wg.Add(1)
768 | _ = p2.Invoke(1)
769 | wg.Wait()
770 | require.NoError(t, p2.ReleaseTimeout(time.Second))
771 | require.ErrorIsf(t, p2.Invoke(1), ants.ErrPoolClosed, "pool should be closed")
772 | p2.Reboot()
773 | wg.Add(1)
774 | require.NoError(t, p2.Invoke(1), "pool should be rebooted")
775 | wg.Wait()
776 | }
777 |
778 | func TestInfinitePool(t *testing.T) {
779 | c := make(chan struct{})
780 | p, _ := ants.NewPool(-1)
781 | _ = p.Submit(func() {
782 | _ = p.Submit(func() {
783 | <-c
784 | })
785 | })
786 | c <- struct{}{}
787 | if n := p.Running(); n != 2 {
788 | t.Errorf("expect 2 workers running, but got %d", n)
789 | }
790 | if n := p.Free(); n != -1 {
791 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
792 | }
793 | p.Tune(10)
794 | if capacity := p.Cap(); capacity != -1 {
795 | t.Fatalf("expect capacity: -1 but got %d", capacity)
796 | }
797 | var err error
798 | _, err = ants.NewPool(-1, ants.WithPreAlloc(true))
799 | require.ErrorIs(t, err, ants.ErrInvalidPreAllocSize)
800 | }
801 |
802 | func testPoolWithDisablePurge(t *testing.T, p *ants.Pool, numWorker int, waitForPurge time.Duration) {
803 | sig := make(chan struct{})
804 | var wg1, wg2 sync.WaitGroup
805 | wg1.Add(numWorker)
806 | wg2.Add(numWorker)
807 | for i := 0; i < numWorker; i++ {
808 | _ = p.Submit(func() {
809 | wg1.Done()
810 | <-sig
811 | wg2.Done()
812 | })
813 | }
814 | wg1.Wait()
815 |
816 | runningCnt := p.Running()
817 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
818 | freeCnt := p.Free()
819 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)
820 |
821 | // Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism,
822 | // we should see that all workers are still running after the sleep.
823 | close(sig)
824 | wg2.Wait()
825 | time.Sleep(waitForPurge + waitForPurge/2)
826 |
827 | runningCnt = p.Running()
828 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
829 | freeCnt = p.Free()
830 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)
831 |
832 | err := p.ReleaseTimeout(waitForPurge + waitForPurge/2)
833 | require.NoErrorf(t, err, "release pool failed: %v", err)
834 |
835 | runningCnt = p.Running()
836 | require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt)
837 | freeCnt = p.Free()
838 | require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt)
839 | }
840 |
841 | func TestWithDisablePurgePool(t *testing.T) {
842 | numWorker := 10
843 | p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true))
844 | testPoolWithDisablePurge(t, p, numWorker, ants.DefaultCleanIntervalTime)
845 | }
846 |
847 | func TestWithDisablePurgeAndWithExpirationPool(t *testing.T) {
848 | numWorker := 10
849 | expiredDuration := time.Millisecond * 100
850 | p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration))
851 | testPoolWithDisablePurge(t, p, numWorker, expiredDuration)
852 | }
853 |
854 | func testPoolFuncWithDisablePurge(t *testing.T, p *ants.PoolWithFunc, numWorker int, wg1, wg2 *sync.WaitGroup, sig chan struct{}, waitForPurge time.Duration) {
855 | for i := 0; i < numWorker; i++ {
856 | _ = p.Invoke(i)
857 | }
858 | wg1.Wait()
859 |
860 | runningCnt := p.Running()
861 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
862 | freeCnt := p.Free()
863 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)
864 |
865 | // Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism,
866 | // we should see that all workers are still running after the sleep.
867 | close(sig)
868 | wg2.Wait()
869 | time.Sleep(waitForPurge + waitForPurge/2)
870 |
871 | runningCnt = p.Running()
872 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
873 | freeCnt = p.Free()
874 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)
875 |
876 | err := p.ReleaseTimeout(waitForPurge + waitForPurge/2)
877 | require.NoErrorf(t, err, "release pool failed: %v", err)
878 |
879 | runningCnt = p.Running()
880 | require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt)
881 | freeCnt = p.Free()
882 | require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt)
883 | }
884 |
885 | func TestWithDisablePurgePoolFunc(t *testing.T) {
886 | numWorker := 10
887 | sig := make(chan struct{})
888 | var wg1, wg2 sync.WaitGroup
889 | wg1.Add(numWorker)
890 | wg2.Add(numWorker)
891 | p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) {
892 | wg1.Done()
893 | <-sig
894 | wg2.Done()
895 | }, ants.WithDisablePurge(true))
896 | testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, ants.DefaultCleanIntervalTime)
897 | }
898 |
899 | func TestWithDisablePurgeAndWithExpirationPoolFunc(t *testing.T) {
900 | numWorker := 2
901 | sig := make(chan struct{})
902 | var wg1, wg2 sync.WaitGroup
903 | wg1.Add(numWorker)
904 | wg2.Add(numWorker)
905 | expiredDuration := time.Millisecond * 100
906 | p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) {
907 | wg1.Done()
908 | <-sig
909 | wg2.Done()
910 | }, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration))
911 | testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, expiredDuration)
912 | }
913 |
914 | func TestInfinitePoolWithFunc(t *testing.T) {
915 | c := make(chan struct{})
916 | p, err := ants.NewPoolWithFunc(-1, func(i any) {
917 | demoPoolFunc(i)
918 | <-c
919 | })
920 | require.NoErrorf(t, err, "create pool with func failed: %v", err)
921 | defer p.Release()
922 | _ = p.Invoke(10)
923 | _ = p.Invoke(10)
924 | c <- struct{}{}
925 | c <- struct{}{}
926 | if n := p.Running(); n != 2 {
927 | t.Errorf("expect 2 workers running, but got %d", n)
928 | }
929 | if n := p.Free(); n != -1 {
930 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
931 | }
932 | p.Tune(10)
933 | if capacity := p.Cap(); capacity != -1 {
934 | t.Fatalf("expect capacity: -1 but got %d", capacity)
935 | }
936 | _, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithPreAlloc(true))
937 | require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err)
938 | }
939 |
940 | func TestInfinitePoolWithFuncGeneric(t *testing.T) {
941 | c := make(chan struct{})
942 | p, err := ants.NewPoolWithFuncGeneric(-1, func(i int) {
943 | demoPoolFuncInt(i)
944 | <-c
945 | })
946 | require.NoErrorf(t, err, "create pool with func failed: %v", err)
947 | defer p.Release()
948 | _ = p.Invoke(10)
949 | _ = p.Invoke(10)
950 | c <- struct{}{}
951 | c <- struct{}{}
952 | if n := p.Running(); n != 2 {
953 | t.Errorf("expect 2 workers running, but got %d", n)
954 | }
955 | if n := p.Free(); n != -1 {
956 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
957 | }
958 | p.Tune(10)
959 | if capacity := p.Cap(); capacity != -1 {
960 | t.Fatalf("expect capacity: -1 but got %d", capacity)
961 | }
962 | _, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithPreAlloc(true))
963 | require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err)
964 | }
965 |
966 | func TestReleaseWhenRunningPool(t *testing.T) {
967 | var wg sync.WaitGroup
968 | p, err := ants.NewPool(1)
969 | require.NoErrorf(t, err, "create pool failed: %v", err)
970 | wg.Add(2)
971 | go func() {
972 | t.Log("start aaa")
973 | defer func() {
974 | wg.Done()
975 | t.Log("stop aaa")
976 | }()
977 | for i := 0; i < 30; i++ {
978 | j := i
979 | _ = p.Submit(func() {
980 | t.Log("do task", j)
981 | time.Sleep(1 * time.Second)
982 | })
983 | }
984 | }()
985 |
986 | go func() {
987 | t.Log("start bbb")
988 | defer func() {
989 | wg.Done()
990 | t.Log("stop bbb")
991 | }()
992 | for i := 100; i < 130; i++ {
993 | j := i
994 | _ = p.Submit(func() {
995 | t.Log("do task", j)
996 | time.Sleep(1 * time.Second)
997 | })
998 | }
999 | }()
1000 |
1001 | time.Sleep(3 * time.Second)
1002 | p.Release()
1003 | t.Log("wait for all goroutines to exit...")
1004 | wg.Wait()
1005 | }
1006 |
1007 | func TestReleaseWhenRunningPoolWithFunc(t *testing.T) {
1008 | var wg sync.WaitGroup
1009 | p, err := ants.NewPoolWithFunc(1, func(i any) {
1010 | t.Log("do task", i)
1011 | time.Sleep(1 * time.Second)
1012 | })
1013 | require.NoErrorf(t, err, "create pool with func failed: %v", err)
1014 |
1015 | wg.Add(2)
1016 | go func() {
1017 | t.Log("start aaa")
1018 | defer func() {
1019 | wg.Done()
1020 | t.Log("stop aaa")
1021 | }()
1022 | for i := 0; i < 30; i++ {
1023 | _ = p.Invoke(i)
1024 | }
1025 | }()
1026 |
1027 | go func() {
1028 | t.Log("start bbb")
1029 | defer func() {
1030 | wg.Done()
1031 | t.Log("stop bbb")
1032 | }()
1033 | for i := 100; i < 130; i++ {
1034 | _ = p.Invoke(i)
1035 | }
1036 | }()
1037 |
1038 | time.Sleep(3 * time.Second)
1039 | p.Release()
1040 | t.Log("wait for all goroutines to exit...")
1041 | wg.Wait()
1042 | }
1043 |
1044 | func TestReleaseWhenRunningPoolWithFuncGeneric(t *testing.T) {
1045 | var wg sync.WaitGroup
1046 | p, err := ants.NewPoolWithFuncGeneric(1, func(i int) {
1047 | t.Log("do task", i)
1048 | time.Sleep(1 * time.Second)
1049 | })
1050 | require.NoErrorf(t, err, "create pool with func failed: %v", err)
1051 | wg.Add(2)
1052 |
1053 | go func() {
1054 | t.Log("start aaa")
1055 | defer func() {
1056 | wg.Done()
1057 | t.Log("stop aaa")
1058 | }()
1059 | for i := 0; i < 30; i++ {
1060 | _ = p.Invoke(i)
1061 | }
1062 | }()
1063 |
1064 | go func() {
1065 | t.Log("start bbb")
1066 | defer func() {
1067 | wg.Done()
1068 | t.Log("stop bbb")
1069 | }()
1070 | for i := 100; i < 130; i++ {
1071 | _ = p.Invoke(i)
1072 | }
1073 | }()
1074 |
1075 | time.Sleep(3 * time.Second)
1076 | p.Release()
1077 | t.Log("wait for all goroutines to exit...")
1078 | wg.Wait()
1079 | }
1080 |
1081 | func TestRestCodeCoverage(t *testing.T) {
1082 | _, err := ants.NewPool(-1, ants.WithExpiryDuration(-1))
1083 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1084 | _, err = ants.NewPool(1, ants.WithExpiryDuration(-1))
1085 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1086 | _, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithExpiryDuration(-1))
1087 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1088 | _, err = ants.NewPoolWithFunc(1, demoPoolFunc, ants.WithExpiryDuration(-1))
1089 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1090 | _, err = ants.NewPoolWithFunc(1, nil, ants.WithExpiryDuration(-1))
1091 | require.ErrorIs(t, err, ants.ErrLackPoolFunc)
1092 | _, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithExpiryDuration(-1))
1093 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1094 | _, err = ants.NewPoolWithFuncGeneric(1, demoPoolFuncInt, ants.WithExpiryDuration(-1))
1095 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1096 | var fn func(i int)
1097 | _, err = ants.NewPoolWithFuncGeneric(1, fn, ants.WithExpiryDuration(-1))
1098 | require.ErrorIs(t, err, ants.ErrLackPoolFunc)
1099 |
1100 | options := ants.Options{}
1101 | options.ExpiryDuration = time.Duration(10) * time.Second
1102 | options.Nonblocking = true
1103 | options.PreAlloc = true
1104 | poolOpts, _ := ants.NewPool(1, ants.WithOptions(options))
1105 | t.Logf("Pool with options, capacity: %d", poolOpts.Cap())
1106 |
1107 | p0, _ := ants.NewPool(TestSize, ants.WithLogger(log.New(os.Stderr, "", log.LstdFlags)))
1108 | defer func() {
1109 | _ = p0.Submit(demoFunc)
1110 | }()
1111 | defer p0.Release()
1112 | for i := 0; i < n; i++ {
1113 | _ = p0.Submit(demoFunc)
1114 | }
1115 | t.Logf("pool, capacity:%d", p0.Cap())
1116 | t.Logf("pool, running workers number:%d", p0.Running())
1117 | t.Logf("pool, free workers number:%d", p0.Free())
1118 | p0.Tune(TestSize)
1119 | p0.Tune(TestSize / 10)
1120 | t.Logf("pool, after tuning capacity, capacity:%d, running:%d", p0.Cap(), p0.Running())
1121 |
1122 | p1, _ := ants.NewPool(TestSize, ants.WithPreAlloc(true))
1123 | defer func() {
1124 | _ = p1.Submit(demoFunc)
1125 | }()
1126 | defer p1.Release()
1127 | for i := 0; i < n; i++ {
1128 | _ = p1.Submit(demoFunc)
1129 | }
1130 | t.Logf("pre-malloc pool, capacity:%d", p1.Cap())
1131 | t.Logf("pre-malloc pool, running workers number:%d", p1.Running())
1132 | t.Logf("pre-malloc pool, free workers number:%d", p1.Free())
1133 | p1.Tune(TestSize)
1134 | p1.Tune(TestSize / 10)
1135 | t.Logf("pre-malloc pool, after tuning capacity, capacity:%d, running:%d", p1.Cap(), p1.Running())
1136 |
1137 | p2, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc)
1138 | defer func() {
1139 | _ = p2.Invoke(Param)
1140 | }()
1141 | defer p2.Release()
1142 | for i := 0; i < n; i++ {
1143 | _ = p2.Invoke(Param)
1144 | }
1145 | time.Sleep(ants.DefaultCleanIntervalTime)
1146 | t.Logf("pool with func, capacity:%d", p2.Cap())
1147 | t.Logf("pool with func, running workers number:%d", p2.Running())
1148 | t.Logf("pool with func, free workers number:%d", p2.Free())
1149 | p2.Tune(TestSize)
1150 | p2.Tune(TestSize / 10)
1151 | t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p2.Cap(), p2.Running())
1152 |
1153 | p3, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt)
1154 | defer func() {
1155 | _ = p3.Invoke(Param)
1156 | }()
1157 | defer p3.Release()
1158 | for i := 0; i < n; i++ {
1159 | _ = p3.Invoke(Param)
1160 | }
1161 | time.Sleep(ants.DefaultCleanIntervalTime)
1162 | t.Logf("pool with func, capacity:%d", p3.Cap())
1163 | t.Logf("pool with func, running workers number:%d", p3.Running())
1164 | t.Logf("pool with func, free workers number:%d", p3.Free())
1165 | p3.Tune(TestSize)
1166 | p3.Tune(TestSize / 10)
1167 | t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p3.Cap(), p3.Running())
1168 |
1169 | p4, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true))
1170 | defer func() {
1171 | _ = p4.Invoke(Param)
1172 | }()
1173 | defer p4.Release()
1174 | for i := 0; i < n; i++ {
1175 | _ = p4.Invoke(Param)
1176 | }
1177 | time.Sleep(ants.DefaultCleanIntervalTime)
1178 | t.Logf("pre-malloc pool with func, capacity:%d", p4.Cap())
1179 | t.Logf("pre-malloc pool with func, running workers number:%d", p4.Running())
1180 | t.Logf("pre-malloc pool with func, free workers number:%d", p4.Free())
1181 | p4.Tune(TestSize)
1182 | p4.Tune(TestSize / 10)
1183 | t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p4.Cap(),
1184 | p4.Running())
1185 |
1186 | p5, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true))
1187 | defer func() {
1188 | _ = p5.Invoke(Param)
1189 | }()
1190 | defer p5.Release()
1191 | for i := 0; i < n; i++ {
1192 | _ = p5.Invoke(Param)
1193 | }
1194 | time.Sleep(ants.DefaultCleanIntervalTime)
1195 | t.Logf("pre-malloc pool with func, capacity:%d", p5.Cap())
1196 | t.Logf("pre-malloc pool with func, running workers number:%d", p5.Running())
1197 | t.Logf("pre-malloc pool with func, free workers number:%d", p5.Free())
1198 | p5.Tune(TestSize)
1199 | p5.Tune(TestSize / 10)
1200 | t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p5.Cap(),
1201 | p5.Running())
1202 | }
1203 |
1204 | func TestPoolTuneScaleUp(t *testing.T) {
1205 | c := make(chan struct{})
1206 | // Test Pool
1207 | p, _ := ants.NewPool(2)
1208 | for i := 0; i < 2; i++ {
1209 | _ = p.Submit(func() {
1210 | <-c
1211 | })
1212 | }
1213 | n := p.Running()
1214 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", p.Running())
1215 | // test pool tune scale up one
1216 | p.Tune(3)
1217 | _ = p.Submit(func() {
1218 | <-c
1219 | })
1220 | n = p.Running()
1221 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
1222 | // test pool tune scale up multiple
1223 | var wg sync.WaitGroup
1224 | for i := 0; i < 5; i++ {
1225 | wg.Add(1)
1226 | go func() {
1227 | defer wg.Done()
1228 | _ = p.Submit(func() {
1229 | <-c
1230 | })
1231 | }()
1232 | }
1233 | p.Tune(8)
1234 | wg.Wait()
1235 | n = p.Running()
1236 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
1237 | for i := 0; i < 8; i++ {
1238 | c <- struct{}{}
1239 | }
1240 | p.Release()
1241 |
1242 | // Test PoolWithFunc
1243 | pf, _ := ants.NewPoolWithFunc(2, func(_ any) {
1244 | <-c
1245 | })
1246 | for i := 0; i < 2; i++ {
1247 | _ = pf.Invoke(1)
1248 | }
1249 | n = pf.Running()
1250 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n)
1251 | // test pool tune scale up one
1252 | pf.Tune(3)
1253 | _ = pf.Invoke(1)
1254 | n = pf.Running()
1255 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
1256 | // test pool tune scale up multiple
1257 | for i := 0; i < 5; i++ {
1258 | wg.Add(1)
1259 | go func() {
1260 | defer wg.Done()
1261 | _ = pf.Invoke(1)
1262 | }()
1263 | }
1264 | pf.Tune(8)
1265 | wg.Wait()
1266 | n = pf.Running()
1267 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
1268 | for i := 0; i < 8; i++ {
1269 | c <- struct{}{}
1270 | }
1271 | pf.Release()
1272 |
1273 | // Test PoolWithFuncGeneric
1274 | pfg, _ := ants.NewPoolWithFuncGeneric(2, func(_ int) {
1275 | <-c
1276 | })
1277 | for i := 0; i < 2; i++ {
1278 | _ = pfg.Invoke(1)
1279 | }
1280 | n = pfg.Running()
1281 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n)
1282 | // test pool tune scale up one
1283 | pfg.Tune(3)
1284 | _ = pfg.Invoke(1)
1285 | n = pfg.Running()
1286 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
1287 | // test pool tune scale up multiple
1288 | for i := 0; i < 5; i++ {
1289 | wg.Add(1)
1290 | go func() {
1291 | defer wg.Done()
1292 | _ = pfg.Invoke(1)
1293 | }()
1294 | }
1295 | pfg.Tune(8)
1296 | wg.Wait()
1297 | n = pfg.Running()
1298 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
1299 | for i := 0; i < 8; i++ {
1300 | c <- struct{}{}
1301 | }
1302 | close(c)
1303 | pfg.Release()
1304 | }
1305 |
1306 | func TestReleaseTimeout(t *testing.T) {
1307 | p, err := ants.NewPool(10)
1308 | require.NoError(t, err)
1309 | for i := 0; i < 5; i++ {
1310 | _ = p.Submit(func() {
1311 | time.Sleep(time.Second)
1312 | })
1313 | }
1314 | require.NotZero(t, p.Running())
1315 | err = p.ReleaseTimeout(2 * time.Second)
1316 | require.NoError(t, err)
1317 |
1318 | pf, err := ants.NewPoolWithFunc(10, func(i any) {
1319 | dur := i.(time.Duration)
1320 | time.Sleep(dur)
1321 | })
1322 | require.NoError(t, err)
1323 | for i := 0; i < 5; i++ {
1324 | _ = pf.Invoke(time.Second)
1325 | }
1326 | require.NotZero(t, pf.Running())
1327 | err = pf.ReleaseTimeout(2 * time.Second)
1328 | require.NoError(t, err)
1329 |
1330 | pfg, err := ants.NewPoolWithFuncGeneric(10, func(d time.Duration) {
1331 | time.Sleep(d)
1332 | })
1333 | require.NoError(t, err)
1334 | for i := 0; i < 5; i++ {
1335 | _ = pfg.Invoke(time.Second)
1336 | }
1337 | require.NotZero(t, pfg.Running())
1338 | err = pfg.ReleaseTimeout(2 * time.Second)
1339 | require.NoError(t, err)
1340 | }
1341 |
1342 | func TestDefaultPoolReleaseTimeout(t *testing.T) {
1343 | ants.Reboot() // should do nothing inside
1344 | for i := 0; i < 5; i++ {
1345 | _ = ants.Submit(func() {
1346 | time.Sleep(time.Second)
1347 | })
1348 | }
1349 | require.NotZero(t, ants.Running())
1350 | err := ants.ReleaseTimeout(2 * time.Second)
1351 | require.NoError(t, err)
1352 | }
1353 |
1354 | func TestMultiPool(t *testing.T) {
1355 | _, err := ants.NewMultiPool(-1, 10, 8)
1356 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
1357 | _, err = ants.NewMultiPool(10, -1, 8)
1358 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
1359 | _, err = ants.NewMultiPool(10, 10, ants.RoundRobin, ants.WithExpiryDuration(-1))
1360 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1361 |
1362 | mp, err := ants.NewMultiPool(10, 5, ants.RoundRobin)
1363 | testFn := func() {
1364 | for i := 0; i < 50; i++ {
1365 | err = mp.Submit(longRunningFunc)
1366 | require.NoError(t, err)
1367 | }
1368 | require.EqualValues(t, mp.Waiting(), 0)
1369 | _, err = mp.WaitingByIndex(-1)
1370 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1371 | _, err = mp.WaitingByIndex(11)
1372 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1373 | require.EqualValues(t, 50, mp.Running())
1374 | _, err = mp.RunningByIndex(-1)
1375 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1376 | _, err = mp.RunningByIndex(11)
1377 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1378 | require.EqualValues(t, 0, mp.Free())
1379 | _, err = mp.FreeByIndex(-1)
1380 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1381 | _, err = mp.FreeByIndex(11)
1382 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1383 | require.EqualValues(t, 50, mp.Cap())
1384 | require.False(t, mp.IsClosed())
1385 | for i := 0; i < 10; i++ {
1386 | n, _ := mp.WaitingByIndex(i)
1387 | require.EqualValues(t, 0, n)
1388 | n, _ = mp.RunningByIndex(i)
1389 | require.EqualValues(t, 5, n)
1390 | n, _ = mp.FreeByIndex(i)
1391 | require.EqualValues(t, 0, n)
1392 | }
1393 | atomic.StoreInt32(&stopLongRunningFunc, 1)
1394 | require.NoError(t, mp.ReleaseTimeout(3*time.Second))
1395 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
1396 | require.ErrorIs(t, mp.Submit(nil), ants.ErrPoolClosed)
1397 | require.Zero(t, mp.Running())
1398 | require.True(t, mp.IsClosed())
1399 | atomic.StoreInt32(&stopLongRunningFunc, 0)
1400 | }
1401 | testFn()
1402 |
1403 | mp.Reboot()
1404 | testFn()
1405 |
1406 | mp, err = ants.NewMultiPool(10, 5, ants.LeastTasks)
1407 | testFn()
1408 |
1409 | mp.Reboot()
1410 | testFn()
1411 |
1412 | mp.Tune(10)
1413 | }
1414 |
1415 | func TestMultiPoolWithFunc(t *testing.T) {
1416 | _, err := ants.NewMultiPoolWithFunc(-1, 10, longRunningPoolFunc, 8)
1417 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
1418 | _, err = ants.NewMultiPoolWithFunc(10, -1, longRunningPoolFunc, 8)
1419 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
1420 | _, err = ants.NewMultiPoolWithFunc(10, 10, longRunningPoolFunc, ants.RoundRobin, ants.WithExpiryDuration(-1))
1421 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1422 |
1423 | ch := make(chan struct{})
1424 | mp, err := ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.RoundRobin)
1425 | testFn := func() {
1426 | for i := 0; i < 50; i++ {
1427 | err = mp.Invoke(ch)
1428 | require.NoError(t, err)
1429 | }
1430 | require.EqualValues(t, mp.Waiting(), 0)
1431 | _, err = mp.WaitingByIndex(-1)
1432 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1433 | _, err = mp.WaitingByIndex(11)
1434 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1435 | require.EqualValues(t, 50, mp.Running())
1436 | _, err = mp.RunningByIndex(-1)
1437 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1438 | _, err = mp.RunningByIndex(11)
1439 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1440 | require.EqualValues(t, 0, mp.Free())
1441 | _, err = mp.FreeByIndex(-1)
1442 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1443 | _, err = mp.FreeByIndex(11)
1444 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1445 | require.EqualValues(t, 50, mp.Cap())
1446 | require.False(t, mp.IsClosed())
1447 | for i := 0; i < 10; i++ {
1448 | n, _ := mp.WaitingByIndex(i)
1449 | require.EqualValues(t, 0, n)
1450 | n, _ = mp.RunningByIndex(i)
1451 | require.EqualValues(t, 5, n)
1452 | n, _ = mp.FreeByIndex(i)
1453 | require.EqualValues(t, 0, n)
1454 | }
1455 | close(ch)
1456 | require.NoError(t, mp.ReleaseTimeout(3*time.Second))
1457 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
1458 | require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed)
1459 | require.Zero(t, mp.Running())
1460 | require.True(t, mp.IsClosed())
1461 | ch = make(chan struct{})
1462 | }
1463 | testFn()
1464 |
1465 | mp.Reboot()
1466 | testFn()
1467 |
1468 | mp, err = ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.LeastTasks)
1469 | testFn()
1470 |
1471 | mp.Reboot()
1472 | testFn()
1473 |
1474 | mp.Tune(10)
1475 | }
1476 |
1477 | func TestMultiPoolWithFuncGeneric(t *testing.T) {
1478 | _, err := ants.NewMultiPoolWithFuncGeneric(-1, 10, longRunningPoolFuncCh, 8)
1479 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
1480 | _, err = ants.NewMultiPoolWithFuncGeneric(10, -1, longRunningPoolFuncCh, 8)
1481 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
1482 | _, err = ants.NewMultiPoolWithFuncGeneric(10, 10, longRunningPoolFuncCh, ants.RoundRobin, ants.WithExpiryDuration(-1))
1483 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
1484 |
1485 | ch := make(chan struct{})
1486 | mp, err := ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.RoundRobin)
1487 | testFn := func() {
1488 | for i := 0; i < 50; i++ {
1489 | err = mp.Invoke(ch)
1490 | require.NoError(t, err)
1491 | }
1492 | require.EqualValues(t, mp.Waiting(), 0)
1493 | _, err = mp.WaitingByIndex(-1)
1494 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1495 | _, err = mp.WaitingByIndex(11)
1496 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1497 | require.EqualValues(t, 50, mp.Running())
1498 | _, err = mp.RunningByIndex(-1)
1499 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1500 | _, err = mp.RunningByIndex(11)
1501 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1502 | require.EqualValues(t, 0, mp.Free())
1503 | _, err = mp.FreeByIndex(-1)
1504 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1505 | _, err = mp.FreeByIndex(11)
1506 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
1507 | require.EqualValues(t, 50, mp.Cap())
1508 | require.False(t, mp.IsClosed())
1509 | for i := 0; i < 10; i++ {
1510 | n, _ := mp.WaitingByIndex(i)
1511 | require.EqualValues(t, 0, n)
1512 | n, _ = mp.RunningByIndex(i)
1513 | require.EqualValues(t, 5, n)
1514 | n, _ = mp.FreeByIndex(i)
1515 | require.EqualValues(t, 0, n)
1516 | }
1517 | close(ch)
1518 | require.NoError(t, mp.ReleaseTimeout(3*time.Second))
1519 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
1520 | require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed)
1521 | require.Zero(t, mp.Running())
1522 | require.True(t, mp.IsClosed())
1523 | ch = make(chan struct{})
1524 | }
1525 | testFn()
1526 |
1527 | mp.Reboot()
1528 | testFn()
1529 |
1530 | mp, err = ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.LeastTasks)
1531 | testFn()
1532 |
1533 | mp.Reboot()
1534 | testFn()
1535 |
1536 | mp.Tune(10)
1537 | }
1538 |
1539 | func TestRebootNewPoolCalc(t *testing.T) {
1540 | atomic.StoreInt32(&sum, 0)
1541 | runTimes := 1000
1542 | wg.Add(runTimes)
1543 |
1544 | pool, err := ants.NewPool(10)
1545 | require.NoError(t, err)
1546 | defer pool.Release()
1547 | // Use the default pool.
1548 | for i := 0; i < runTimes; i++ {
1549 | j := i
1550 | _ = pool.Submit(func() {
1551 | incSumInt(int32(j))
1552 | })
1553 | }
1554 | wg.Wait()
1555 | require.EqualValues(t, 499500, sum, "The result should be 499500")
1556 |
1557 | atomic.StoreInt32(&sum, 0)
1558 | wg.Add(runTimes)
1559 | err = pool.ReleaseTimeout(time.Second) // use both Release and ReleaseTimeout will occur panic
1560 | require.NoError(t, err)
1561 | pool.Reboot()
1562 |
1563 | for i := 0; i < runTimes; i++ {
1564 | j := i
1565 | _ = pool.Submit(func() {
1566 | incSumInt(int32(j))
1567 | })
1568 | }
1569 | wg.Wait()
1570 | require.EqualValues(t, 499500, sum, "The result should be 499500")
1571 | }
1572 |
1573 | func TestRebootNewPoolWithPreAllocCalc(t *testing.T) {
1574 | atomic.StoreInt32(&sum, 0)
1575 | runTimes := 1000
1576 | wg.Add(runTimes)
1577 |
1578 | pool, err := ants.NewPool(10, ants.WithPreAlloc(true))
1579 | require.NoError(t, err)
1580 | defer pool.Release()
1581 | // Use the default pool.
1582 | for i := 0; i < runTimes; i++ {
1583 | j := i
1584 | _ = pool.Submit(func() {
1585 | incSumInt(int32(j))
1586 | })
1587 | }
1588 | wg.Wait()
1589 | require.EqualValues(t, 499500, sum, "The result should be 499500")
1590 |
1591 | atomic.StoreInt32(&sum, 0)
1592 | err = pool.ReleaseTimeout(time.Second)
1593 | require.NoError(t, err)
1594 | pool.Reboot()
1595 |
1596 | wg.Add(runTimes)
1597 | for i := 0; i < runTimes; i++ {
1598 | j := i
1599 | _ = pool.Submit(func() {
1600 | incSumInt(int32(j))
1601 | })
1602 | }
1603 | wg.Wait()
1604 | require.EqualValues(t, 499500, sum, "The result should be 499500")
1605 | }
1606 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025. Andy Pan. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants_test
24 |
25 | import (
26 | "fmt"
27 | "sync"
28 | "sync/atomic"
29 | "time"
30 |
31 | "github.com/panjf2000/ants/v2"
32 | )
33 |
34 | var (
35 | sum int32
36 | wg sync.WaitGroup
37 | )
38 |
39 | func incSum(i any) {
40 | incSumInt(i.(int32))
41 | }
42 |
43 | func incSumInt(i int32) {
44 | atomic.AddInt32(&sum, i)
45 | wg.Done()
46 | }
47 |
48 | func ExamplePool() {
49 | ants.Reboot() // ensure the default pool is available
50 |
51 | atomic.StoreInt32(&sum, 0)
52 | runTimes := 1000
53 | wg.Add(runTimes)
54 | // Use the default pool.
55 | for i := 0; i < runTimes; i++ {
56 | j := i
57 | _ = ants.Submit(func() {
58 | incSumInt(int32(j))
59 | })
60 | }
61 | wg.Wait()
62 | fmt.Printf("The result is %d\n", sum)
63 |
64 | atomic.StoreInt32(&sum, 0)
65 | wg.Add(runTimes)
66 | // Use the new pool.
67 | pool, _ := ants.NewPool(10)
68 | defer pool.Release()
69 | for i := 0; i < runTimes; i++ {
70 | j := i
71 | _ = pool.Submit(func() {
72 | incSumInt(int32(j))
73 | })
74 | }
75 | wg.Wait()
76 | fmt.Printf("The result is %d\n", sum)
77 |
78 | // Output:
79 | // The result is 499500
80 | // The result is 499500
81 | }
82 |
83 | func ExamplePoolWithFunc() {
84 | atomic.StoreInt32(&sum, 0)
85 | runTimes := 1000
86 | wg.Add(runTimes)
87 |
88 | pool, _ := ants.NewPoolWithFunc(10, incSum)
89 | defer pool.Release()
90 |
91 | for i := 0; i < runTimes; i++ {
92 | _ = pool.Invoke(int32(i))
93 | }
94 | wg.Wait()
95 |
96 | fmt.Printf("The result is %d\n", sum)
97 |
98 | // Output: The result is 499500
99 | }
100 |
101 | func ExamplePoolWithFuncGeneric() {
102 | atomic.StoreInt32(&sum, 0)
103 | runTimes := 1000
104 | wg.Add(runTimes)
105 |
106 | pool, _ := ants.NewPoolWithFuncGeneric(10, incSumInt)
107 | defer pool.Release()
108 |
109 | for i := 0; i < runTimes; i++ {
110 | _ = pool.Invoke(int32(i))
111 | }
112 | wg.Wait()
113 |
114 | fmt.Printf("The result is %d\n", sum)
115 |
116 | // Output: The result is 499500
117 | }
118 |
119 | func ExampleMultiPool() {
120 | atomic.StoreInt32(&sum, 0)
121 | runTimes := 1000
122 | wg.Add(runTimes)
123 |
124 | mp, _ := ants.NewMultiPool(10, runTimes/10, ants.RoundRobin)
125 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
126 |
127 | for i := 0; i < runTimes; i++ {
128 | j := i
129 | _ = mp.Submit(func() {
130 | incSumInt(int32(j))
131 | })
132 | }
133 | wg.Wait()
134 |
135 | fmt.Printf("The result is %d\n", sum)
136 |
137 | // Output: The result is 499500
138 | }
139 |
140 | func ExampleMultiPoolWithFunc() {
141 | atomic.StoreInt32(&sum, 0)
142 | runTimes := 1000
143 | wg.Add(runTimes)
144 |
145 | mp, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, incSum, ants.RoundRobin)
146 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
147 |
148 | for i := 0; i < runTimes; i++ {
149 | _ = mp.Invoke(int32(i))
150 | }
151 | wg.Wait()
152 |
153 | fmt.Printf("The result is %d\n", sum)
154 |
155 | // Output: The result is 499500
156 | }
157 |
158 | func ExampleMultiPoolWithFuncGeneric() {
159 | atomic.StoreInt32(&sum, 0)
160 | runTimes := 1000
161 | wg.Add(runTimes)
162 |
163 | mp, _ := ants.NewMultiPoolWithFuncGeneric(10, runTimes/10, incSumInt, ants.RoundRobin)
164 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
165 |
166 | for i := 0; i < runTimes; i++ {
167 | _ = mp.Invoke(int32(i))
168 | }
169 | wg.Wait()
170 |
171 | fmt.Printf("The result is %d\n", sum)
172 |
173 | // Output: The result is 499500
174 | }
175 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/panjf2000/ants/v2
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/stretchr/testify v1.10.0
7 | golang.org/x/sync v0.11.0
8 | )
9 |
10 | require (
11 | github.com/davecgh/go-spew v1.1.1 // indirect
12 | github.com/pmezard/go-difflib v1.0.0 // indirect
13 | gopkg.in/yaml.v3 v3.0.1 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
8 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 |
--------------------------------------------------------------------------------
/multipool.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2023 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "errors"
27 | "fmt"
28 | "math"
29 | "strings"
30 | "sync/atomic"
31 | "time"
32 |
33 | "golang.org/x/sync/errgroup"
34 | )
35 |
36 | // LoadBalancingStrategy represents the type of load-balancing algorithm.
37 | type LoadBalancingStrategy int
38 |
39 | const (
40 | // RoundRobin distributes task to a list of pools in rotation.
41 | RoundRobin LoadBalancingStrategy = 1 << (iota + 1)
42 |
43 | // LeastTasks always selects the pool with the least number of pending tasks.
44 | LeastTasks
45 | )
46 |
47 | // MultiPool consists of multiple pools, from which you will benefit the
48 | // performance improvement on basis of the fine-grained locking that reduces
49 | // the lock contention.
50 | // MultiPool is a good fit for the scenario where you have a large number of
51 | // tasks to submit, and you don't want the single pool to be the bottleneck.
52 | type MultiPool struct {
53 | pools []*Pool
54 | index uint32
55 | state int32
56 | lbs LoadBalancingStrategy
57 | }
58 |
59 | // NewMultiPool instantiates a MultiPool with a size of the pool list and a size
60 | // per pool, and the load-balancing strategy.
61 | func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) {
62 | if size <= 0 {
63 | return nil, ErrInvalidMultiPoolSize
64 | }
65 |
66 | if lbs != RoundRobin && lbs != LeastTasks {
67 | return nil, ErrInvalidLoadBalancingStrategy
68 | }
69 | pools := make([]*Pool, size)
70 | for i := 0; i < size; i++ {
71 | pool, err := NewPool(sizePerPool, options...)
72 | if err != nil {
73 | return nil, err
74 | }
75 | pools[i] = pool
76 | }
77 | return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
78 | }
79 |
80 | func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
81 | switch lbs {
82 | case RoundRobin:
83 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
84 | case LeastTasks:
85 | leastTasks := 1<<31 - 1
86 | for i, pool := range mp.pools {
87 | if n := pool.Running(); n < leastTasks {
88 | leastTasks = n
89 | idx = i
90 | }
91 | }
92 | return
93 | }
94 | return -1
95 | }
96 |
97 | // Submit submits a task to a pool selected by the load-balancing strategy.
98 | func (mp *MultiPool) Submit(task func()) (err error) {
99 | if mp.IsClosed() {
100 | return ErrPoolClosed
101 | }
102 | if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil {
103 | return
104 | }
105 | if err == ErrPoolOverload && mp.lbs == RoundRobin {
106 | return mp.pools[mp.next(LeastTasks)].Submit(task)
107 | }
108 | return
109 | }
110 |
111 | // Running returns the number of the currently running workers across all pools.
112 | func (mp *MultiPool) Running() (n int) {
113 | for _, pool := range mp.pools {
114 | n += pool.Running()
115 | }
116 | return
117 | }
118 |
119 | // RunningByIndex returns the number of the currently running workers in the specific pool.
120 | func (mp *MultiPool) RunningByIndex(idx int) (int, error) {
121 | if idx < 0 || idx >= len(mp.pools) {
122 | return -1, ErrInvalidPoolIndex
123 | }
124 | return mp.pools[idx].Running(), nil
125 | }
126 |
127 | // Free returns the number of available workers across all pools.
128 | func (mp *MultiPool) Free() (n int) {
129 | for _, pool := range mp.pools {
130 | n += pool.Free()
131 | }
132 | return
133 | }
134 |
135 | // FreeByIndex returns the number of available workers in the specific pool.
136 | func (mp *MultiPool) FreeByIndex(idx int) (int, error) {
137 | if idx < 0 || idx >= len(mp.pools) {
138 | return -1, ErrInvalidPoolIndex
139 | }
140 | return mp.pools[idx].Free(), nil
141 | }
142 |
143 | // Waiting returns the number of the currently waiting tasks across all pools.
144 | func (mp *MultiPool) Waiting() (n int) {
145 | for _, pool := range mp.pools {
146 | n += pool.Waiting()
147 | }
148 | return
149 | }
150 |
151 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
152 | func (mp *MultiPool) WaitingByIndex(idx int) (int, error) {
153 | if idx < 0 || idx >= len(mp.pools) {
154 | return -1, ErrInvalidPoolIndex
155 | }
156 | return mp.pools[idx].Waiting(), nil
157 | }
158 |
159 | // Cap returns the capacity of this multi-pool.
160 | func (mp *MultiPool) Cap() (n int) {
161 | for _, pool := range mp.pools {
162 | n += pool.Cap()
163 | }
164 | return
165 | }
166 |
167 | // Tune resizes each pool in multi-pool.
168 | //
169 | // Note that this method doesn't resize the overall
170 | // capacity of multi-pool.
171 | func (mp *MultiPool) Tune(size int) {
172 | for _, pool := range mp.pools {
173 | pool.Tune(size)
174 | }
175 | }
176 |
177 | // IsClosed indicates whether the multi-pool is closed.
178 | func (mp *MultiPool) IsClosed() bool {
179 | return atomic.LoadInt32(&mp.state) == CLOSED
180 | }
181 |
182 | // ReleaseTimeout closes the multi-pool with a timeout,
183 | // it waits all pools to be closed before timing out.
184 | func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error {
185 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
186 | return ErrPoolClosed
187 | }
188 |
189 | errCh := make(chan error, len(mp.pools))
190 | var wg errgroup.Group
191 | for i, pool := range mp.pools {
192 | func(p *Pool, idx int) {
193 | wg.Go(func() error {
194 | err := p.ReleaseTimeout(timeout)
195 | if err != nil {
196 | err = fmt.Errorf("pool %d: %v", idx, err)
197 | }
198 | errCh <- err
199 | return err
200 | })
201 | }(pool, i)
202 | }
203 |
204 | _ = wg.Wait()
205 |
206 | var errStr strings.Builder
207 | for i := 0; i < len(mp.pools); i++ {
208 | if err := <-errCh; err != nil {
209 | errStr.WriteString(err.Error())
210 | errStr.WriteString(" | ")
211 | }
212 | }
213 |
214 | if errStr.Len() == 0 {
215 | return nil
216 | }
217 |
218 | return errors.New(strings.TrimSuffix(errStr.String(), " | "))
219 | }
220 |
221 | // Reboot reboots a released multi-pool.
222 | func (mp *MultiPool) Reboot() {
223 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
224 | atomic.StoreUint32(&mp.index, 0)
225 | for _, pool := range mp.pools {
226 | pool.Reboot()
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/multipool_func.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2023 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "errors"
27 | "fmt"
28 | "math"
29 | "strings"
30 | "sync/atomic"
31 | "time"
32 |
33 | "golang.org/x/sync/errgroup"
34 | )
35 |
36 | // MultiPoolWithFunc consists of multiple pools, from which you will benefit the
37 | // performance improvement on basis of the fine-grained locking that reduces
38 | // the lock contention.
39 | // MultiPoolWithFunc is a good fit for the scenario where you have a large number of
40 | // tasks to submit, and you don't want the single pool to be the bottleneck.
41 | type MultiPoolWithFunc struct {
42 | pools []*PoolWithFunc
43 | index uint32
44 | state int32
45 | lbs LoadBalancingStrategy
46 | }
47 |
48 | // NewMultiPoolWithFunc instantiates a MultiPoolWithFunc with a size of the pool list and a size
49 | // per pool, and the load-balancing strategy.
50 | func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
51 | if size <= 0 {
52 | return nil, ErrInvalidMultiPoolSize
53 | }
54 |
55 | if lbs != RoundRobin && lbs != LeastTasks {
56 | return nil, ErrInvalidLoadBalancingStrategy
57 | }
58 | pools := make([]*PoolWithFunc, size)
59 | for i := 0; i < size; i++ {
60 | pool, err := NewPoolWithFunc(sizePerPool, fn, options...)
61 | if err != nil {
62 | return nil, err
63 | }
64 | pools[i] = pool
65 | }
66 | return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
67 | }
68 |
69 | func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
70 | switch lbs {
71 | case RoundRobin:
72 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
73 | case LeastTasks:
74 | leastTasks := 1<<31 - 1
75 | for i, pool := range mp.pools {
76 | if n := pool.Running(); n < leastTasks {
77 | leastTasks = n
78 | idx = i
79 | }
80 | }
81 | return
82 | }
83 | return -1
84 | }
85 |
86 | // Invoke submits a task to a pool selected by the load-balancing strategy.
87 | func (mp *MultiPoolWithFunc) Invoke(args any) (err error) {
88 | if mp.IsClosed() {
89 | return ErrPoolClosed
90 | }
91 |
92 | if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
93 | return
94 | }
95 | if err == ErrPoolOverload && mp.lbs == RoundRobin {
96 | return mp.pools[mp.next(LeastTasks)].Invoke(args)
97 | }
98 | return
99 | }
100 |
101 | // Running returns the number of the currently running workers across all pools.
102 | func (mp *MultiPoolWithFunc) Running() (n int) {
103 | for _, pool := range mp.pools {
104 | n += pool.Running()
105 | }
106 | return
107 | }
108 |
109 | // RunningByIndex returns the number of the currently running workers in the specific pool.
110 | func (mp *MultiPoolWithFunc) RunningByIndex(idx int) (int, error) {
111 | if idx < 0 || idx >= len(mp.pools) {
112 | return -1, ErrInvalidPoolIndex
113 | }
114 | return mp.pools[idx].Running(), nil
115 | }
116 |
117 | // Free returns the number of available workers across all pools.
118 | func (mp *MultiPoolWithFunc) Free() (n int) {
119 | for _, pool := range mp.pools {
120 | n += pool.Free()
121 | }
122 | return
123 | }
124 |
125 | // FreeByIndex returns the number of available workers in the specific pool.
126 | func (mp *MultiPoolWithFunc) FreeByIndex(idx int) (int, error) {
127 | if idx < 0 || idx >= len(mp.pools) {
128 | return -1, ErrInvalidPoolIndex
129 | }
130 | return mp.pools[idx].Free(), nil
131 | }
132 |
133 | // Waiting returns the number of the currently waiting tasks across all pools.
134 | func (mp *MultiPoolWithFunc) Waiting() (n int) {
135 | for _, pool := range mp.pools {
136 | n += pool.Waiting()
137 | }
138 | return
139 | }
140 |
141 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
142 | func (mp *MultiPoolWithFunc) WaitingByIndex(idx int) (int, error) {
143 | if idx < 0 || idx >= len(mp.pools) {
144 | return -1, ErrInvalidPoolIndex
145 | }
146 | return mp.pools[idx].Waiting(), nil
147 | }
148 |
149 | // Cap returns the capacity of this multi-pool.
150 | func (mp *MultiPoolWithFunc) Cap() (n int) {
151 | for _, pool := range mp.pools {
152 | n += pool.Cap()
153 | }
154 | return
155 | }
156 |
157 | // Tune resizes each pool in multi-pool.
158 | //
159 | // Note that this method doesn't resize the overall
160 | // capacity of multi-pool.
161 | func (mp *MultiPoolWithFunc) Tune(size int) {
162 | for _, pool := range mp.pools {
163 | pool.Tune(size)
164 | }
165 | }
166 |
167 | // IsClosed indicates whether the multi-pool is closed.
168 | func (mp *MultiPoolWithFunc) IsClosed() bool {
169 | return atomic.LoadInt32(&mp.state) == CLOSED
170 | }
171 |
172 | // ReleaseTimeout closes the multi-pool with a timeout,
173 | // it waits all pools to be closed before timing out.
174 | func (mp *MultiPoolWithFunc) ReleaseTimeout(timeout time.Duration) error {
175 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
176 | return ErrPoolClosed
177 | }
178 |
179 | errCh := make(chan error, len(mp.pools))
180 | var wg errgroup.Group
181 | for i, pool := range mp.pools {
182 | func(p *PoolWithFunc, idx int) {
183 | wg.Go(func() error {
184 | err := p.ReleaseTimeout(timeout)
185 | if err != nil {
186 | err = fmt.Errorf("pool %d: %v", idx, err)
187 | }
188 | errCh <- err
189 | return err
190 | })
191 | }(pool, i)
192 | }
193 |
194 | _ = wg.Wait()
195 |
196 | var errStr strings.Builder
197 | for i := 0; i < len(mp.pools); i++ {
198 | if err := <-errCh; err != nil {
199 | errStr.WriteString(err.Error())
200 | errStr.WriteString(" | ")
201 | }
202 | }
203 |
204 | if errStr.Len() == 0 {
205 | return nil
206 | }
207 |
208 | return errors.New(strings.TrimSuffix(errStr.String(), " | "))
209 | }
210 |
211 | // Reboot reboots a released multi-pool.
212 | func (mp *MultiPoolWithFunc) Reboot() {
213 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
214 | atomic.StoreUint32(&mp.index, 0)
215 | for _, pool := range mp.pools {
216 | pool.Reboot()
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/multipool_func_generic.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2025 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "errors"
27 | "fmt"
28 | "math"
29 | "strings"
30 | "sync/atomic"
31 | "time"
32 |
33 | "golang.org/x/sync/errgroup"
34 | )
35 |
36 | // MultiPoolWithFuncGeneric is the generic version of MultiPoolWithFunc.
37 | type MultiPoolWithFuncGeneric[T any] struct {
38 | pools []*PoolWithFuncGeneric[T]
39 | index uint32
40 | state int32
41 | lbs LoadBalancingStrategy
42 | }
43 |
44 | // NewMultiPoolWithFuncGeneric instantiates a MultiPoolWithFunc with a size of the pool list and a size
45 | // per pool, and the load-balancing strategy.
46 | func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFuncGeneric[T], error) {
47 | if size <= 0 {
48 | return nil, ErrInvalidMultiPoolSize
49 | }
50 |
51 | if lbs != RoundRobin && lbs != LeastTasks {
52 | return nil, ErrInvalidLoadBalancingStrategy
53 | }
54 | pools := make([]*PoolWithFuncGeneric[T], size)
55 | for i := 0; i < size; i++ {
56 | pool, err := NewPoolWithFuncGeneric(sizePerPool, fn, options...)
57 | if err != nil {
58 | return nil, err
59 | }
60 | pools[i] = pool
61 | }
62 | return &MultiPoolWithFuncGeneric[T]{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
63 | }
64 |
65 | func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (idx int) {
66 | switch lbs {
67 | case RoundRobin:
68 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
69 | case LeastTasks:
70 | leastTasks := 1<<31 - 1
71 | for i, pool := range mp.pools {
72 | if n := pool.Running(); n < leastTasks {
73 | leastTasks = n
74 | idx = i
75 | }
76 | }
77 | return
78 | }
79 | return -1
80 | }
81 |
82 | // Invoke submits a task to a pool selected by the load-balancing strategy.
83 | func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) {
84 | if mp.IsClosed() {
85 | return ErrPoolClosed
86 | }
87 |
88 | if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
89 | return
90 | }
91 | if err == ErrPoolOverload && mp.lbs == RoundRobin {
92 | return mp.pools[mp.next(LeastTasks)].Invoke(args)
93 | }
94 | return
95 | }
96 |
97 | // Running returns the number of the currently running workers across all pools.
98 | func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) {
99 | for _, pool := range mp.pools {
100 | n += pool.Running()
101 | }
102 | return
103 | }
104 |
105 | // RunningByIndex returns the number of the currently running workers in the specific pool.
106 | func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, error) {
107 | if idx < 0 || idx >= len(mp.pools) {
108 | return -1, ErrInvalidPoolIndex
109 | }
110 | return mp.pools[idx].Running(), nil
111 | }
112 |
113 | // Free returns the number of available workers across all pools.
114 | func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) {
115 | for _, pool := range mp.pools {
116 | n += pool.Free()
117 | }
118 | return
119 | }
120 |
121 | // FreeByIndex returns the number of available workers in the specific pool.
122 | func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) {
123 | if idx < 0 || idx >= len(mp.pools) {
124 | return -1, ErrInvalidPoolIndex
125 | }
126 | return mp.pools[idx].Free(), nil
127 | }
128 |
129 | // Waiting returns the number of the currently waiting tasks across all pools.
130 | func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) {
131 | for _, pool := range mp.pools {
132 | n += pool.Waiting()
133 | }
134 | return
135 | }
136 |
137 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
138 | func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, error) {
139 | if idx < 0 || idx >= len(mp.pools) {
140 | return -1, ErrInvalidPoolIndex
141 | }
142 | return mp.pools[idx].Waiting(), nil
143 | }
144 |
145 | // Cap returns the capacity of this multi-pool.
146 | func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) {
147 | for _, pool := range mp.pools {
148 | n += pool.Cap()
149 | }
150 | return
151 | }
152 |
153 | // Tune resizes each pool in multi-pool.
154 | //
155 | // Note that this method doesn't resize the overall
156 | // capacity of multi-pool.
157 | func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) {
158 | for _, pool := range mp.pools {
159 | pool.Tune(size)
160 | }
161 | }
162 |
163 | // IsClosed indicates whether the multi-pool is closed.
164 | func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool {
165 | return atomic.LoadInt32(&mp.state) == CLOSED
166 | }
167 |
168 | // ReleaseTimeout closes the multi-pool with a timeout,
169 | // it waits all pools to be closed before timing out.
170 | func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Duration) error {
171 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
172 | return ErrPoolClosed
173 | }
174 |
175 | errCh := make(chan error, len(mp.pools))
176 | var wg errgroup.Group
177 | for i, pool := range mp.pools {
178 | func(p *PoolWithFuncGeneric[T], idx int) {
179 | wg.Go(func() error {
180 | err := p.ReleaseTimeout(timeout)
181 | if err != nil {
182 | err = fmt.Errorf("pool %d: %v", idx, err)
183 | }
184 | errCh <- err
185 | return err
186 | })
187 | }(pool, i)
188 | }
189 |
190 | _ = wg.Wait()
191 |
192 | var errStr strings.Builder
193 | for i := 0; i < len(mp.pools); i++ {
194 | if err := <-errCh; err != nil {
195 | errStr.WriteString(err.Error())
196 | errStr.WriteString(" | ")
197 | }
198 | }
199 |
200 | if errStr.Len() == 0 {
201 | return nil
202 | }
203 |
204 | return errors.New(strings.TrimSuffix(errStr.String(), " | "))
205 | }
206 |
207 | // Reboot reboots a released multi-pool.
208 | func (mp *MultiPoolWithFuncGeneric[T]) Reboot() {
209 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
210 | atomic.StoreUint32(&mp.index, 0)
211 | for _, pool := range mp.pools {
212 | pool.Reboot()
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018. Andy Pan. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import "time"
26 |
27 | // Option represents the optional function.
28 | type Option func(opts *Options)
29 |
30 | func loadOptions(options ...Option) *Options {
31 | opts := new(Options)
32 | for _, option := range options {
33 | option(opts)
34 | }
35 | return opts
36 | }
37 |
38 | // Options contains all options which will be applied when instantiating an ants pool.
39 | type Options struct {
40 | // ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers,
41 | // the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been
42 | // used for more than `ExpiryDuration`.
43 | ExpiryDuration time.Duration
44 |
45 | // PreAlloc indicates whether to make memory pre-allocation when initializing Pool.
46 | PreAlloc bool
47 |
48 | // Max number of goroutine blocking on pool.Submit.
49 | // 0 (default value) means no such limit.
50 | MaxBlockingTasks int
51 |
52 | // When Nonblocking is true, Pool.Submit will never be blocked.
53 | // ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
54 | // When Nonblocking is true, MaxBlockingTasks is inoperative.
55 | Nonblocking bool
56 |
57 | // PanicHandler is used to handle panics from each worker goroutine.
58 | // If nil, the default behavior is to capture the value given to panic
59 | // and resume normal execution and print that value along with the
60 | // stack trace of the goroutine
61 | PanicHandler func(any)
62 |
63 | // Logger is the customized logger for logging info, if it is not set,
64 | // default standard logger from log package is used.
65 | Logger Logger
66 |
67 | // When DisablePurge is true, workers are not purged and are resident.
68 | DisablePurge bool
69 | }
70 |
71 | // WithOptions accepts the whole Options config.
72 | func WithOptions(options Options) Option {
73 | return func(opts *Options) {
74 | *opts = options
75 | }
76 | }
77 |
78 | // WithExpiryDuration sets up the interval time of cleaning up goroutines.
79 | func WithExpiryDuration(expiryDuration time.Duration) Option {
80 | return func(opts *Options) {
81 | opts.ExpiryDuration = expiryDuration
82 | }
83 | }
84 |
85 | // WithPreAlloc indicates whether it should malloc for workers.
86 | func WithPreAlloc(preAlloc bool) Option {
87 | return func(opts *Options) {
88 | opts.PreAlloc = preAlloc
89 | }
90 | }
91 |
92 | // WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool.
93 | func WithMaxBlockingTasks(maxBlockingTasks int) Option {
94 | return func(opts *Options) {
95 | opts.MaxBlockingTasks = maxBlockingTasks
96 | }
97 | }
98 |
99 | // WithNonblocking indicates that pool will return nil when there is no available workers.
100 | func WithNonblocking(nonblocking bool) Option {
101 | return func(opts *Options) {
102 | opts.Nonblocking = nonblocking
103 | }
104 | }
105 |
106 | // WithPanicHandler sets up panic handler.
107 | func WithPanicHandler(panicHandler func(any)) Option {
108 | return func(opts *Options) {
109 | opts.PanicHandler = panicHandler
110 | }
111 | }
112 |
113 | // WithLogger sets up a customized logger.
114 | func WithLogger(logger Logger) Option {
115 | return func(opts *Options) {
116 | opts.Logger = logger
117 | }
118 | }
119 |
120 | // WithDisablePurge indicates whether we turn off automatically purge.
121 | func WithDisablePurge(disable bool) Option {
122 | return func(opts *Options) {
123 | opts.DisablePurge = disable
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/pkg/sync/spinlock.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Andy Pan & Dietoad. All rights reserved.
2 | // Use of this source code is governed by an MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package sync
6 |
7 | import (
8 | "runtime"
9 | "sync"
10 | "sync/atomic"
11 | )
12 |
13 | type spinLock uint32
14 |
15 | const maxBackoff = 16
16 |
17 | func (sl *spinLock) Lock() {
18 | backoff := 1
19 | for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
20 | // Leverage the exponential backoff algorithm, see https://en.wikipedia.org/wiki/Exponential_backoff.
21 | for i := 0; i < backoff; i++ {
22 | runtime.Gosched()
23 | }
24 | if backoff < maxBackoff {
25 | backoff <<= 1
26 | }
27 | }
28 | }
29 |
30 | func (sl *spinLock) Unlock() {
31 | atomic.StoreUint32((*uint32)(sl), 0)
32 | }
33 |
34 | // NewSpinLock instantiates a spin-lock.
35 | func NewSpinLock() sync.Locker {
36 | return new(spinLock)
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/sync/spinlock_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Andy Pan & Dietoad. All rights reserved.
2 | // Use of this source code is governed by an MIT-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package sync
6 |
7 | import (
8 | "runtime"
9 | "sync"
10 | "sync/atomic"
11 | "testing"
12 | )
13 |
14 | /*
15 | Benchmark result for three types of locks:
16 | goos: darwin
17 | goarch: arm64
18 | pkg: github.com/panjf2000/ants/v2/pkg/sync
19 | BenchmarkMutex-10 10452573 111.1 ns/op 0 B/op 0 allocs/op
20 | BenchmarkSpinLock-10 58953211 18.01 ns/op 0 B/op 0 allocs/op
21 | BenchmarkBackOffSpinLock-10 100000000 10.81 ns/op 0 B/op 0 allocs/op
22 | */
23 |
24 | type originSpinLock uint32
25 |
26 | func (sl *originSpinLock) Lock() {
27 | for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
28 | runtime.Gosched()
29 | }
30 | }
31 |
32 | func (sl *originSpinLock) Unlock() {
33 | atomic.StoreUint32((*uint32)(sl), 0)
34 | }
35 |
36 | func NewOriginSpinLock() sync.Locker {
37 | return new(originSpinLock)
38 | }
39 |
40 | func BenchmarkMutex(b *testing.B) {
41 | m := sync.Mutex{}
42 | b.RunParallel(func(pb *testing.PB) {
43 | for pb.Next() {
44 | m.Lock()
45 | //nolint:staticcheck
46 | m.Unlock()
47 | }
48 | })
49 | }
50 |
51 | func BenchmarkSpinLock(b *testing.B) {
52 | spin := NewOriginSpinLock()
53 | b.RunParallel(func(pb *testing.PB) {
54 | for pb.Next() {
55 | spin.Lock()
56 | //nolint:staticcheck
57 | spin.Unlock()
58 | }
59 | })
60 | }
61 |
62 | func BenchmarkBackOffSpinLock(b *testing.B) {
63 | spin := NewSpinLock()
64 | b.RunParallel(func(pb *testing.PB) {
65 | for pb.Next() {
66 | spin.Lock()
67 | //nolint:staticcheck
68 | spin.Unlock()
69 | }
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/sync/sync.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025. Andy Pan. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | // Package sync provides some handy implementations for synchronization access.
24 | // At the moment, there is only an implementation of spin-lock.
25 | package sync
26 |
--------------------------------------------------------------------------------
/pool.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | // Pool is a goroutine pool that limits and recycles a mass of goroutines.
26 | // The pool capacity can be fixed or unlimited.
27 | type Pool struct {
28 | *poolCommon
29 | }
30 |
31 | // Submit submits a task to the pool.
32 | //
33 | // Note that you are allowed to call Pool.Submit() from the current Pool.Submit(),
34 | // but what calls for special attention is that you will get blocked with the last
35 | // Pool.Submit() call once the current Pool runs out of its capacity, and to avoid this,
36 | // you should instantiate a Pool with ants.WithNonblocking(true).
37 | func (p *Pool) Submit(task func()) error {
38 | if p.IsClosed() {
39 | return ErrPoolClosed
40 | }
41 |
42 | w, err := p.retrieveWorker()
43 | if w != nil {
44 | w.inputFunc(task)
45 | }
46 | return err
47 | }
48 |
49 | // NewPool instantiates a Pool with customized options.
50 | func NewPool(size int, options ...Option) (*Pool, error) {
51 | pc, err := newPool(size, options...)
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | pool := &Pool{poolCommon: pc}
57 | pool.workerCache.New = func() any {
58 | return &goWorker{
59 | pool: pool,
60 | task: make(chan func(), workerChanCap),
61 | }
62 | }
63 |
64 | return pool, nil
65 | }
66 |
--------------------------------------------------------------------------------
/pool_func.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | // PoolWithFunc is like Pool but accepts a unified function for all goroutines to execute.
26 | type PoolWithFunc struct {
27 | *poolCommon
28 |
29 | // fn is the unified function for processing tasks.
30 | fn func(any)
31 | }
32 |
33 | // Invoke passes arguments to the pool.
34 | //
35 | // Note that you are allowed to call Pool.Invoke() from the current Pool.Invoke(),
36 | // but what calls for special attention is that you will get blocked with the last
37 | // Pool.Invoke() call once the current Pool runs out of its capacity, and to avoid this,
38 | // you should instantiate a PoolWithFunc with ants.WithNonblocking(true).
39 | func (p *PoolWithFunc) Invoke(arg any) error {
40 | if p.IsClosed() {
41 | return ErrPoolClosed
42 | }
43 |
44 | w, err := p.retrieveWorker()
45 | if w != nil {
46 | w.inputArg(arg)
47 | }
48 | return err
49 | }
50 |
51 | // NewPoolWithFunc instantiates a PoolWithFunc with customized options.
52 | func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) {
53 | if pf == nil {
54 | return nil, ErrLackPoolFunc
55 | }
56 |
57 | pc, err := newPool(size, options...)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | pool := &PoolWithFunc{
63 | poolCommon: pc,
64 | fn: pf,
65 | }
66 |
67 | pool.workerCache.New = func() any {
68 | return &goWorkerWithFunc{
69 | pool: pool,
70 | arg: make(chan any, workerChanCap),
71 | }
72 | }
73 |
74 | return pool, nil
75 | }
76 |
--------------------------------------------------------------------------------
/pool_func_generic.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2025 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | // PoolWithFuncGeneric is the generic version of PoolWithFunc.
26 | type PoolWithFuncGeneric[T any] struct {
27 | *poolCommon
28 |
29 | // fn is the unified function for processing tasks.
30 | fn func(T)
31 | }
32 |
33 | // Invoke passes the argument to the pool to start a new task.
34 | func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error {
35 | if p.IsClosed() {
36 | return ErrPoolClosed
37 | }
38 |
39 | w, err := p.retrieveWorker()
40 | if w != nil {
41 | w.(*goWorkerWithFuncGeneric[T]).arg <- arg
42 | }
43 | return err
44 | }
45 |
46 | // NewPoolWithFuncGeneric instantiates a PoolWithFuncGeneric[T] with customized options.
47 | func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Option) (*PoolWithFuncGeneric[T], error) {
48 | if pf == nil {
49 | return nil, ErrLackPoolFunc
50 | }
51 |
52 | pc, err := newPool(size, options...)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | pool := &PoolWithFuncGeneric[T]{
58 | poolCommon: pc,
59 | fn: pf,
60 | }
61 |
62 | pool.workerCache.New = func() any {
63 | return &goWorkerWithFuncGeneric[T]{
64 | pool: pool,
65 | arg: make(chan T, workerChanCap),
66 | exit: make(chan struct{}, 1),
67 | }
68 | }
69 |
70 | return pool, nil
71 | }
72 |
--------------------------------------------------------------------------------
/worker.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "runtime/debug"
27 | "time"
28 | )
29 |
30 | // goWorker is the actual executor who runs the tasks,
31 | // it starts a goroutine that accepts tasks and
32 | // performs function calls.
33 | type goWorker struct {
34 | worker
35 |
36 | // pool who owns this worker.
37 | pool *Pool
38 |
39 | // task is a job should be done.
40 | task chan func()
41 |
42 | // lastUsed will be updated when putting a worker back into queue.
43 | lastUsed time.Time
44 | }
45 |
46 | // run starts a goroutine to repeat the process
47 | // that performs the function calls.
48 | func (w *goWorker) run() {
49 | w.pool.addRunning(1)
50 | go func() {
51 | defer func() {
52 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
53 | w.pool.once.Do(func() {
54 | close(w.pool.allDone)
55 | })
56 | }
57 | w.pool.workerCache.Put(w)
58 | if p := recover(); p != nil {
59 | if ph := w.pool.options.PanicHandler; ph != nil {
60 | ph(p)
61 | } else {
62 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
63 | }
64 | }
65 | // Call Signal() here in case there are goroutines waiting for available workers.
66 | w.pool.cond.Signal()
67 | }()
68 |
69 | for fn := range w.task {
70 | if fn == nil {
71 | return
72 | }
73 | fn()
74 | if ok := w.pool.revertWorker(w); !ok {
75 | return
76 | }
77 | }
78 | }()
79 | }
80 |
81 | func (w *goWorker) finish() {
82 | w.task <- nil
83 | }
84 |
85 | func (w *goWorker) lastUsedTime() time.Time {
86 | return w.lastUsed
87 | }
88 |
89 | func (w *goWorker) setLastUsedTime(t time.Time) {
90 | w.lastUsed = t
91 | }
92 |
93 | func (w *goWorker) inputFunc(fn func()) {
94 | w.task <- fn
95 | }
96 |
--------------------------------------------------------------------------------
/worker_func.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2018 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "runtime/debug"
27 | "time"
28 | )
29 |
30 | // goWorkerWithFunc is the actual executor who runs the tasks,
31 | // it starts a goroutine that accepts tasks and
32 | // performs function calls.
33 | type goWorkerWithFunc struct {
34 | worker
35 |
36 | // pool who owns this worker.
37 | pool *PoolWithFunc
38 |
39 | // arg is the argument for the function.
40 | arg chan any
41 |
42 | // lastUsed will be updated when putting a worker back into queue.
43 | lastUsed time.Time
44 | }
45 |
46 | // run starts a goroutine to repeat the process
47 | // that performs the function calls.
48 | func (w *goWorkerWithFunc) run() {
49 | w.pool.addRunning(1)
50 | go func() {
51 | defer func() {
52 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
53 | w.pool.once.Do(func() {
54 | close(w.pool.allDone)
55 | })
56 | }
57 | w.pool.workerCache.Put(w)
58 | if p := recover(); p != nil {
59 | if ph := w.pool.options.PanicHandler; ph != nil {
60 | ph(p)
61 | } else {
62 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
63 | }
64 | }
65 | // Call Signal() here in case there are goroutines waiting for available workers.
66 | w.pool.cond.Signal()
67 | }()
68 |
69 | for arg := range w.arg {
70 | if arg == nil {
71 | return
72 | }
73 | w.pool.fn(arg)
74 | if ok := w.pool.revertWorker(w); !ok {
75 | return
76 | }
77 | }
78 | }()
79 | }
80 |
81 | func (w *goWorkerWithFunc) finish() {
82 | w.arg <- nil
83 | }
84 |
85 | func (w *goWorkerWithFunc) lastUsedTime() time.Time {
86 | return w.lastUsed
87 | }
88 |
89 | func (w *goWorkerWithFunc) setLastUsedTime(t time.Time) {
90 | w.lastUsed = t
91 | }
92 |
93 | func (w *goWorkerWithFunc) inputArg(arg any) {
94 | w.arg <- arg
95 | }
96 |
--------------------------------------------------------------------------------
/worker_func_generic.go:
--------------------------------------------------------------------------------
1 | // MIT License
2 |
3 | // Copyright (c) 2025 Andy Pan
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package ants
24 |
25 | import (
26 | "runtime/debug"
27 | "time"
28 | )
29 |
30 | // goWorkerWithFunc is the actual executor who runs the tasks,
31 | // it starts a goroutine that accepts tasks and
32 | // performs function calls.
33 | type goWorkerWithFuncGeneric[T any] struct {
34 | worker
35 |
36 | // pool who owns this worker.
37 | pool *PoolWithFuncGeneric[T]
38 |
39 | // arg is a job should be done.
40 | arg chan T
41 |
42 | // exit signals the goroutine to exit.
43 | exit chan struct{}
44 |
45 | // lastUsed will be updated when putting a worker back into queue.
46 | lastUsed time.Time
47 | }
48 |
49 | // run starts a goroutine to repeat the process
50 | // that performs the function calls.
51 | func (w *goWorkerWithFuncGeneric[T]) run() {
52 | w.pool.addRunning(1)
53 | go func() {
54 | defer func() {
55 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
56 | w.pool.once.Do(func() {
57 | close(w.pool.allDone)
58 | })
59 | }
60 | w.pool.workerCache.Put(w)
61 | if p := recover(); p != nil {
62 | if ph := w.pool.options.PanicHandler; ph != nil {
63 | ph(p)
64 | } else {
65 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
66 | }
67 | }
68 | // Call Signal() here in case there are goroutines waiting for available workers.
69 | w.pool.cond.Signal()
70 | }()
71 |
72 | for {
73 | select {
74 | case <-w.exit:
75 | return
76 | case arg := <-w.arg:
77 | w.pool.fn(arg)
78 | if ok := w.pool.revertWorker(w); !ok {
79 | return
80 | }
81 | }
82 | }
83 | }()
84 | }
85 |
86 | func (w *goWorkerWithFuncGeneric[T]) finish() {
87 | w.exit <- struct{}{}
88 | }
89 |
90 | func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() time.Time {
91 | return w.lastUsed
92 | }
93 |
94 | func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t time.Time) {
95 | w.lastUsed = t
96 | }
97 |
--------------------------------------------------------------------------------
/worker_loop_queue.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019. Ants Authors. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import "time"
26 |
27 | type loopQueue struct {
28 | items []worker
29 | expiry []worker
30 | head int
31 | tail int
32 | size int
33 | isFull bool
34 | }
35 |
36 | func newWorkerLoopQueue(size int) *loopQueue {
37 | if size <= 0 {
38 | return nil
39 | }
40 | return &loopQueue{
41 | items: make([]worker, size),
42 | size: size,
43 | }
44 | }
45 |
46 | func (wq *loopQueue) len() int {
47 | if wq.size == 0 || wq.isEmpty() {
48 | return 0
49 | }
50 |
51 | if wq.head == wq.tail && wq.isFull {
52 | return wq.size
53 | }
54 |
55 | if wq.tail > wq.head {
56 | return wq.tail - wq.head
57 | }
58 |
59 | return wq.size - wq.head + wq.tail
60 | }
61 |
62 | func (wq *loopQueue) isEmpty() bool {
63 | return wq.head == wq.tail && !wq.isFull
64 | }
65 |
66 | func (wq *loopQueue) insert(w worker) error {
67 | if wq.isFull {
68 | return errQueueIsFull
69 | }
70 | wq.items[wq.tail] = w
71 | wq.tail = (wq.tail + 1) % wq.size
72 |
73 | if wq.tail == wq.head {
74 | wq.isFull = true
75 | }
76 |
77 | return nil
78 | }
79 |
80 | func (wq *loopQueue) detach() worker {
81 | if wq.isEmpty() {
82 | return nil
83 | }
84 |
85 | w := wq.items[wq.head]
86 | wq.items[wq.head] = nil
87 | wq.head = (wq.head + 1) % wq.size
88 |
89 | wq.isFull = false
90 |
91 | return w
92 | }
93 |
94 | func (wq *loopQueue) refresh(duration time.Duration) []worker {
95 | expiryTime := time.Now().Add(-duration)
96 | index := wq.binarySearch(expiryTime)
97 | if index == -1 {
98 | return nil
99 | }
100 | wq.expiry = wq.expiry[:0]
101 |
102 | if wq.head <= index {
103 | wq.expiry = append(wq.expiry, wq.items[wq.head:index+1]...)
104 | for i := wq.head; i < index+1; i++ {
105 | wq.items[i] = nil
106 | }
107 | } else {
108 | wq.expiry = append(wq.expiry, wq.items[0:index+1]...)
109 | wq.expiry = append(wq.expiry, wq.items[wq.head:]...)
110 | for i := 0; i < index+1; i++ {
111 | wq.items[i] = nil
112 | }
113 | for i := wq.head; i < wq.size; i++ {
114 | wq.items[i] = nil
115 | }
116 | }
117 | head := (index + 1) % wq.size
118 | wq.head = head
119 | if len(wq.expiry) > 0 {
120 | wq.isFull = false
121 | }
122 |
123 | return wq.expiry
124 | }
125 |
126 | func (wq *loopQueue) binarySearch(expiryTime time.Time) int {
127 | var mid, nlen, basel, tmid int
128 | nlen = len(wq.items)
129 |
130 | // if no need to remove work, return -1
131 | if wq.isEmpty() || expiryTime.Before(wq.items[wq.head].lastUsedTime()) {
132 | return -1
133 | }
134 |
135 | // example
136 | // size = 8, head = 7, tail = 4
137 | // [ 2, 3, 4, 5, nil, nil, nil, 1] true position
138 | // 0 1 2 3 4 5 6 7
139 | // tail head
140 | //
141 | // 1 2 3 4 nil nil nil 0 mapped position
142 | // r l
143 |
144 | // base algorithm is a copy from worker_stack
145 | // map head and tail to effective left and right
146 | r := (wq.tail - 1 - wq.head + nlen) % nlen
147 | basel = wq.head
148 | l := 0
149 | for l <= r {
150 | mid = l + ((r - l) >> 1) // avoid overflow when computing mid
151 | // calculate true mid position from mapped mid position
152 | tmid = (mid + basel + nlen) % nlen
153 | if expiryTime.Before(wq.items[tmid].lastUsedTime()) {
154 | r = mid - 1
155 | } else {
156 | l = mid + 1
157 | }
158 | }
159 | // return true position from mapped position
160 | return (r + basel + nlen) % nlen
161 | }
162 |
163 | func (wq *loopQueue) reset() {
164 | if wq.isEmpty() {
165 | return
166 | }
167 |
168 | retry:
169 | if w := wq.detach(); w != nil {
170 | w.finish()
171 | goto retry
172 | }
173 | wq.head = 0
174 | wq.tail = 0
175 | }
176 |
--------------------------------------------------------------------------------
/worker_loop_queue_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019. Ants Authors. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import (
26 | "runtime"
27 | "testing"
28 | "time"
29 |
30 | "github.com/stretchr/testify/require"
31 | )
32 |
33 | func TestNewLoopQueue(t *testing.T) {
34 | size := 100
35 | q := newWorkerLoopQueue(size)
36 | require.EqualValues(t, 0, q.len(), "Len error")
37 | require.Equal(t, true, q.isEmpty(), "IsEmpty error")
38 | require.Nil(t, q.detach(), "Dequeue error")
39 |
40 | require.Nil(t, newWorkerLoopQueue(0))
41 | }
42 |
43 | func TestLoopQueue(t *testing.T) {
44 | size := 10
45 | q := newWorkerLoopQueue(size)
46 |
47 | for i := 0; i < 5; i++ {
48 | err := q.insert(&goWorker{lastUsed: time.Now()})
49 | if err != nil {
50 | break
51 | }
52 | }
53 | require.EqualValues(t, 5, q.len(), "Len error")
54 | _ = q.detach()
55 | require.EqualValues(t, 4, q.len(), "Len error")
56 |
57 | time.Sleep(time.Second)
58 |
59 | for i := 0; i < 6; i++ {
60 | err := q.insert(&goWorker{lastUsed: time.Now()})
61 | if err != nil {
62 | break
63 | }
64 | }
65 | require.EqualValues(t, 10, q.len(), "Len error")
66 |
67 | err := q.insert(&goWorker{lastUsed: time.Now()})
68 | require.Error(t, err, "Enqueue, error")
69 |
70 | q.refresh(time.Second)
71 | require.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
72 | }
73 |
74 | func TestRotatedQueueSearch(t *testing.T) {
75 | if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
76 | t.Skip("Skip this test on Windows platform")
77 | }
78 |
79 | size := 10
80 | q := newWorkerLoopQueue(size)
81 |
82 | // 1
83 | expiry1 := time.Now()
84 |
85 | _ = q.insert(&goWorker{lastUsed: time.Now()})
86 |
87 | require.EqualValues(t, 0, q.binarySearch(time.Now()), "index should be 0")
88 | require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
89 |
90 | // 2
91 | expiry2 := time.Now()
92 | _ = q.insert(&goWorker{lastUsed: time.Now()})
93 |
94 | require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
95 |
96 | require.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0")
97 |
98 | require.EqualValues(t, 1, q.binarySearch(time.Now()), "index should be 1")
99 |
100 | // more
101 | for i := 0; i < 5; i++ {
102 | _ = q.insert(&goWorker{lastUsed: time.Now()})
103 | }
104 |
105 | expiry3 := time.Now()
106 | _ = q.insert(&goWorker{lastUsed: expiry3})
107 |
108 | var err error
109 | for err != errQueueIsFull {
110 | err = q.insert(&goWorker{lastUsed: time.Now()})
111 | }
112 |
113 | require.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7")
114 |
115 | // rotate
116 | for i := 0; i < 6; i++ {
117 | _ = q.detach()
118 | }
119 |
120 | expiry4 := time.Now()
121 | _ = q.insert(&goWorker{lastUsed: expiry4})
122 |
123 | for i := 0; i < 4; i++ {
124 | _ = q.insert(&goWorker{lastUsed: time.Now()})
125 | }
126 | // head = 6, tail = 5, insert direction ->
127 | // [expiry4, time, time, time, time, nil/tail, time/head, time, time, time]
128 | require.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0")
129 |
130 | for i := 0; i < 3; i++ {
131 | _ = q.detach()
132 | }
133 | expiry5 := time.Now()
134 | _ = q.insert(&goWorker{lastUsed: expiry5})
135 |
136 | // head = 6, tail = 5, insert direction ->
137 | // [expiry4, time, time, time, time, expiry5, nil/tail, nil, nil, time/head]
138 | require.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5")
139 |
140 | for i := 0; i < 3; i++ {
141 | _ = q.insert(&goWorker{lastUsed: time.Now()})
142 | }
143 | // head = 9, tail = 9, insert direction ->
144 | // [expiry4, time, time, time, time, expiry5, time, time, time, time/head/tail]
145 | require.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1")
146 |
147 | require.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9")
148 | require.EqualValues(t, 8, q.binarySearch(time.Now()), "index should be 8")
149 | }
150 |
151 | func TestRetrieveExpiry(t *testing.T) {
152 | size := 10
153 | q := newWorkerLoopQueue(size)
154 | expirew := make([]worker, 0)
155 | u, _ := time.ParseDuration("1s")
156 |
157 | // test [ time+1s, time+1s, time+1s, time+1s, time+1s, time, time, time, time, time]
158 | for i := 0; i < size/2; i++ {
159 | _ = q.insert(&goWorker{lastUsed: time.Now()})
160 | }
161 | expirew = append(expirew, q.items[:size/2]...)
162 | time.Sleep(u)
163 |
164 | for i := 0; i < size/2; i++ {
165 | _ = q.insert(&goWorker{lastUsed: time.Now()})
166 | }
167 | workers := q.refresh(u)
168 |
169 | require.EqualValues(t, expirew, workers, "expired workers aren't right")
170 |
171 | // test [ time, time, time, time, time, time+1s, time+1s, time+1s, time+1s, time+1s]
172 | time.Sleep(u)
173 |
174 | for i := 0; i < size/2; i++ {
175 | _ = q.insert(&goWorker{lastUsed: time.Now()})
176 | }
177 | expirew = expirew[:0]
178 | expirew = append(expirew, q.items[size/2:]...)
179 |
180 | workers2 := q.refresh(u)
181 |
182 | require.EqualValues(t, expirew, workers2, "expired workers aren't right")
183 |
184 | // test [ time+1s, time+1s, time+1s, nil, nil, time+1s, time+1s, time+1s, time+1s, time+1s]
185 | for i := 0; i < size/2; i++ {
186 | _ = q.insert(&goWorker{lastUsed: time.Now()})
187 | }
188 | for i := 0; i < size/2; i++ {
189 | _ = q.detach()
190 | }
191 | for i := 0; i < 3; i++ {
192 | _ = q.insert(&goWorker{lastUsed: time.Now()})
193 | }
194 | time.Sleep(u)
195 |
196 | expirew = expirew[:0]
197 | expirew = append(expirew, q.items[0:3]...)
198 | expirew = append(expirew, q.items[size/2:]...)
199 |
200 | workers3 := q.refresh(u)
201 |
202 | require.EqualValues(t, expirew, workers3, "expired workers aren't right")
203 | }
204 |
--------------------------------------------------------------------------------
/worker_queue.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019. Ants Authors. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import (
26 | "errors"
27 | "time"
28 | )
29 |
30 | // errQueueIsFull will be returned when the worker queue is full.
31 | var errQueueIsFull = errors.New("the queue is full")
32 |
33 | type worker interface {
34 | run()
35 | finish()
36 | lastUsedTime() time.Time
37 | setLastUsedTime(t time.Time)
38 | inputFunc(func())
39 | inputArg(any)
40 | }
41 |
42 | type workerQueue interface {
43 | len() int
44 | isEmpty() bool
45 | insert(worker) error
46 | detach() worker
47 | refresh(duration time.Duration) []worker // clean up the stale workers and return them
48 | reset()
49 | }
50 |
51 | type queueType int
52 |
53 | const (
54 | queueTypeStack queueType = 1 << iota
55 | queueTypeLoopQueue
56 | )
57 |
58 | func newWorkerQueue(qType queueType, size int) workerQueue {
59 | switch qType {
60 | case queueTypeStack:
61 | return newWorkerStack(size)
62 | case queueTypeLoopQueue:
63 | return newWorkerLoopQueue(size)
64 | default:
65 | return newWorkerStack(size)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/worker_stack.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019. Ants Authors. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import "time"
26 |
27 | type workerStack struct {
28 | items []worker
29 | expiry []worker
30 | }
31 |
32 | func newWorkerStack(size int) *workerStack {
33 | return &workerStack{
34 | items: make([]worker, 0, size),
35 | }
36 | }
37 |
38 | func (ws *workerStack) len() int {
39 | return len(ws.items)
40 | }
41 |
42 | func (ws *workerStack) isEmpty() bool {
43 | return len(ws.items) == 0
44 | }
45 |
46 | func (ws *workerStack) insert(w worker) error {
47 | ws.items = append(ws.items, w)
48 | return nil
49 | }
50 |
51 | func (ws *workerStack) detach() worker {
52 | l := ws.len()
53 | if l == 0 {
54 | return nil
55 | }
56 |
57 | w := ws.items[l-1]
58 | ws.items[l-1] = nil // avoid memory leaks
59 | ws.items = ws.items[:l-1]
60 |
61 | return w
62 | }
63 |
64 | func (ws *workerStack) refresh(duration time.Duration) []worker {
65 | n := ws.len()
66 | if n == 0 {
67 | return nil
68 | }
69 |
70 | expiryTime := time.Now().Add(-duration)
71 | index := ws.binarySearch(0, n-1, expiryTime)
72 |
73 | ws.expiry = ws.expiry[:0]
74 | if index != -1 {
75 | ws.expiry = append(ws.expiry, ws.items[:index+1]...)
76 | m := copy(ws.items, ws.items[index+1:])
77 | for i := m; i < n; i++ {
78 | ws.items[i] = nil
79 | }
80 | ws.items = ws.items[:m]
81 | }
82 | return ws.expiry
83 | }
84 |
85 | func (ws *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
86 | for l <= r {
87 | mid := l + ((r - l) >> 1) // avoid overflow when computing mid
88 | if expiryTime.Before(ws.items[mid].lastUsedTime()) {
89 | r = mid - 1
90 | } else {
91 | l = mid + 1
92 | }
93 | }
94 | return r
95 | }
96 |
97 | func (ws *workerStack) reset() {
98 | for i := 0; i < ws.len(); i++ {
99 | ws.items[i].finish()
100 | ws.items[i] = nil
101 | }
102 | ws.items = ws.items[:0]
103 | }
104 |
--------------------------------------------------------------------------------
/worker_stack_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019. Ants Authors. All rights reserved.
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | package ants
24 |
25 | import (
26 | "runtime"
27 | "testing"
28 | "time"
29 |
30 | "github.com/stretchr/testify/require"
31 | )
32 |
33 | func TestNewWorkerStack(t *testing.T) {
34 | size := 100
35 | q := newWorkerStack(size)
36 | require.EqualValues(t, 0, q.len(), "Len error")
37 | require.Equal(t, true, q.isEmpty(), "IsEmpty error")
38 | require.Nil(t, q.detach(), "Dequeue error")
39 | }
40 |
41 | func TestWorkerStack(t *testing.T) {
42 | q := newWorkerQueue(queueType(-1), 0)
43 |
44 | for i := 0; i < 5; i++ {
45 | err := q.insert(&goWorker{lastUsed: time.Now()})
46 | if err != nil {
47 | break
48 | }
49 | }
50 | require.EqualValues(t, 5, q.len(), "Len error")
51 |
52 | expired := time.Now()
53 |
54 | err := q.insert(&goWorker{lastUsed: expired})
55 | if err != nil {
56 | t.Fatal("Enqueue error")
57 | }
58 |
59 | time.Sleep(time.Second)
60 |
61 | for i := 0; i < 6; i++ {
62 | err := q.insert(&goWorker{lastUsed: time.Now()})
63 | if err != nil {
64 | t.Fatal("Enqueue error")
65 | }
66 | }
67 | require.EqualValues(t, 12, q.len(), "Len error")
68 | q.refresh(time.Second)
69 | require.EqualValues(t, 6, q.len(), "Len error")
70 | }
71 |
72 | // It seems that something wrong with time.Now() on Windows, not sure whether it is a bug on Windows,
73 | // so exclude this test from Windows platform temporarily.
74 | func TestSearch(t *testing.T) {
75 | if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
76 | t.Skip("Skip this test on Windows platform")
77 | }
78 |
79 | q := newWorkerStack(0)
80 |
81 | // 1
82 | expiry1 := time.Now()
83 |
84 | _ = q.insert(&goWorker{lastUsed: time.Now()})
85 |
86 | require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, time.Now()), "index should be 0")
87 | require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
88 |
89 | // 2
90 | expiry2 := time.Now()
91 | _ = q.insert(&goWorker{lastUsed: time.Now()})
92 |
93 | require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
94 |
95 | require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, expiry2), "index should be 0")
96 |
97 | require.EqualValues(t, 1, q.binarySearch(0, q.len()-1, time.Now()), "index should be 1")
98 |
99 | // more
100 | for i := 0; i < 5; i++ {
101 | _ = q.insert(&goWorker{lastUsed: time.Now()})
102 | }
103 |
104 | expiry3 := time.Now()
105 |
106 | _ = q.insert(&goWorker{lastUsed: expiry3})
107 |
108 | for i := 0; i < 10; i++ {
109 | _ = q.insert(&goWorker{lastUsed: time.Now()})
110 | }
111 |
112 | require.EqualValues(t, 7, q.binarySearch(0, q.len()-1, expiry3), "index should be 7")
113 | }
114 |
--------------------------------------------------------------------------------