mirror of
https://github.com/panjf2000/ants.git
synced 2025-12-16 18:11:03 +00:00
Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0e137a51c | ||
|
|
49d5ce6567 | ||
|
|
d12e26cb9e | ||
|
|
0de04f1c99 | ||
|
|
a44594205e | ||
|
|
1bf9cfdd1b | ||
|
|
6eb0590bc2 | ||
|
|
3120dab1dd | ||
|
|
160ee0a8b2 | ||
|
|
60bd4c42f9 | ||
|
|
9a1446b823 | ||
|
|
4f33c6ef27 | ||
|
|
d4218913f7 | ||
|
|
df029e6411 | ||
|
|
4acc96973c | ||
|
|
0ee85b0a1e | ||
|
|
99121e2404 | ||
|
|
e7e3c844aa | ||
|
|
2d40f3041b | ||
|
|
2a562a7c2a | ||
|
|
d85919e716 | ||
|
|
9df432d040 | ||
|
|
4d0ebb896a | ||
|
|
6169763f4f | ||
|
|
1e73dc2c7b | ||
|
|
29210901a0 | ||
|
|
313f136d00 | ||
|
|
b40e489286 | ||
|
|
da22980e2c | ||
|
|
95dad45c7d | ||
|
|
1933478e2e | ||
|
|
15e896153d | ||
|
|
3ffd3daa37 | ||
|
|
b2374d5ae4 | ||
|
|
0d650f5c1e | ||
|
|
ee5a7183d9 | ||
|
|
0729518fc6 | ||
|
|
34ff2c2282 | ||
|
|
83817c11bb | ||
|
|
9df33f340c | ||
|
|
ce28ca17d1 | ||
|
|
10d9975f10 | ||
|
|
0454cab594 | ||
|
|
1eb46544fb | ||
|
|
5c07bca122 | ||
|
|
74f5b2d330 | ||
|
|
306f027948 | ||
|
|
8b0eb06e60 | ||
|
|
1dbe4629aa | ||
|
|
fb82167503 | ||
|
|
19bd1ea02b | ||
|
|
27685ba408 | ||
|
|
d9a08d1309 | ||
|
|
f0b98c348a | ||
|
|
c8b4646d89 | ||
|
|
2ce8d85f28 | ||
|
|
45bc4f51ba | ||
|
|
aee9c2e2da | ||
|
|
1ce814699d | ||
|
|
16771ceb8f | ||
|
|
1da45fef96 | ||
|
|
2806c4af7c | ||
|
|
45a0390a9f | ||
|
|
a66958c9d3 | ||
|
|
d3b35b8db2 | ||
|
|
89ecc3ff68 | ||
|
|
46f9b68028 | ||
|
|
2c599b83a1 | ||
|
|
daef17d647 | ||
|
|
7be597c9e8 | ||
|
|
67b3a7a2c3 | ||
|
|
9fdd99a7b4 | ||
|
|
b9ac4d4730 | ||
|
|
bca5b3a7d6 | ||
|
|
b32591f8bd | ||
|
|
73defa0289 | ||
|
|
650c9db322 | ||
|
|
4b495fd500 | ||
|
|
55e222d20f | ||
|
|
e425c7b917 | ||
|
|
7a56a5c082 | ||
|
|
3110e41921 | ||
|
|
0313effc53 | ||
|
|
b880b659f5 | ||
|
|
22139c0295 | ||
|
|
33c77540bd | ||
|
|
711cad9624 | ||
|
|
88d2454bbb | ||
|
|
b6eaea118b | ||
|
|
858f91f48b | ||
|
|
4e0cb8cd03 | ||
|
|
b1b2df0c10 | ||
|
|
23c4f48d0d | ||
|
|
3fbd9567c9 | ||
|
|
7b1e246b0e | ||
|
|
846d76a437 | ||
|
|
03011bc512 | ||
|
|
668e945f4c | ||
|
|
5791c39f93 | ||
|
|
48ff383ed2 | ||
|
|
011b98b3e0 | ||
|
|
ad3f65bf5b | ||
|
|
b4dedcd523 | ||
|
|
b604f7dc64 | ||
|
|
8b106abaf3 | ||
|
|
06e6934c35 | ||
|
|
32664cb140 | ||
|
|
f85611741e | ||
|
|
a35b88d906 | ||
|
|
9310acdff2 | ||
|
|
607d0390c6 | ||
|
|
eedcecdf4a | ||
|
|
15f3cdfb7b | ||
|
|
9d85d57cc4 | ||
|
|
96d074234a | ||
|
|
134f354e8e | ||
|
|
fbd17036db | ||
|
|
0fa2fd6dc1 | ||
|
|
8d03fcf77f | ||
|
|
1bd4304727 | ||
|
|
f85be55586 | ||
|
|
1e89742186 | ||
|
|
26d1224862 | ||
|
|
d3e3a334a3 | ||
|
|
f9266077b7 | ||
|
|
fdb318c1d7 | ||
|
|
91b12588db | ||
|
|
59fbca71b6 | ||
|
|
76ce0ce24f | ||
|
|
3f9c4cd548 | ||
|
|
61d120b6f0 | ||
|
|
f62e8ab1e0 | ||
|
|
6de43fdfb9 | ||
|
|
4733584056 | ||
|
|
cfb27797a8 | ||
|
|
63489606ef | ||
|
|
4b16a81116 | ||
|
|
1ce7c89177 | ||
|
|
b36422aac4 | ||
|
|
eeb3be1580 | ||
|
|
8ab8c9f899 | ||
|
|
a5ccf7622a | ||
|
|
2b08f9fb1b | ||
|
|
2a6afefa00 | ||
|
|
bb601305c6 | ||
|
|
1a31e9a96b | ||
|
|
a71395c7c8 | ||
|
|
a2ad870d2d | ||
|
|
dbcb6a104f | ||
|
|
2e763f1216 | ||
|
|
e01348e424 | ||
|
|
36c4272286 | ||
|
|
e45d13c630 | ||
|
|
fd8d670fd0 | ||
|
|
92c43a7129 | ||
|
|
94a7a7f1cb | ||
|
|
ef60172172 | ||
|
|
21f632368a | ||
|
|
001c8b5e1b | ||
|
|
9577415c6d | ||
|
|
9d287009e8 | ||
|
|
c32db55d3e | ||
|
|
32ee5efd59 | ||
|
|
60aec33d6f | ||
|
|
2ae89b7931 | ||
|
|
0a7be73d35 | ||
|
|
b266619975 | ||
|
|
0cb499c7b8 | ||
|
|
796f13af32 | ||
|
|
678f39767e | ||
|
|
4d9057a8d0 | ||
|
|
a195593eb7 | ||
|
|
ad86bfa6d2 | ||
|
|
1d11f39375 | ||
|
|
5057293d76 | ||
|
|
1c534853c8 | ||
|
|
88b5a85d64 | ||
|
|
f33679bb79 | ||
|
|
7135fcafc8 | ||
|
|
d55cc24a22 | ||
|
|
77a3da4040 | ||
|
|
e507ae340f | ||
|
|
ef20703b02 | ||
|
|
d8cb036198 | ||
|
|
c3b448271b | ||
|
|
d7115c8000 | ||
|
|
67766a5661 | ||
|
|
d32d668565 | ||
|
|
b7fb5f33c9 | ||
|
|
d3e44612e2 | ||
|
|
ea787e5c0b | ||
|
|
c7ddae76e4 | ||
|
|
809379e657 | ||
|
|
0be4487445 | ||
|
|
80b807db18 | ||
|
|
fd3841dd88 | ||
|
|
709f67fb01 | ||
|
|
4683d6b6e6 | ||
|
|
1b1fee36aa | ||
|
|
73c26bc792 | ||
|
|
562ae1caf1 | ||
|
|
e1937e6dfd | ||
|
|
88fe6df3ab | ||
|
|
3bfc4f2ebd | ||
|
|
5a77e7a59d | ||
|
|
b5a214b59f | ||
|
|
6b4a00c5cc | ||
|
|
7aaa4349f5 | ||
|
|
52b301019a | ||
|
|
8138a23edd | ||
|
|
5e2245cb8d | ||
|
|
8cbfb5dd67 | ||
|
|
023672e877 | ||
|
|
4db04ca4a6 | ||
|
|
5ecbdf4bf2 | ||
|
|
5697095a46 | ||
|
|
7724fc2c7b | ||
|
|
566511ec5f | ||
|
|
f0e23928f4 | ||
|
|
0efbda3f68 | ||
|
|
66350c88db | ||
|
|
617c89699a | ||
|
|
5f1a32384f | ||
|
|
49150bfa5e | ||
|
|
b1cf2ff445 | ||
|
|
0a946593e2 | ||
|
|
b0ec5102cc | ||
|
|
2b9f35b18f | ||
|
|
e73db1d7d2 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [panjf2000]
|
||||
patreon: panjf2000
|
||||
open_collective: panjf2000
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
121
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
121
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
name: Bug Report
|
||||
description: Oops!..., it's a bug.
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- panjf2000
|
||||
body:
|
||||
- type: markdown
|
||||
id: tips
|
||||
attributes:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Actions I've taken before I'm here
|
||||
description: Make sure you have tried the following ways but still the problem has not been solved.
|
||||
options:
|
||||
- label: I've thoroughly read the documentations on this issue but still have no clue.
|
||||
required: true
|
||||
- label: I've searched the current list of Github issues but didn't find any duplicate issues that have been solved.
|
||||
required: true
|
||||
- label: I've searched the internet with this issue, but haven't found anything helpful.
|
||||
required: true
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Describe (and illustrate) the bug that you encountered precisely.
|
||||
placeholder: Please describe what happened and how it happened, the more details you provide, the faster the bug gets fixed.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: major-version
|
||||
attributes:
|
||||
label: Major version of ants
|
||||
description: What major version of ants are you running?
|
||||
options:
|
||||
- v2
|
||||
- v1
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: specific-version
|
||||
attributes:
|
||||
label: Specific version of ants
|
||||
description: What's the specific version of ants?
|
||||
placeholder: "For example: v2.6.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
multiple: true
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- BSD
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS version
|
||||
description: What's the specific version of OS?
|
||||
placeholder: "Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: go-version
|
||||
attributes:
|
||||
label: Go version
|
||||
description: What's the specific version of Go?
|
||||
placeholder: "Run `go version` command to get the info, for example: go1.20.5 linux/amd64"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Reproducer
|
||||
description: Please provide the minimal code to reproduce the bug.
|
||||
render: go
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
attributes:
|
||||
label: How to Reproduce
|
||||
description: Steps to reproduce the result.
|
||||
placeholder: Tell us step by step how we can replicate this bug and what we should see in the end.
|
||||
value: |
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '....'
|
||||
2. Click on '....'
|
||||
3. Do '....'
|
||||
4. See '....'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: bug-in-latest-code
|
||||
attributes:
|
||||
label: Does this issue reproduce with the latest release?
|
||||
description: Is this bug still present in the latest version?
|
||||
options:
|
||||
- It can reproduce with the latest release
|
||||
- It gets fixed in the latest release
|
||||
- I haven't verified it with the latest release
|
||||
validations:
|
||||
required: true
|
||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,31 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: panjf2000
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Error messages/Trace logs**
|
||||
If applicable, add some logs to help explain your problem.
|
||||
|
||||
**System info (please complete the following information):**
|
||||
- OS: [e.g. linux, macOS]
|
||||
- Go Version [e.g. 1.12]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
60
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: Feature Request
|
||||
description: Propose an idea to make ants even better.
|
||||
title: "[Feature]: "
|
||||
labels: ["proposal", "enhancement"]
|
||||
assignees:
|
||||
- panjf2000
|
||||
body:
|
||||
- type: markdown
|
||||
id: tips
|
||||
attributes:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: textarea
|
||||
id: feature-request
|
||||
attributes:
|
||||
label: Description of new feature
|
||||
description: Make a concise but clear description about this new feature.
|
||||
placeholder: Describe this new feature with critical details.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature-scenario
|
||||
attributes:
|
||||
label: Scenarios for new feature
|
||||
description: Explain why you need this feature and what scenarios can benefit from it.
|
||||
placeholder: Please try to convince us that this new feature makes sense, also it will improve ants.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: breaking-changes
|
||||
attributes:
|
||||
label: Breaking changes or not?
|
||||
description: Is this new feature going to break the backward compatibility of the existing APIs?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Code snippets (optional)
|
||||
description: Illustrate this new feature with source code, what it looks like in code.
|
||||
render: go
|
||||
- type: textarea
|
||||
id: feature-alternative
|
||||
attributes:
|
||||
label: Alternatives for new feature
|
||||
description: Alternatives in your mind in case that the feature can't be done for some reasons.
|
||||
placeholder: A concise but clear description of any alternative solutions or features you had in mind.
|
||||
value: None.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context (optional)
|
||||
description: Any additional context about this new feature we should know.
|
||||
placeholder: What else should we know before we start discussing this new feature?
|
||||
value: None.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: proposal
|
||||
assignees: panjf2000
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
26
.github/ISSUE_TEMPLATE/question.yaml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/question.yaml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Question
|
||||
description: Ask questions about ants.
|
||||
title: "[Question]: "
|
||||
labels: ["question", "help wanted"]
|
||||
body:
|
||||
- type: markdown
|
||||
id: tips
|
||||
attributes:
|
||||
value: |
|
||||
## Before you go any further
|
||||
- Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.
|
||||
- Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Questions with details
|
||||
description: What do you want to know?
|
||||
placeholder: Describe your question with critical details here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Code snippets (optional)
|
||||
description: Illustrate your question with source code if needed.
|
||||
render: go
|
||||
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@ -1,13 +1,5 @@
|
||||
---
|
||||
name: Pull request
|
||||
about: Propose changes to the code
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for contributing to `ants`! Please fill this out to help us make the most of your pull request.
|
||||
Thank you for contributing to `ants`! Please fill this out to help us review your pull request more efficiently.
|
||||
|
||||
Was this change discussed in an issue first? That can help save time in case the change is not a good fit for the project. Not all pull requests get merged.
|
||||
|
||||
|
||||
106
.github/release-drafter.yml
vendored
Normal file
106
.github/release-drafter.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name-template: Ants v$RESOLVED_VERSION
|
||||
tag-template: v$RESOLVED_VERSION
|
||||
categories:
|
||||
- title: 🧨 Breaking changes
|
||||
labels:
|
||||
- breaking changes
|
||||
- title: 🚀 Features
|
||||
labels:
|
||||
- proposal
|
||||
- new feature
|
||||
- title: 🛩 Enhancements
|
||||
labels:
|
||||
- enhancement
|
||||
- optimization
|
||||
- title: 🐛 Bugfixes
|
||||
labels:
|
||||
- bug
|
||||
- title: 📚 Documentation
|
||||
labels:
|
||||
- doc
|
||||
- docs
|
||||
- title: 🗃 Misc
|
||||
labels:
|
||||
- chores
|
||||
change-template: '- $TITLE (#$NUMBER)'
|
||||
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- major
|
||||
minor:
|
||||
labels:
|
||||
- minor
|
||||
- new feature
|
||||
- proposal
|
||||
patch:
|
||||
labels:
|
||||
- patch
|
||||
- bug
|
||||
- dependencies
|
||||
default: patch
|
||||
autolabeler:
|
||||
- label: bug
|
||||
title:
|
||||
- /fix/i
|
||||
- /bug/i
|
||||
- /resolve/i
|
||||
- label: docs
|
||||
files:
|
||||
- '*.md'
|
||||
title:
|
||||
- /doc/i
|
||||
- /README/i
|
||||
- label: enhancement
|
||||
title:
|
||||
- /opt:/i
|
||||
- /refactor/i
|
||||
- /optimize/i
|
||||
- /improve/i
|
||||
- /update/i
|
||||
- /remove/i
|
||||
- /delete/i
|
||||
- label: optimization
|
||||
title:
|
||||
- /opt:/i
|
||||
- /refactor/i
|
||||
- /optimize/i
|
||||
- /improve/i
|
||||
- /update/i
|
||||
- /remove/i
|
||||
- /delete/i
|
||||
- label: new feature
|
||||
title:
|
||||
- /feat:/i
|
||||
- /feature/i
|
||||
- /implement/i
|
||||
- /add/i
|
||||
- /minor/i
|
||||
- label: dependencies
|
||||
title:
|
||||
- /dependencies/i
|
||||
- /upgrade/i
|
||||
- /bump up/i
|
||||
- label: chores
|
||||
title:
|
||||
- /chore/i
|
||||
- /misc/i
|
||||
- /cleanup/i
|
||||
- /clean up/i
|
||||
- label: major
|
||||
title:
|
||||
- /major:/i
|
||||
- label: minor
|
||||
title:
|
||||
- /minor:/i
|
||||
- label: patch
|
||||
title:
|
||||
- /patch:/i
|
||||
template: |
|
||||
## Changelogs
|
||||
|
||||
$CHANGES
|
||||
|
||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
||||
|
||||
Thanks to all these contributors: $CONTRIBUTORS for making this release possible.
|
||||
72
.github/workflows/codeql.yml
vendored
Normal file
72
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
name: "Code scanning"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- '!.github/workflows/codeql.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- '!.github/workflows/codeql.yml'
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# * * * * *
|
||||
- cron: '30 4 * * 6'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below).
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following
|
||||
# three lines and modify them (or add more) to build your code if your
|
||||
# project uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
19
.github/workflows/pull-request.yml
vendored
Normal file
19
.github/workflows/pull-request.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Check pull request target
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
check-branches:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check target branch
|
||||
run: |
|
||||
if [ ${{ github.head_ref }} != "dev" ]; then
|
||||
echo "Only pull requests from dev branch are only allowed to be merged into master branch."
|
||||
exit 1
|
||||
fi
|
||||
41
.github/workflows/release-drafter.yml
vendored
Normal file
41
.github/workflows/release-drafter.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
# branches to consider in the event; optional, defaults to all
|
||||
branches:
|
||||
- master
|
||||
# pull_request event is required only for autolabeler
|
||||
pull_request:
|
||||
# Only following types are handled by the action, but one can default to all as well
|
||||
types: [opened, reopened, synchronize]
|
||||
# pull_request_target event is required for autolabeler to support PRs from forks
|
||||
# pull_request_target:
|
||||
# types: [opened, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
# write permission is required to create a github release
|
||||
contents: write
|
||||
# write permission is required for autolabeler
|
||||
# otherwise, read permission is required at least
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# (Optional) GitHub Enterprise requires GHE_HOST variable set
|
||||
#- name: Set GHE_HOST
|
||||
# run: |
|
||||
# echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV
|
||||
|
||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
|
||||
# with:
|
||||
# config-name: my-config.yml
|
||||
# disable-autolabeler: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
48
.github/workflows/stale-bot.yml
vendored
Normal file
48
.github/workflows/stale-bot.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
name: Monitor inactive issues and PRs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
operations-per-run: 50
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 7
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-message: |
|
||||
This issue is marked as stale because it has been open for 30 days with no activity.
|
||||
|
||||
You should take one of the following actions:
|
||||
- Manually close this issue if it is no longer relevant
|
||||
- Comment if you have more information to share
|
||||
|
||||
This issue will be automatically closed in 7 days if no further activity occurs.
|
||||
close-issue-message: |
|
||||
This issue was closed because it has been inactive for 7 days since being marked as stale.
|
||||
|
||||
If you believe this is a false alarm, please leave a comment for it or open a new issue, you can also reopen this issue directly if you have permission.
|
||||
days-before-pr-stale: 21
|
||||
days-before-pr-close: 7
|
||||
stale-pr-label: 'stale'
|
||||
stale-pr-message: |
|
||||
This PR is marked as stale because it has been open for 21 days with no activity.
|
||||
|
||||
You should take one of the following actions:
|
||||
- Manually close this PR if it is no longer relevant
|
||||
- Push new commits or comment if you have more information to share
|
||||
|
||||
This PR will be automatically closed in 7 days if no further activity occurs.
|
||||
close-pr-message: |
|
||||
This PR was closed because it has been inactive for 7 days since being marked as stale.
|
||||
|
||||
If you believe this is a false alarm, feel free to reopen this PR or create a new one.
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
98
.github/workflows/test.yml
vendored
Normal file
98
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- 'examples/*'
|
||||
- '!.github/workflows/test.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.yml'
|
||||
- '**.yaml'
|
||||
- 'examples/*'
|
||||
- '!.github/workflows/test.yml'
|
||||
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOPROXY: "https://proxy.golang.org"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
name: Run golangci-lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
|
||||
- name: Setup and run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.62.2
|
||||
args: --timeout 5m -v -E gofumpt -E gocritic -E misspell -E revive -E godot
|
||||
test:
|
||||
needs: lint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [1.18, 1.23]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
name: Go ${{ matrix.go }} @ ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Print Go environment
|
||||
id: go-env
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
# Calculate the short SHA1 hash of the git commit
|
||||
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run unit tests and integrated tests
|
||||
run: go test -v -race -coverprofile="codecov.report" -covermode=atomic
|
||||
|
||||
- name: Upload code coverage report to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./codecov.report
|
||||
flags: unittests
|
||||
name: codecov-ants
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@
|
||||
# vendor/
|
||||
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@ -1,18 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
#- go test -cpu=16 -bench=. -benchmem=true -run=none ./...
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic -v
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
@ -6,7 +6,7 @@
|
||||
- Review existing issues and provide feedback or react to them.
|
||||
|
||||
## With pull requests:
|
||||
- Open your pull request against `master`.
|
||||
- Open your pull request against `dev`.
|
||||
- Open one pull request for only one feature/proposal, if you have several those, please put them into different PRs, whereas you are allowed to open one pull request with several bug-fixs.
|
||||
- Your pull request should have no more than two commits, if not, you should squash them.
|
||||
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||
|
||||
535
README.md
535
README.md
@ -1,323 +1,354 @@
|
||||
# ants
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/7496278/51748488-8efd2600-20e7-11e9-91f5-1c5b466dcca1.jpg"/>
|
||||
A goroutine pool for Go
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
|
||||
<b>A goroutine pool for Go</b>
|
||||
<br/><br/>
|
||||
<a title="Build Status" target="_blank" href="https://travis-ci.com/panjf2000/ants"><img src="https://img.shields.io/travis/com/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Ants on Sourcegraph" target="_blank" href="https://sourcegraph.com/github.com/panjf2000/ants?badge"><img src="https://sourcegraph.com/github.com/panjf2000/ants/-/badge.svg?style=flat-square"></a>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
|
||||
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
|
||||
<br/>
|
||||
<a title="" target="_blank" href="https://golangci.com/r/github.com/panjf2000/ants"><img src="https://golangci.com/badges/github.com/panjf2000/ants.svg"></a>
|
||||
<a title="Godoc for ants" target="_blank" href="https://godoc.org/github.com/panjf2000/ants"><img src="https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square"></a>
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/release/panjf2000/ants.svg?style=flat-square"></a>
|
||||
<a title="License" target="_blank" href="https://opensource.org/licenses/mit-license.php"><img src="https://img.shields.io/aur/license/pac?style=flat-square"></a>
|
||||
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square&logo=go" /></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
|
||||
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
|
||||
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
|
||||
</p>
|
||||
|
||||
# [[中文](README_ZH.md)]
|
||||
English | [中文](README_ZH.md)
|
||||
|
||||
## 📖 Introduction
|
||||
|
||||
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.
|
||||
|
||||
## Features:
|
||||
## 🚀 Features:
|
||||
|
||||
- Automatically managing and recycling a massive number of goroutines.
|
||||
- Periodically purging overdue goroutines.
|
||||
- Friendly interfaces: submitting tasks, getting the number of running goroutines, tuning capacity of pool dynamically, closing pool.
|
||||
- Handle panic gracefully to prevent programs from crash.
|
||||
- Efficient in memory usage and it even achieves higher performance than unlimited goroutines in golang.
|
||||
- Nonblocking mechanism.
|
||||
- Managing and recycling a massive number of goroutines automatically
|
||||
- Purging overdue goroutines periodically
|
||||
- Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool, etc.
|
||||
- Handle panic gracefully to prevent programs from crash
|
||||
- Efficient in memory usage and it may even achieve ***higher performance*** than unlimited goroutines in Go
|
||||
- Nonblocking mechanism
|
||||
- Preallocated memory (ring buffer, optional)
|
||||
|
||||
## Tested in the following Golang versions:
|
||||
## 💡 How `ants` works
|
||||
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
### Flow Diagram
|
||||
|
||||
<p align="center">
|
||||
<img width="1011" alt="ants-flowchart-en" src="https://user-images.githubusercontent.com/7496278/66396509-7b42e700-ea0c-11e9-8612-b71a4b734683.png">
|
||||
</p>
|
||||
|
||||
## How to install
|
||||
### Activity Diagrams
|
||||
|
||||
``` sh
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 🧰 How to install
|
||||
|
||||
### For `ants` v1
|
||||
|
||||
``` powershell
|
||||
go get -u github.com/panjf2000/ants
|
||||
```
|
||||
|
||||
## How to use
|
||||
Just take a imagination that your program starts a massive number of goroutines, resulting in a huge consumption of memory. To mitigate that kind of situation, all you need to do is to import `ants` package and submit all your tasks to a default pool with fixed capacity, activated when package `ants` is imported:
|
||||
### For `ants` v2 (with GO111MODULE=on)
|
||||
|
||||
```powershell
|
||||
go get -u github.com/panjf2000/ants/v2
|
||||
```
|
||||
|
||||
## 🛠 How to use
|
||||
Check out [the examples](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples) for basic usage.
|
||||
|
||||
### Functional options for pool
|
||||
|
||||
`ants.Options`contains all optional configurations of the ants pool, which allows you to customize the goroutine pool by invoking option functions to set up each configuration in `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` method.
|
||||
|
||||
Check out [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) and [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option) for more details.
|
||||
|
||||
### Customize pool capacity
|
||||
|
||||
`ants` supports customizing the capacity of the pool. You can call the `NewPool` method to instantiate a `Pool` with a given capacity, as follows:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var sum int32
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a function,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
}
|
||||
```
|
||||
|
||||
## Integrate with http server
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Param []byte
|
||||
Result chan []byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
pool, _ := ants.NewPoolWithFunc(100000, func(payload interface{}) {
|
||||
request, ok := payload.(*Request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
reverseParam := func(s []byte) []byte {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}(request.Param)
|
||||
|
||||
request.Result <- reverseParam
|
||||
})
|
||||
defer pool.Release()
|
||||
|
||||
http.HandleFunc("/reverse", func(w http.ResponseWriter, r *http.Request) {
|
||||
param, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "request error", http.StatusInternalServerError)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
request := &Request{Param: param, Result: make(chan []byte)}
|
||||
|
||||
// Throttle the requests traffic with ants pool. This process is asynchronous and
|
||||
// you can receive a result from the channel defined outside.
|
||||
if err := pool.Invoke(request); err != nil {
|
||||
http.Error(w, "throttle limit error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Write(<-request.Result)
|
||||
})
|
||||
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
## Functional options for ants pool
|
||||
|
||||
```go
|
||||
type Options struct {
|
||||
// ExpiryDuration set the expired time (second) of every worker.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicate whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
}
|
||||
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ants.Options`contains all optional configurations of ants pool, which allows you to customize the goroutine pool by invoking option functions to set up each configuration in `NewPool`/`NewPoolWithFunc`method.
|
||||
|
||||
## Customize limited pool
|
||||
|
||||
`ants` also supports customizing the capacity of pool. You can invoke the `NewPool` method to instantiate a pool with a given capacity, as following:
|
||||
|
||||
``` go
|
||||
// Set 10000 the size of goroutine pool
|
||||
p, _ := ants.NewPool(10000)
|
||||
```
|
||||
|
||||
## Submit tasks
|
||||
Tasks can be submitted by calling `ants.Submit(func())`
|
||||
### Submit tasks
|
||||
Tasks can be submitted by calling `ants.Submit`
|
||||
```go
|
||||
ants.Submit(func(){})
|
||||
```
|
||||
|
||||
## Tune pool capacity in runtime
|
||||
You can tune the capacity of `ants` pool in runtime with `Tune(int)`:
|
||||
### Tune pool capacity at runtime
|
||||
You can tune the capacity of `ants` pool at runtime with `ants.Tune`:
|
||||
|
||||
``` go
|
||||
pool.Tune(1000) // Tune its capacity to 1000
|
||||
pool.Tune(100000) // Tune its capacity to 100000
|
||||
```
|
||||
|
||||
Don't worry about the synchronous problems in this case, the method here is thread-safe (or should be called goroutine-safe).
|
||||
Don't worry about the contention problems in this case, the method here is thread-safe (or should be called goroutine-safe).
|
||||
|
||||
## Pre-malloc goroutine queue in pool
|
||||
### Pre-malloc goroutine queue in pool
|
||||
|
||||
`ants` allows you to pre-allocate memory of goroutine queue in pool, which may get a performance enhancement under some special certain circumstances such as the scenario that requires a pool with ultra-large capacity, meanwhile each task in goroutine lasts for a long time, in this case, pre-mallocing will reduce a lot of costs when re-slicing goroutine queue.
|
||||
`ants` allows you to pre-allocate the memory of the goroutine queue in the pool, which may get a performance enhancement under some special certain circumstances such as the scenario that requires a pool with ultra-large capacity, meanwhile, each task in goroutine lasts for a long time, in this case, pre-mallocing will reduce a lot of memory allocation in goroutine queue.
|
||||
|
||||
```go
|
||||
// ants will pre-malloc the whole capacity of pool when you invoke this method
|
||||
// ants will pre-malloc the whole capacity of pool when calling ants.NewPool.
|
||||
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
```
|
||||
|
||||
## Release Pool
|
||||
### Release pool
|
||||
|
||||
```go
|
||||
pool.Release()
|
||||
```
|
||||
|
||||
## About sequence
|
||||
or
|
||||
|
||||
```go
|
||||
pool.ReleaseTimeout(time.Second * 3)
|
||||
```
|
||||
|
||||
### Reboot pool
|
||||
|
||||
```go
|
||||
// A pool that has been released can be still used after calling the Reboot().
|
||||
pool.Reboot()
|
||||
```
|
||||
|
||||
## ⚙️ About sequence
|
||||
|
||||
All tasks submitted to `ants` pool will not be guaranteed to be addressed in order, because those tasks scatter among a series of concurrent workers, thus those tasks would be executed concurrently.
|
||||
|
||||
## Benchmarks
|
||||
## 👏 Contributors
|
||||
|
||||
<div align="center"><img src="https://user-images.githubusercontent.com/7496278/51515466-c7ce9e00-1e4e-11e9-89c4-bd3785b3c667.png"/></div>
|
||||
In this benchmark-picture, the first and second benchmarks performed test cases with 1M tasks and the rest of benchmarks performed test cases with 10M tasks, both in unlimited goroutines and `ants` pool, and the capacity of this `ants` goroutine-pool was limited to 50K.
|
||||
Please read our [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `ants`!
|
||||
|
||||
- BenchmarkGoroutine-4 represents the benchmarks with unlimited goroutines in golang.
|
||||
<a href="https://github.com/panjf2000/ants/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=panjf2000/ants" />
|
||||
</a>
|
||||
|
||||
- BenchmarkPoolGroutine-4 represents the benchmarks with a `ants` pool.
|
||||
## 📄 License
|
||||
|
||||
### Benchmarks with Pool
|
||||
The source code in `ants` is available under the [MIT License](/LICENSE).
|
||||
|
||||

|
||||
## 📚 Relevant Articles
|
||||
|
||||
In above benchmark picture, the first and second benchmarks performed test cases with 1M tasks and the rest of benchmarks performed test cases with 10M tasks, both in unlimited goroutines and `ants` pool, and the capacity of this `ants` goroutine-pool was limited to 50K.
|
||||
- [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
|
||||
- [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
|
||||
- [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
|
||||
- [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)
|
||||
|
||||
**As you can see, `ants` performs 2 times faster than goroutines without pool (10M tasks) and it only consumes half the memory comparing with goroutines without pool. (both in 1M and 10M tasks)**
|
||||
## 🖥 Use cases
|
||||
|
||||
### Benchmarks with PoolWithFunc
|
||||
### business corporations & open-source organizations
|
||||
|
||||

|
||||
Trusted by the following corporations/organizations.
|
||||
|
||||
### Throughput (it is suitable for scenarios where tasks are submitted asynchronously without waiting for the final results)
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencent.com/">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytedance.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tieba.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weibo.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencentmusic.com/en-us/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent-music-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.futuhk.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.shopify.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.wechat.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.360.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.huaweicloud.com/intl/en-us/" target="_blank">
|
||||
<img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/%E7%BB%84%E4%BB%B6%E9%AA%8C%E8%AF%81/pep-common-header/logo-en.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.matrixorigin.io/" target="_blank">
|
||||
<img src="https://www.matrixorigin.io/_next/static/media/logo-light-en.42553c69.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://adguard-dns.io/" target="_blank">
|
||||
<img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://bk.tencent.com/" target="_blank">
|
||||
<img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.alibabacloud.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-intl-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.zuoyebang.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.antgroup.com/en/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://zilliz.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://amap.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.apache.org/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.coze.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/coze-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
#### 100K tasks
|
||||
If you're also using `ants` in production, please help us enrich this list by opening a pull request.
|
||||
|
||||

|
||||
### open-source software
|
||||
|
||||
#### 1M tasks
|
||||
The open-source projects below do concurrent programming with the help of `ants`.
|
||||
|
||||

|
||||
- [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
|
||||
- [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications.
|
||||
- [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
|
||||
- [TDengine](https://github.com/taosdata/TDengine): TDengine is an open source, high-performance, cloud native time-series database optimized for Internet of Things (IoT), Connected Cars, and Industrial IoT.
|
||||
- [siyuan](https://github.com/siyuan-note/siyuan): SiYuan is a local-first personal knowledge management system that supports complete offline use, as well as end-to-end encrypted synchronization.
|
||||
- [BillionMail](https://github.com/aaPanel/BillionMail): A future open-source Mail server, Email marketing platform designed to help businesses and individuals manage their email campaigns with ease.
|
||||
- [WeKnora](https://github.com/Tencent/WeKnora): An LLM-powered framework designed for deep document understanding and semantic retrieval, especially for handling complex, heterogeneous documents.
|
||||
- [coze-loop](https://github.com/coze-dev/coze-loop): A developer-oriented, platform-level solution focused on the development and operation of AI agents.
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
|
||||
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
|
||||
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
|
||||
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
|
||||
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
|
||||
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
|
||||
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne is a future-oriented hyper-converged cloud and edge native DBMS that supports transactional, analytical, and streaming workloads with a simplified and distributed database engine, across multiple data centers, clouds, edges and other heterogeneous infrastructures.
|
||||
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): BlueKing Container Service (BCS, same below) is a container management and orchestration platform for the micro-services under the BlueKing ecosystem.
|
||||
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
|
||||
- [openGemini](https://github.com/openGemini/openGemini): openGemini is an open-source,cloud-native time-series database(TSDB) that can be widely used in IoT, Internet of Vehicles(IoV), O&M monitoring, and industrial Internet scenarios.
|
||||
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
|
||||
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
|
||||
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
|
||||
- [trpc-go](https://github.com/trpc-group/trpc-go): A pluggable, high-performance RPC framework written in Golang.
|
||||
- [motan-go](https://github.com/weibocom/motan-go): Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services. motan-go is the golang implementation of Motan.
|
||||
|
||||
#### 10M tasks
|
||||
#### All use cases:
|
||||
|
||||

|
||||
- [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)
|
||||
|
||||
### Performance Summary
|
||||
- [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)
|
||||
|
||||

|
||||
If you have `ants` integrated into projects, feel free to open a pull request refreshing this list of use cases.
|
||||
|
||||
**In conclusion, `ants` performs 2~6 times faster than goroutines without a pool and the memory consumption is reduced by 10 to 20 times.**
|
||||
## 🔋 JetBrains OS licenses
|
||||
|
||||
# License
|
||||
`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.
|
||||
|
||||
Source code in `gnet` is available under the MIT [License](/LICENSE).
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>
|
||||
|
||||
# Relevant Articles
|
||||
## ☕️ Buy me a coffee
|
||||
|
||||
- [Goroutine 并发调度模型深度解析之手撸一个高性能协程池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
|
||||
> 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.
|
||||
|
||||
# Users of ants (please feel free to add your projects here ~~)
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://buymeacoffee.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="By me coffee" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://www.patreon.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://opencollective.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[](https://github.com/panjf2000/gnet)
|
||||
## 🔋 Sponsorship
|
||||
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
532
README_ZH.md
532
README_ZH.md
@ -1,249 +1,92 @@
|
||||
# ants
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/7496278/51748488-8efd2600-20e7-11e9-91f5-1c5b466dcca1.jpg"/>
|
||||
A goroutine pool for Go
|
||||
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
|
||||
<b>Go 语言的 goroutine 池</b>
|
||||
<br/><br/>
|
||||
<a title="Build Status" target="_blank" href="https://travis-ci.com/panjf2000/ants"><img src="https://img.shields.io/travis/com/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square"></a>
|
||||
<a title="Ants on Sourcegraph" target="_blank" href="https://sourcegraph.com/github.com/panjf2000/ants?badge"><img src="https://sourcegraph.com/github.com/panjf2000/ants/-/badge.svg?style=flat-square"></a>
|
||||
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
|
||||
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
|
||||
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
|
||||
<br/>
|
||||
<a title="" target="_blank" href="https://golangci.com/r/github.com/panjf2000/ants"><img src="https://golangci.com/badges/github.com/panjf2000/ants.svg"></a>
|
||||
<a title="Godoc for ants" target="_blank" href="https://godoc.org/github.com/panjf2000/ants"><img src="https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square"></a>
|
||||
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/release/panjf2000/ants.svg?style=flat-square"></a>
|
||||
<a title="License" target="_blank" href="https://opensource.org/licenses/mit-license.php"><img src="https://img.shields.io/aur/license/pac?style=flat-square"></a>
|
||||
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square&logo=go" /></a>
|
||||
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
|
||||
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
|
||||
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
|
||||
</p>
|
||||
|
||||
# [[英文](README.md)]
|
||||
[英文](README.md) | 中文
|
||||
|
||||
`ants`是一个高性能的协程池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制协程数量,复用资源,达到更高效执行任务的效果。
|
||||
## 📖 简介
|
||||
|
||||
## 功能:
|
||||
`ants` 是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。
|
||||
|
||||
- 实现了自动调度并发的 goroutine,复用 goroutine
|
||||
- 定时清理过期的 goroutine,进一步节省资源
|
||||
- 提供了友好的接口:任务提交、获取运行中的协程数量、动态调整协程池大小
|
||||
## 🚀 功能:
|
||||
|
||||
- 自动调度海量的 goroutines,复用 goroutines
|
||||
- 定期清理过期的 goroutines,进一步节省资源
|
||||
- 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等
|
||||
- 优雅处理 panic,防止程序崩溃
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下比原生 goroutine 并发具有更高的性能
|
||||
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能***
|
||||
- 非阻塞机制
|
||||
- 预分配内存 (环形队列,可选)
|
||||
|
||||
## 目前测试通过的Golang版本:
|
||||
## 💡 `ants` 是如何运行的
|
||||
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
### 流程图
|
||||
|
||||
<p align="center">
|
||||
<img width="845" alt="ants-flowchart-cn" src="https://user-images.githubusercontent.com/7496278/66396519-7ed66e00-ea0c-11e9-9c1a-5ca54bbd61eb.png">
|
||||
</p>
|
||||
|
||||
## 安装
|
||||
### 动态图
|
||||
|
||||
``` sh
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 🧰 安装
|
||||
|
||||
### 使用 `ants` v1 版本:
|
||||
|
||||
``` powershell
|
||||
go get -u github.com/panjf2000/ants
|
||||
```
|
||||
|
||||
## 使用
|
||||
写 go 并发程序的时候如果程序会启动大量的 goroutine ,势必会消耗大量的系统资源(内存,CPU),通过使用 `ants`,可以实例化一个协程池,复用 goroutine ,节省资源,提升性能:
|
||||
### 使用 `ants` v2 版本 (开启 GO111MODULE=on):
|
||||
|
||||
```powershell
|
||||
go get -u github.com/panjf2000/ants/v2
|
||||
```
|
||||
|
||||
## 🛠 使用
|
||||
基本的使用请查看[示例](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples).
|
||||
|
||||
### Pool 配置
|
||||
|
||||
通过在调用 `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` 之时使用各种 optional function,可以设置 `ants.Options` 中各个配置项的值,然后用它来定制化 goroutine pool。
|
||||
|
||||
更多细节请查看 [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) 和 [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option)
|
||||
|
||||
|
||||
### 自定义 pool 容量
|
||||
`ants` 支持实例化使用者自己的一个 Pool,指定具体的 pool 容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 `Pool`,如下:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var sum int32
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a function,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
}
|
||||
```
|
||||
|
||||
## 与 http server 集成
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Param []byte
|
||||
Result chan []byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
pool, _ := ants.NewPoolWithFunc(100000, func(payload interface{}) {
|
||||
request, ok := payload.(*Request)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
reverseParam := func(s []byte) []byte {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}(request.Param)
|
||||
|
||||
request.Result <- reverseParam
|
||||
})
|
||||
defer pool.Release()
|
||||
|
||||
http.HandleFunc("/reverse", func(w http.ResponseWriter, r *http.Request) {
|
||||
param, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "request error", http.StatusInternalServerError)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
request := &Request{Param: param, Result: make(chan []byte)}
|
||||
|
||||
// Throttle the requests traffic with ants pool. This process is asynchronous and
|
||||
// you can receive a result from the channel defined outside.
|
||||
if err := pool.Invoke(request); err != nil {
|
||||
http.Error(w, "throttle limit error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Write(<-request.Result)
|
||||
})
|
||||
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
## Pool 配置
|
||||
|
||||
```go
|
||||
type Options struct {
|
||||
// ExpiryDuration set the expired time (second) of every worker.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicate whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
}
|
||||
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过在调用`NewPool`/`NewPoolWithFunc`之时使用各种 optional function,可以设置`ants.Options`中各个配置项的值,然后用它来定制化 goroutine pool.
|
||||
|
||||
|
||||
## 自定义池
|
||||
`ants`支持实例化使用者自己的一个 Pool ,指定具体的池容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 Pool ,如下:
|
||||
|
||||
``` go
|
||||
// Set 10000 the size of goroutine pool
|
||||
p, _ := ants.NewPool(10000)
|
||||
```
|
||||
|
||||
## 任务提交
|
||||
### 任务提交
|
||||
|
||||
提交任务通过调用 `ants.Submit(func())`方法:
|
||||
提交任务通过调用 `ants.Submit` 方法:
|
||||
```go
|
||||
ants.Submit(func(){})
|
||||
```
|
||||
|
||||
## 动态调整协程池容量
|
||||
需要动态调整协程池容量可以通过调用`Tune(int)`:
|
||||
### 动态调整 goroutine 池容量
|
||||
需要动态调整 pool 容量可以通过调用 `ants.Tune`:
|
||||
|
||||
``` go
|
||||
pool.Tune(1000) // Tune its capacity to 1000
|
||||
@ -252,72 +95,259 @@ pool.Tune(100000) // Tune its capacity to 100000
|
||||
|
||||
该方法是线程安全的。
|
||||
|
||||
## 预先分配 goroutine 队列内存
|
||||
### 预先分配 goroutine 队列内存
|
||||
|
||||
`ants`允许你预先把整个池的容量分配内存, 这个功能可以在某些特定的场景下提高协程池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少 re-slice 时的复制内存损耗。
|
||||
`ants` 支持预先为 pool 分配容量的内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。
|
||||
|
||||
```go
|
||||
// ants will pre-malloc the whole capacity of pool when you invoke this function
|
||||
// 提前分配的 pool 容量的内存空间
|
||||
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 销毁协程池
|
||||
### 释放 Pool
|
||||
|
||||
```go
|
||||
pool.Release()
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
或者
|
||||
|
||||
<div align="center"><img src="https://user-images.githubusercontent.com/7496278/51515466-c7ce9e00-1e4e-11e9-89c4-bd3785b3c667.png"/></div>
|
||||
上图中的前两个 benchmark 测试结果是基于100w 任务量的条件,剩下的几个是基于 1000w 任务量的测试结果,`ants` 的默认池容量是 5w。
|
||||
```go
|
||||
pool.ReleaseTimeout(time.Second * 3)
|
||||
```
|
||||
|
||||
- BenchmarkGoroutine-4 代表原生 goroutine
|
||||
### 重启 Pool
|
||||
|
||||
- BenchmarkPoolGroutine-4 代表使用协程池 `ants`
|
||||
```go
|
||||
// 只要调用 Reboot() 方法,就可以重新激活一个之前已经被销毁掉的池,并且投入使用。
|
||||
pool.Reboot()
|
||||
```
|
||||
|
||||
### Benchmarks with Pool
|
||||
## ⚙️ 关于任务执行顺序
|
||||
|
||||

|
||||
`ants` 并不保证提交的任务被执行的顺序,执行的顺序也不是和提交的顺序保持一致,因为在 `ants` 是并发地处理所有提交的任务,提交的任务会被分派到正在并发运行的 workers 上去,因此那些任务将会被并发且无序地被执行。
|
||||
|
||||
**这里为了模拟大规模 goroutine 的场景,两次测试的并发次数分别是 100w 和 1000w,前两个测试分别是执行 100w 个并发任务不使用 Pool 和使用了`ants`的 Goroutine Pool 的性能,后两个则是 1000w 个任务下的表现,可以直观的看出在执行速度和内存使用上,`ants`的 Pool 都占有明显的优势。100w 的任务量,使用`ants`,执行速度与原生 goroutine 相当甚至略快,但只实际使用了不到 5w 个 goroutine 完成了全部任务,且内存消耗仅为原生并发的 40%;而当任务量达到 1000w,优势则更加明显了:用了 70w 左右的 goroutine 完成全部任务,执行速度比原生 goroutine 提高了 100%,且内存消耗依旧保持在不使用 Pool 的 40% 左右。**
|
||||
## 👏 贡献者
|
||||
|
||||
### Benchmarks with PoolWithFunc
|
||||
请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `ants` 贡献过代码的开发者!
|
||||
|
||||

|
||||
<a href="https://github.com/panjf2000/ants/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=panjf2000/ants" />
|
||||
</a>
|
||||
|
||||
**因为`PoolWithFunc`这个 Pool 只绑定一个任务函数,也即所有任务都是运行同一个函数,所以相较于`Pool`对原生 goroutine 在执行速度和内存消耗的优势更大,上面的结果可以看出,执行速度可以达到原生 goroutine 的 300%,而内存消耗的优势已经达到了两位数的差距,原生 goroutine 的内存消耗达到了`ants`的35倍且原生 goroutine 的每次执行的内存分配次数也达到了`ants`45倍,1000w 的任务量,`ants`的初始分配容量是 5w,因此它完成了所有的任务依旧只使用了 5w 个 goroutine!事实上,`ants`的 Goroutine Pool 的容量是可以自定义的,也就是说使用者可以根据不同场景对这个参数进行调优直至达到最高性能。**
|
||||
## 📄 证书
|
||||
|
||||
### 吞吐量测试(适用于那种只管提交异步任务而无须关心结果的场景)
|
||||
`ants` 的源码允许用户在遵循 [MIT 开源证书](/LICENSE) 规则的前提下使用。
|
||||
|
||||
#### 10w 任务量
|
||||
## 📚 相关文章
|
||||
|
||||

|
||||
- [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
|
||||
- [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
|
||||
- [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
|
||||
- [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)
|
||||
|
||||
#### 100w 任务量
|
||||
## 🖥 用户案例
|
||||
|
||||

|
||||
### 商业公司和开源组织
|
||||
|
||||
#### 1000w 任务量
|
||||
以下公司/组织在生产环境上使用了 `ants`。
|
||||
|
||||

|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencent.com/">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytedance.com/zh/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tieba.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weibo.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.tencentmusic.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/tencent-music-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.futuhk.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.shopify.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://weixin.qq.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.baidu.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.360.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.huaweicloud.com/" target="_blank">
|
||||
<img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/wangxue/header/logo.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://matrixorigin.cn/" target="_blank">
|
||||
<img src="https://matrixorigin.cn/_next/static/media/logo-light-zh.a2a8f3c0.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://adguard-dns.io/" target="_blank">
|
||||
<img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://bk.tencent.com/" target="_blank">
|
||||
<img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://cn.aliyun.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/aliyun-cn-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.zuoyebang.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.antgroup.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://zilliz.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://amap.com/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.apache.org/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.coze.cn/" target="_blank">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/coze-logo-cn.png" width="250" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。
|
||||
|
||||
### 性能小结
|
||||
### 开源软件
|
||||
|
||||

|
||||
这些开源项目借助 `ants` 进行并发编程。
|
||||
|
||||
**从该 demo 测试吞吐性能对比可以看出,使用`ants`的吞吐性能相较于原生 goroutine 可以保持在 2-6 倍的性能压制,而内存消耗则可以达到 10-20 倍的节省优势。**
|
||||
- [gnet](https://github.com/panjf2000/gnet): gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。
|
||||
- [milvus](https://github.com/milvus-io/milvus): 一个高度灵活、可靠且速度极快的云原生开源向量数据库。
|
||||
- [nps](https://github.com/ehang-io/nps): 一款轻量级、高性能、功能强大的内网穿透代理服务器。
|
||||
- [TDengine](https://github.com/taosdata/TDengine): TDengine 是一款开源、高性能、云原生的时序数据库 (Time-Series Database, TSDB)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。
|
||||
- [siyuan](https://github.com/siyuan-note/siyuan): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
|
||||
- [BillionMail](https://github.com/aaPanel/BillionMail): BillionMail 是一个未来的开源邮件服务器和电子邮件营销平台,旨在帮助企业和个人轻松管理他们的电子邮件营销活动。
|
||||
- [WeKnora](https://github.com/Tencent/WeKnora): 一款基于大语言模型(LLM)的文档理解与语义检索框架,专为结构复杂、内容异构的文档场景而打造。
|
||||
- [coze-loop](https://github.com/coze-dev/coze-loop): Coze Loop 是一个面向开发者,专注于 AI Agent 开发与运维的平台级解决方案。
|
||||
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
|
||||
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
|
||||
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
|
||||
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
|
||||
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
|
||||
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
|
||||
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
|
||||
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne 是一款面向未来的超融合异构云原生数据库,通过超融合数据引擎支持事务/分析/流处理等混合工作负载,通过异构云原生架构支持跨机房协同/多地协同/云边协同。简化开发运维,消简数据碎片,打破数据的系统、位置和创新边界。
|
||||
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): 蓝鲸容器管理平台(Blueking Container Service)定位于打造云原生技术和业务实际应用场景之间的桥梁;聚焦于复杂应用场景的容器化部署技术方案的研发、整合和产品化;致力于为游戏等复杂应用提供一站式、低门槛的容器编排和服务治理服务。
|
||||
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
|
||||
- [openGemini](https://github.com/openGemini/openGemini): openGemini 是华为云开源的一款云原生分布式时序数据库,可广泛应用于物联网、车联网、运维监控、工业互联网等业务场景,具备卓越的读写性能和高效的数据分析能力,采用类SQL查询语言,无第三方软件依赖、安装简单、部署灵活、运维便捷。
|
||||
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
|
||||
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
|
||||
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
|
||||
- [trpc-go](https://github.com/trpc-group/trpc-go): 一个 Go 实现的可插拔的高性能 RPC 框架。
|
||||
- [motan-go](https://github.com/weibocom/motan-go): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。
|
||||
|
||||
# 证书
|
||||
#### 所有案例:
|
||||
|
||||
`gnet` 的源码允许用户在遵循 MIT [开源证书](/LICENSE) 规则的前提下使用。
|
||||
- [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)
|
||||
|
||||
# 相关文章
|
||||
- [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)
|
||||
|
||||
- [Goroutine 并发调度模型深度解析之手撸一个高性能协程池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
|
||||
如果你的项目也在使用 `ants`,欢迎给我提 Pull Request 来更新这份用户案例列表。
|
||||
|
||||
# 正在使用 ants 的库列表(欢迎补充 ~~)
|
||||
## 🔋 JetBrains 开源证书支持
|
||||
|
||||
[](https://github.com/panjf2000/gnet)
|
||||
`ants` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>
|
||||
|
||||
## ☕️ 打赏
|
||||
|
||||
> 当您通过以下方式进行捐赠时,请务必留下姓名、GitHub 账号或其他社交媒体账号,以便我将其添加到捐赠者名单中,以表谢意。
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://buymeacoffee.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="By me coffee" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://www.patreon.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://opencollective.com/panjf2000">
|
||||
<img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔋 赞助商
|
||||
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
518
ants.go
518
ants.go
@ -20,33 +20,46 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Package ants implements an efficient and reliable goroutine pool for Go.
|
||||
//
|
||||
// With ants, Go applications are able to limit the number of active goroutines,
|
||||
// recycle goroutines efficiently, and reduce the memory footprint significantly.
|
||||
// Package ants is extremely useful in the scenarios where a massive number of
|
||||
// goroutines are created and destroyed frequently, such as highly-concurrent
|
||||
// batch processing systems, HTTP servers, services of asynchronous tasks, etc.
|
||||
package ants
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
syncx "github.com/panjf2000/ants/v2/pkg/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// DEFAULT_ANTS_POOL_SIZE is the default capacity for a default goroutine pool.
|
||||
DEFAULT_ANTS_POOL_SIZE = math.MaxInt32
|
||||
// DefaultAntsPoolSize is the default capacity for a default goroutine pool.
|
||||
DefaultAntsPoolSize = math.MaxInt32
|
||||
|
||||
// DEFAULT_CLEAN_INTERVAL_TIME is the interval time to clean up goroutines.
|
||||
DEFAULT_CLEAN_INTERVAL_TIME = 1
|
||||
// DefaultCleanIntervalTime is the interval time to clean up goroutines.
|
||||
DefaultCleanIntervalTime = time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
// OPENED represents that the pool is opened.
|
||||
OPENED = iota
|
||||
|
||||
// CLOSED represents that the pool is closed.
|
||||
CLOSED = 1
|
||||
CLOSED
|
||||
)
|
||||
|
||||
var (
|
||||
// Error types for the Ants API.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// ErrInvalidPoolSize will be returned when setting a negative number as pool capacity.
|
||||
ErrInvalidPoolSize = errors.New("invalid size for pool")
|
||||
|
||||
// ErrLackPoolFunc will be returned when invokers don't provide function for pool.
|
||||
ErrLackPoolFunc = errors.New("must provide function for pool")
|
||||
|
||||
@ -58,87 +71,44 @@ var (
|
||||
|
||||
// ErrPoolOverload will be returned when the pool is full and no workers available.
|
||||
ErrPoolOverload = errors.New("too many goroutines blocked on submit or Nonblocking is set")
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// ErrInvalidPreAllocSize will be returned when trying to set up a negative capacity under PreAlloc mode.
|
||||
ErrInvalidPreAllocSize = errors.New("can not set up a negative capacity under PreAlloc mode")
|
||||
|
||||
// ErrTimeout will be returned after the operations timed out.
|
||||
ErrTimeout = errors.New("operation timed out")
|
||||
|
||||
// ErrInvalidPoolIndex will be returned when trying to retrieve a pool with an invalid index.
|
||||
ErrInvalidPoolIndex = errors.New("invalid pool index")
|
||||
|
||||
// ErrInvalidLoadBalancingStrategy will be returned when trying to create a MultiPool with an invalid load-balancing strategy.
|
||||
ErrInvalidLoadBalancingStrategy = errors.New("invalid load-balancing strategy")
|
||||
|
||||
// ErrInvalidMultiPoolSize will be returned when trying to create a MultiPool with an invalid size.
|
||||
ErrInvalidMultiPoolSize = errors.New("invalid size for multiple pool")
|
||||
|
||||
// workerChanCap determines whether the channel of a worker should be a buffered channel
|
||||
// to get the best performance. Inspired by fasthttp at https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139
|
||||
// to get the best performance. Inspired by fasthttp at
|
||||
// https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139
|
||||
workerChanCap = func() int {
|
||||
// Use blocking workerChan if GOMAXPROCS=1.
|
||||
// This immediately switches Serve to WorkerFunc, which results
|
||||
// in higher performance (under go1.5 at least).
|
||||
// Use blocking channel if GOMAXPROCS=1.
|
||||
// This switches context from sender to receiver immediately,
|
||||
// which results in higher performance (under go1.5 at least).
|
||||
if runtime.GOMAXPROCS(0) == 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Use non-blocking workerChan if GOMAXPROCS>1,
|
||||
// since otherwise the Serve caller (Acceptor) may lag accepting
|
||||
// new connections if WorkerFunc is CPU-bound.
|
||||
// since otherwise the sender might be dragged down if the receiver is CPU-bound.
|
||||
return 1
|
||||
}()
|
||||
|
||||
// Init a instance pool when importing ants.
|
||||
defaultAntsPool, _ = NewPool(DEFAULT_ANTS_POOL_SIZE)
|
||||
defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds))
|
||||
|
||||
// Init an instance pool when importing ants.
|
||||
defaultAntsPool, _ = NewPool(DefaultAntsPoolSize)
|
||||
)
|
||||
|
||||
type Option func(opts *Options)
|
||||
|
||||
type Options struct {
|
||||
// ExpiryDuration set the expired time (second) of every worker.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicate whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
PanicHandler func(interface{})
|
||||
}
|
||||
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
func WithPanicHandler(panicHandler func(interface{})) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
|
||||
// Submit submits a task to pool.
|
||||
func Submit(task func()) error {
|
||||
return defaultAntsPool.Submit(task)
|
||||
@ -163,3 +133,393 @@ func Free() int {
|
||||
func Release() {
|
||||
defaultAntsPool.Release()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func ReleaseTimeout(timeout time.Duration) error {
|
||||
return defaultAntsPool.ReleaseTimeout(timeout)
|
||||
}
|
||||
|
||||
// Reboot reboots the default pool.
|
||||
func Reboot() {
|
||||
defaultAntsPool.Reboot()
|
||||
}
|
||||
|
||||
// Logger is used for logging formatted messages.
|
||||
type Logger interface {
|
||||
// Printf must have the same semantics as log.Printf.
|
||||
Printf(format string, args ...any)
|
||||
}
|
||||
|
||||
// poolCommon contains all common fields for other sophisticated pools.
|
||||
type poolCommon struct {
|
||||
// capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to
|
||||
// avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool
|
||||
// which submits a new task to the same pool.
|
||||
capacity int32
|
||||
|
||||
// running is the number of the currently running goroutines.
|
||||
running int32
|
||||
|
||||
// lock for protecting the worker queue.
|
||||
lock sync.Locker
|
||||
|
||||
// workers is a slice that store the available workers.
|
||||
workers workerQueue
|
||||
|
||||
// state is used to notice the pool to closed itself.
|
||||
state int32
|
||||
|
||||
// cond for waiting to get an idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// done is used to indicate that all workers are done.
|
||||
allDone chan struct{}
|
||||
// once is used to make sure the pool is closed just once.
|
||||
once *sync.Once
|
||||
|
||||
// workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock
|
||||
waiting int32
|
||||
|
||||
purgeDone int32
|
||||
purgeCtx context.Context
|
||||
stopPurge context.CancelFunc
|
||||
|
||||
ticktockDone int32
|
||||
ticktockCtx context.Context
|
||||
stopTicktock context.CancelFunc
|
||||
|
||||
now atomic.Value
|
||||
|
||||
options *Options
|
||||
}
|
||||
|
||||
func newPool(size int, options ...Option) (*poolCommon, error) {
|
||||
if size <= 0 {
|
||||
size = -1
|
||||
}
|
||||
|
||||
opts := loadOptions(options...)
|
||||
|
||||
if !opts.DisablePurge {
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = DefaultCleanIntervalTime
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = defaultLogger
|
||||
}
|
||||
|
||||
p := &poolCommon{
|
||||
capacity: int32(size),
|
||||
allDone: make(chan struct{}),
|
||||
lock: syncx.NewSpinLock(),
|
||||
once: &sync.Once{},
|
||||
options: opts,
|
||||
}
|
||||
if p.options.PreAlloc {
|
||||
if size == -1 {
|
||||
return nil, ErrInvalidPreAllocSize
|
||||
}
|
||||
p.workers = newWorkerQueue(queueTypeLoopQueue, size)
|
||||
} else {
|
||||
p.workers = newWorkerQueue(queueTypeStack, 0)
|
||||
}
|
||||
|
||||
p.cond = sync.NewCond(p.lock)
|
||||
|
||||
p.goPurge()
|
||||
p.goTicktock()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
|
||||
func (p *poolCommon) purgeStaleWorkers() {
|
||||
ticker := time.NewTicker(p.options.ExpiryDuration)
|
||||
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.purgeDone, 1)
|
||||
}()
|
||||
|
||||
purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-purgeCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
var isDormant bool
|
||||
p.lock.Lock()
|
||||
staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
|
||||
n := p.Running()
|
||||
isDormant = n == 0 || n == len(staleWorkers)
|
||||
p.lock.Unlock()
|
||||
|
||||
// Clean up the stale workers.
|
||||
for i := range staleWorkers {
|
||||
staleWorkers[i].finish()
|
||||
staleWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation where all workers have been cleaned up (no worker is running),
|
||||
// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
|
||||
if isDormant && p.Waiting() > 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nowTimeUpdateInterval = 500 * time.Millisecond
|
||||
|
||||
// ticktock is a goroutine that updates the current time in the pool regularly.
|
||||
func (p *poolCommon) ticktock() {
|
||||
ticker := time.NewTicker(nowTimeUpdateInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
atomic.StoreInt32(&p.ticktockDone, 1)
|
||||
}()
|
||||
|
||||
ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
|
||||
for {
|
||||
select {
|
||||
case <-ticktockCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if p.IsClosed() {
|
||||
break
|
||||
}
|
||||
|
||||
p.now.Store(time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *poolCommon) goPurge() {
|
||||
if p.options.DisablePurge {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
|
||||
go p.purgeStaleWorkers()
|
||||
}
|
||||
|
||||
func (p *poolCommon) goTicktock() {
|
||||
p.now.Store(time.Now())
|
||||
p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
|
||||
go p.ticktock()
|
||||
}
|
||||
|
||||
func (p *poolCommon) nowTime() time.Time {
|
||||
return p.now.Load().(time.Time)
|
||||
}
|
||||
|
||||
// Running returns the number of workers currently running.
|
||||
func (p *poolCommon) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns the number of available workers, -1 indicates this pool is unlimited.
|
||||
func (p *poolCommon) Free() int {
|
||||
c := p.Cap()
|
||||
if c < 0 {
|
||||
return -1
|
||||
}
|
||||
return c - p.Running()
|
||||
}
|
||||
|
||||
// Waiting returns the number of tasks waiting to be executed.
|
||||
func (p *poolCommon) Waiting() int {
|
||||
return int(atomic.LoadInt32(&p.waiting))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *poolCommon) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
|
||||
func (p *poolCommon) Tune(size int) {
|
||||
capacity := p.Cap()
|
||||
if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
if size > capacity {
|
||||
if size-capacity == 1 {
|
||||
p.cond.Signal()
|
||||
return
|
||||
}
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the pool is closed.
|
||||
func (p *poolCommon) IsClosed() bool {
|
||||
return atomic.LoadInt32(&p.state) == CLOSED
|
||||
}
|
||||
|
||||
// Release closes this pool and releases the worker queue.
|
||||
func (p *poolCommon) Release() {
|
||||
if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
|
||||
return
|
||||
}
|
||||
|
||||
if p.stopPurge != nil {
|
||||
p.stopPurge()
|
||||
p.stopPurge = nil
|
||||
}
|
||||
if p.stopTicktock != nil {
|
||||
p.stopTicktock()
|
||||
p.stopTicktock = nil
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
p.workers.reset()
|
||||
p.lock.Unlock()
|
||||
|
||||
// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
|
||||
// those callers blocking infinitely.
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
|
||||
// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
|
||||
func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error {
|
||||
if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
p.Release()
|
||||
|
||||
var purgeCh <-chan struct{}
|
||||
if !p.options.DisablePurge {
|
||||
purgeCh = p.purgeCtx.Done()
|
||||
} else {
|
||||
purgeCh = p.allDone
|
||||
}
|
||||
|
||||
if p.Running() == 0 {
|
||||
p.once.Do(func() {
|
||||
close(p.allDone)
|
||||
})
|
||||
}
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return ErrTimeout
|
||||
case <-p.allDone:
|
||||
<-purgeCh
|
||||
<-p.ticktockCtx.Done()
|
||||
if p.Running() == 0 &&
|
||||
(p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
|
||||
atomic.LoadInt32(&p.ticktockDone) == 1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot reboots a closed pool, it does nothing if the pool is not closed.
|
||||
// If you intend to reboot a closed pool, use ReleaseTimeout() instead of
|
||||
// Release() to ensure that all workers are stopped and resource are released
|
||||
// before rebooting, otherwise you may run into data race.
|
||||
func (p *poolCommon) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
|
||||
atomic.StoreInt32(&p.purgeDone, 0)
|
||||
p.goPurge()
|
||||
atomic.StoreInt32(&p.ticktockDone, 0)
|
||||
p.goTicktock()
|
||||
p.allDone = make(chan struct{})
|
||||
p.once = &sync.Once{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *poolCommon) addRunning(delta int) int {
|
||||
return int(atomic.AddInt32(&p.running, int32(delta)))
|
||||
}
|
||||
|
||||
func (p *poolCommon) addWaiting(delta int) {
|
||||
atomic.AddInt32(&p.waiting, int32(delta))
|
||||
}
|
||||
|
||||
// retrieveWorker returns an available worker to run the tasks.
|
||||
func (p *poolCommon) retrieveWorker() (w worker, err error) {
|
||||
p.lock.Lock()
|
||||
|
||||
retry:
|
||||
// First try to fetch the worker from the queue.
|
||||
if w = p.workers.detach(); w != nil {
|
||||
p.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// If the worker queue is empty, and we don't run out of the pool capacity,
|
||||
// then just spawn a new worker goroutine.
|
||||
if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
|
||||
p.lock.Unlock()
|
||||
w = p.workerCache.Get().(worker)
|
||||
w.run()
|
||||
return
|
||||
}
|
||||
|
||||
// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
|
||||
if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolOverload
|
||||
}
|
||||
|
||||
// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
|
||||
p.addWaiting(1)
|
||||
p.cond.Wait() // block and wait for an available worker
|
||||
p.addWaiting(-1)
|
||||
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return nil, ErrPoolClosed
|
||||
}
|
||||
|
||||
goto retry
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *poolCommon) revertWorker(worker worker) bool {
|
||||
if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
|
||||
p.cond.Broadcast()
|
||||
return false
|
||||
}
|
||||
|
||||
worker.setLastUsedTime(p.nowTime())
|
||||
|
||||
p.lock.Lock()
|
||||
// To avoid memory leaks, add a double check in the lock scope.
|
||||
// Issue: https://github.com/panjf2000/ants/issues/113
|
||||
if p.IsClosed() {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
if err := p.workers.insert(worker); err != nil {
|
||||
p.lock.Unlock()
|
||||
return false
|
||||
}
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -25,51 +25,58 @@ package ants_test
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
RunTimes = 1000000
|
||||
benchParam = 10
|
||||
benchAntsSize = 200000
|
||||
RunTimes = 1e6
|
||||
PoolCap = 5e4
|
||||
BenchParam = 10
|
||||
DefaultExpiredTime = 10 * time.Second
|
||||
)
|
||||
|
||||
func demoFunc() {
|
||||
n := 10
|
||||
time.Sleep(time.Duration(n) * time.Millisecond)
|
||||
time.Sleep(time.Duration(BenchParam) * time.Millisecond)
|
||||
}
|
||||
|
||||
func demoPoolFunc(args interface{}) {
|
||||
func demoPoolFunc(args any) {
|
||||
n := args.(int)
|
||||
time.Sleep(time.Duration(n) * time.Millisecond)
|
||||
}
|
||||
|
||||
func demoPoolFuncInt(n int) {
|
||||
time.Sleep(time.Duration(n) * time.Millisecond)
|
||||
}
|
||||
|
||||
var stopLongRunningFunc int32
|
||||
|
||||
func longRunningFunc() {
|
||||
for {
|
||||
for atomic.LoadInt32(&stopLongRunningFunc) == 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func longRunningPoolFunc(arg interface{}) {
|
||||
if ch, ok := arg.(chan struct{}); ok {
|
||||
<-ch
|
||||
return
|
||||
}
|
||||
for {
|
||||
runtime.Gosched()
|
||||
}
|
||||
func longRunningPoolFunc(arg any) {
|
||||
<-arg.(chan struct{})
|
||||
}
|
||||
|
||||
func BenchmarkGoroutineWithFunc(b *testing.B) {
|
||||
func longRunningPoolFuncCh(ch chan struct{}) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func BenchmarkGoroutines(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
go func() {
|
||||
demoPoolFunc(benchParam)
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
@ -77,16 +84,17 @@ func BenchmarkGoroutineWithFunc(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSemaphoreWithFunc(b *testing.B) {
|
||||
func BenchmarkChannel(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
sema := make(chan struct{}, benchAntsSize)
|
||||
sema := make(chan struct{}, PoolCap)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
sema <- struct{}{}
|
||||
go func() {
|
||||
demoPoolFunc(benchParam)
|
||||
demoFunc()
|
||||
<-sema
|
||||
wg.Done()
|
||||
}()
|
||||
@ -95,40 +103,76 @@ func BenchmarkSemaphoreWithFunc(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAntsPoolWithFunc(b *testing.B) {
|
||||
func BenchmarkErrGroup(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := ants.NewPoolWithFunc(benchAntsSize, func(i interface{}) {
|
||||
demoPoolFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
var pool errgroup.Group
|
||||
pool.SetLimit(PoolCap)
|
||||
|
||||
b.StartTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Invoke(benchParam)
|
||||
pool.Go(func() error {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkGoroutineThroughput(b *testing.B) {
|
||||
func BenchmarkAntsPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAntsMultiPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(RunTimes)
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoroutinesThroughput(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
go demoPoolFunc(benchParam)
|
||||
go demoFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSemaphoreThroughput(b *testing.B) {
|
||||
sema := make(chan struct{}, benchAntsSize)
|
||||
sema := make(chan struct{}, PoolCap)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
sema <- struct{}{}
|
||||
go func() {
|
||||
demoPoolFunc(benchParam)
|
||||
demoFunc()
|
||||
<-sema
|
||||
}()
|
||||
}
|
||||
@ -136,13 +180,49 @@ func BenchmarkSemaphoreThroughput(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewPoolWithFunc(benchAntsSize, demoPoolFunc)
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
b.StartTimer()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Invoke(benchParam)
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < RunTimes; j++ {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.Release()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {
|
||||
p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
|
||||
defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = p.Submit(demoFunc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
1378
ants_test.go
1378
ants_test.go
File diff suppressed because it is too large
Load Diff
174
example_test.go
Normal file
174
example_test.go
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
sum int32
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
func incSum(i any) {
|
||||
incSumInt(i.(int32))
|
||||
}
|
||||
|
||||
func incSumInt(i int32) {
|
||||
atomic.AddInt32(&sum, i)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func ExamplePool() {
|
||||
ants.Reboot() // ensure the default pool is available
|
||||
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
// Use the default pool.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = ants.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
wg.Add(runTimes)
|
||||
// Use the new pool.
|
||||
pool, _ := ants.NewPool(10)
|
||||
defer pool.Release()
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = pool.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output:
|
||||
// The result is 499500
|
||||
// The result is 499500
|
||||
}
|
||||
|
||||
func ExamplePoolWithFunc() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
pool, _ := ants.NewPoolWithFunc(10, incSum)
|
||||
defer pool.Release()
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = pool.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExamplePoolWithFuncGeneric() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
pool, _ := ants.NewPoolWithFuncGeneric(10, incSumInt)
|
||||
defer pool.Release()
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = pool.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPool() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPool(10, runTimes/10, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
j := i
|
||||
_ = mp.Submit(func() {
|
||||
incSumInt(int32(j))
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPoolWithFunc() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, incSum, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = mp.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
|
||||
func ExampleMultiPoolWithFuncGeneric() {
|
||||
atomic.StoreInt32(&sum, 0)
|
||||
runTimes := 1000
|
||||
wg.Add(runTimes)
|
||||
|
||||
mp, _ := ants.NewMultiPoolWithFuncGeneric(10, runTimes/10, incSumInt, ants.RoundRobin)
|
||||
defer mp.ReleaseTimeout(time.Second) // nolint:errcheck
|
||||
|
||||
for i := 0; i < runTimes; i++ {
|
||||
_ = mp.Invoke(int32(i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Printf("The result is %d\n", sum)
|
||||
|
||||
// Output: The result is 499500
|
||||
}
|
||||
13
go.mod
13
go.mod
@ -1,3 +1,14 @@
|
||||
module github.com/panjf2000/ants/v2
|
||||
|
||||
go 1.12
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/sync v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
229
multipool.go
Normal file
229
multipool.go
Normal file
@ -0,0 +1,229 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2023 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// LoadBalancingStrategy represents the type of load-balancing algorithm.
|
||||
type LoadBalancingStrategy int
|
||||
|
||||
const (
|
||||
// RoundRobin distributes task to a list of pools in rotation.
|
||||
RoundRobin LoadBalancingStrategy = 1 << (iota + 1)
|
||||
|
||||
// LeastTasks always selects the pool with the least number of pending tasks.
|
||||
LeastTasks
|
||||
)
|
||||
|
||||
// MultiPool consists of multiple pools, from which you will benefit the
|
||||
// performance improvement on basis of the fine-grained locking that reduces
|
||||
// the lock contention.
|
||||
// MultiPool is a good fit for the scenario where you have a large number of
|
||||
// tasks to submit, and you don't want the single pool to be the bottleneck.
|
||||
type MultiPool struct {
|
||||
pools []*Pool
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// NewMultiPool instantiates a MultiPool with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
pools := make([]*Pool, size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPool(sizePerPool, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
if n := pool.Running(); n < leastTasks {
|
||||
leastTasks = n
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Submit submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPool) Submit(task func()) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
||||
return mp.pools[mp.next(LeastTasks)].Submit(task)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running workers across all pools.
|
||||
func (mp *MultiPool) Running() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Running()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunningByIndex returns the number of the currently running workers in the specific pool.
|
||||
func (mp *MultiPool) RunningByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Running(), nil
|
||||
}
|
||||
|
||||
// Free returns the number of available workers across all pools.
|
||||
func (mp *MultiPool) Free() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Free()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FreeByIndex returns the number of available workers in the specific pool.
|
||||
func (mp *MultiPool) FreeByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Free(), nil
|
||||
}
|
||||
|
||||
// Waiting returns the number of the currently waiting tasks across all pools.
|
||||
func (mp *MultiPool) Waiting() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Waiting()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
|
||||
func (mp *MultiPool) WaitingByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Waiting(), nil
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this multi-pool.
|
||||
func (mp *MultiPool) Cap() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Cap()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tune resizes each pool in multi-pool.
|
||||
//
|
||||
// Note that this method doesn't resize the overall
|
||||
// capacity of multi-pool.
|
||||
func (mp *MultiPool) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPool) IsClosed() bool {
|
||||
return atomic.LoadInt32(&mp.state) == CLOSED
|
||||
}
|
||||
|
||||
// ReleaseTimeout closes the multi-pool with a timeout,
|
||||
// it waits all pools to be closed before timing out.
|
||||
func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error {
|
||||
if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
errCh := make(chan error, len(mp.pools))
|
||||
var wg errgroup.Group
|
||||
for i, pool := range mp.pools {
|
||||
func(p *Pool, idx int) {
|
||||
wg.Go(func() error {
|
||||
err := p.ReleaseTimeout(timeout)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("pool %d: %v", idx, err)
|
||||
}
|
||||
errCh <- err
|
||||
return err
|
||||
})
|
||||
}(pool, i)
|
||||
}
|
||||
|
||||
_ = wg.Wait()
|
||||
|
||||
var errStr strings.Builder
|
||||
for i := 0; i < len(mp.pools); i++ {
|
||||
if err := <-errCh; err != nil {
|
||||
errStr.WriteString(err.Error())
|
||||
errStr.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
|
||||
if errStr.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimSuffix(errStr.String(), " | "))
|
||||
}
|
||||
|
||||
// Reboot reboots a released multi-pool.
|
||||
func (mp *MultiPool) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
219
multipool_func.go
Normal file
219
multipool_func.go
Normal file
@ -0,0 +1,219 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2023 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// MultiPoolWithFunc consists of multiple pools, from which you will benefit the
|
||||
// performance improvement on basis of the fine-grained locking that reduces
|
||||
// the lock contention.
|
||||
// MultiPoolWithFunc is a good fit for the scenario where you have a large number of
|
||||
// tasks to submit, and you don't want the single pool to be the bottleneck.
|
||||
type MultiPoolWithFunc struct {
|
||||
pools []*PoolWithFunc
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// NewMultiPoolWithFunc instantiates a MultiPoolWithFunc with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
pools := make([]*PoolWithFunc, size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPoolWithFunc(sizePerPool, fn, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
if n := pool.Running(); n < leastTasks {
|
||||
leastTasks = n
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Invoke submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPoolWithFunc) Invoke(args any) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
||||
return mp.pools[mp.next(LeastTasks)].Invoke(args)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running workers across all pools.
|
||||
func (mp *MultiPoolWithFunc) Running() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Running()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunningByIndex returns the number of the currently running workers in the specific pool.
|
||||
func (mp *MultiPoolWithFunc) RunningByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Running(), nil
|
||||
}
|
||||
|
||||
// Free returns the number of available workers across all pools.
|
||||
func (mp *MultiPoolWithFunc) Free() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Free()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FreeByIndex returns the number of available workers in the specific pool.
|
||||
func (mp *MultiPoolWithFunc) FreeByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Free(), nil
|
||||
}
|
||||
|
||||
// Waiting returns the number of the currently waiting tasks across all pools.
|
||||
func (mp *MultiPoolWithFunc) Waiting() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Waiting()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
|
||||
func (mp *MultiPoolWithFunc) WaitingByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Waiting(), nil
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this multi-pool.
|
||||
func (mp *MultiPoolWithFunc) Cap() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Cap()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tune resizes each pool in multi-pool.
|
||||
//
|
||||
// Note that this method doesn't resize the overall
|
||||
// capacity of multi-pool.
|
||||
func (mp *MultiPoolWithFunc) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPoolWithFunc) IsClosed() bool {
|
||||
return atomic.LoadInt32(&mp.state) == CLOSED
|
||||
}
|
||||
|
||||
// ReleaseTimeout closes the multi-pool with a timeout,
|
||||
// it waits all pools to be closed before timing out.
|
||||
func (mp *MultiPoolWithFunc) ReleaseTimeout(timeout time.Duration) error {
|
||||
if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
errCh := make(chan error, len(mp.pools))
|
||||
var wg errgroup.Group
|
||||
for i, pool := range mp.pools {
|
||||
func(p *PoolWithFunc, idx int) {
|
||||
wg.Go(func() error {
|
||||
err := p.ReleaseTimeout(timeout)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("pool %d: %v", idx, err)
|
||||
}
|
||||
errCh <- err
|
||||
return err
|
||||
})
|
||||
}(pool, i)
|
||||
}
|
||||
|
||||
_ = wg.Wait()
|
||||
|
||||
var errStr strings.Builder
|
||||
for i := 0; i < len(mp.pools); i++ {
|
||||
if err := <-errCh; err != nil {
|
||||
errStr.WriteString(err.Error())
|
||||
errStr.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
|
||||
if errStr.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimSuffix(errStr.String(), " | "))
|
||||
}
|
||||
|
||||
// Reboot reboots a released multi-pool.
|
||||
func (mp *MultiPoolWithFunc) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
215
multipool_func_generic.go
Normal file
215
multipool_func_generic.go
Normal file
@ -0,0 +1,215 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// MultiPoolWithFuncGeneric is the generic version of MultiPoolWithFunc.
|
||||
type MultiPoolWithFuncGeneric[T any] struct {
|
||||
pools []*PoolWithFuncGeneric[T]
|
||||
index uint32
|
||||
state int32
|
||||
lbs LoadBalancingStrategy
|
||||
}
|
||||
|
||||
// NewMultiPoolWithFuncGeneric instantiates a MultiPoolWithFunc with a size of the pool list and a size
|
||||
// per pool, and the load-balancing strategy.
|
||||
func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFuncGeneric[T], error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidMultiPoolSize
|
||||
}
|
||||
|
||||
if lbs != RoundRobin && lbs != LeastTasks {
|
||||
return nil, ErrInvalidLoadBalancingStrategy
|
||||
}
|
||||
pools := make([]*PoolWithFuncGeneric[T], size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool, err := NewPoolWithFuncGeneric(sizePerPool, fn, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[i] = pool
|
||||
}
|
||||
return &MultiPoolWithFuncGeneric[T]{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
|
||||
}
|
||||
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (idx int) {
|
||||
switch lbs {
|
||||
case RoundRobin:
|
||||
return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
|
||||
case LeastTasks:
|
||||
leastTasks := 1<<31 - 1
|
||||
for i, pool := range mp.pools {
|
||||
if n := pool.Running(); n < leastTasks {
|
||||
leastTasks = n
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Invoke submits a task to a pool selected by the load-balancing strategy.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) {
|
||||
if mp.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPoolOverload && mp.lbs == RoundRobin {
|
||||
return mp.pools[mp.next(LeastTasks)].Invoke(args)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running workers across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Running()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunningByIndex returns the number of the currently running workers in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Running(), nil
|
||||
}
|
||||
|
||||
// Free returns the number of available workers across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Free()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FreeByIndex returns the number of available workers in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Free(), nil
|
||||
}
|
||||
|
||||
// Waiting returns the number of the currently waiting tasks across all pools.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Waiting()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, error) {
|
||||
if idx < 0 || idx >= len(mp.pools) {
|
||||
return -1, ErrInvalidPoolIndex
|
||||
}
|
||||
return mp.pools[idx].Waiting(), nil
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) {
|
||||
for _, pool := range mp.pools {
|
||||
n += pool.Cap()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tune resizes each pool in multi-pool.
|
||||
//
|
||||
// Note that this method doesn't resize the overall
|
||||
// capacity of multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) {
|
||||
for _, pool := range mp.pools {
|
||||
pool.Tune(size)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClosed indicates whether the multi-pool is closed.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool {
|
||||
return atomic.LoadInt32(&mp.state) == CLOSED
|
||||
}
|
||||
|
||||
// ReleaseTimeout closes the multi-pool with a timeout,
|
||||
// it waits all pools to be closed before timing out.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Duration) error {
|
||||
if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
errCh := make(chan error, len(mp.pools))
|
||||
var wg errgroup.Group
|
||||
for i, pool := range mp.pools {
|
||||
func(p *PoolWithFuncGeneric[T], idx int) {
|
||||
wg.Go(func() error {
|
||||
err := p.ReleaseTimeout(timeout)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("pool %d: %v", idx, err)
|
||||
}
|
||||
errCh <- err
|
||||
return err
|
||||
})
|
||||
}(pool, i)
|
||||
}
|
||||
|
||||
_ = wg.Wait()
|
||||
|
||||
var errStr strings.Builder
|
||||
for i := 0; i < len(mp.pools); i++ {
|
||||
if err := <-errCh; err != nil {
|
||||
errStr.WriteString(err.Error())
|
||||
errStr.WriteString(" | ")
|
||||
}
|
||||
}
|
||||
|
||||
if errStr.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimSuffix(errStr.String(), " | "))
|
||||
}
|
||||
|
||||
// Reboot reboots a released multi-pool.
|
||||
func (mp *MultiPoolWithFuncGeneric[T]) Reboot() {
|
||||
if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
|
||||
atomic.StoreUint32(&mp.index, 0)
|
||||
for _, pool := range mp.pools {
|
||||
pool.Reboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
125
options.go
Normal file
125
options.go
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2018. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
|
||||
// Option represents the optional function.
|
||||
type Option func(opts *Options)
|
||||
|
||||
func loadOptions(options ...Option) *Options {
|
||||
opts := new(Options)
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Options contains all options which will be applied when instantiating an ants pool.
|
||||
type Options struct {
|
||||
// ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers,
|
||||
// the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been
|
||||
// used for more than `ExpiryDuration`.
|
||||
ExpiryDuration time.Duration
|
||||
|
||||
// PreAlloc indicates whether to make memory pre-allocation when initializing Pool.
|
||||
PreAlloc bool
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
MaxBlockingTasks int
|
||||
|
||||
// When Nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When Nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
Nonblocking bool
|
||||
|
||||
// PanicHandler is used to handle panics from each worker goroutine.
|
||||
// If nil, the default behavior is to capture the value given to panic
|
||||
// and resume normal execution and print that value along with the
|
||||
// stack trace of the goroutine
|
||||
PanicHandler func(any)
|
||||
|
||||
// Logger is the customized logger for logging info, if it is not set,
|
||||
// default standard logger from log package is used.
|
||||
Logger Logger
|
||||
|
||||
// When DisablePurge is true, workers are not purged and are resident.
|
||||
DisablePurge bool
|
||||
}
|
||||
|
||||
// WithOptions accepts the whole Options config.
|
||||
func WithOptions(options Options) Option {
|
||||
return func(opts *Options) {
|
||||
*opts = options
|
||||
}
|
||||
}
|
||||
|
||||
// WithExpiryDuration sets up the interval time of cleaning up goroutines.
|
||||
func WithExpiryDuration(expiryDuration time.Duration) Option {
|
||||
return func(opts *Options) {
|
||||
opts.ExpiryDuration = expiryDuration
|
||||
}
|
||||
}
|
||||
|
||||
// WithPreAlloc indicates whether it should malloc for workers.
|
||||
func WithPreAlloc(preAlloc bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PreAlloc = preAlloc
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool.
|
||||
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.MaxBlockingTasks = maxBlockingTasks
|
||||
}
|
||||
}
|
||||
|
||||
// WithNonblocking indicates that pool will return nil when there is no available workers.
|
||||
func WithNonblocking(nonblocking bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Nonblocking = nonblocking
|
||||
}
|
||||
}
|
||||
|
||||
// WithPanicHandler sets up panic handler.
|
||||
func WithPanicHandler(panicHandler func(any)) Option {
|
||||
return func(opts *Options) {
|
||||
opts.PanicHandler = panicHandler
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets up a customized logger.
|
||||
func WithLogger(logger Logger) Option {
|
||||
return func(opts *Options) {
|
||||
opts.Logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisablePurge indicates whether we turn off automatically purge.
|
||||
func WithDisablePurge(disable bool) Option {
|
||||
return func(opts *Options) {
|
||||
opts.DisablePurge = disable
|
||||
}
|
||||
}
|
||||
37
pkg/sync/spinlock.go
Normal file
37
pkg/sync/spinlock.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2019 Andy Pan & Dietoad. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type spinLock uint32
|
||||
|
||||
const maxBackoff = 16
|
||||
|
||||
func (sl *spinLock) Lock() {
|
||||
backoff := 1
|
||||
for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
|
||||
// Leverage the exponential backoff algorithm, see https://en.wikipedia.org/wiki/Exponential_backoff.
|
||||
for i := 0; i < backoff; i++ {
|
||||
runtime.Gosched()
|
||||
}
|
||||
if backoff < maxBackoff {
|
||||
backoff <<= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *spinLock) Unlock() {
|
||||
atomic.StoreUint32((*uint32)(sl), 0)
|
||||
}
|
||||
|
||||
// NewSpinLock instantiates a spin-lock.
|
||||
func NewSpinLock() sync.Locker {
|
||||
return new(spinLock)
|
||||
}
|
||||
71
pkg/sync/spinlock_test.go
Normal file
71
pkg/sync/spinlock_test.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2021 Andy Pan & Dietoad. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
Benchmark result for three types of locks:
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/panjf2000/ants/v2/pkg/sync
|
||||
BenchmarkMutex-10 10452573 111.1 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSpinLock-10 58953211 18.01 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBackOffSpinLock-10 100000000 10.81 ns/op 0 B/op 0 allocs/op
|
||||
*/
|
||||
|
||||
type originSpinLock uint32
|
||||
|
||||
func (sl *originSpinLock) Lock() {
|
||||
for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *originSpinLock) Unlock() {
|
||||
atomic.StoreUint32((*uint32)(sl), 0)
|
||||
}
|
||||
|
||||
func NewOriginSpinLock() sync.Locker {
|
||||
return new(originSpinLock)
|
||||
}
|
||||
|
||||
func BenchmarkMutex(b *testing.B) {
|
||||
m := sync.Mutex{}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
m.Lock()
|
||||
//nolint:staticcheck
|
||||
m.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSpinLock(b *testing.B) {
|
||||
spin := NewOriginSpinLock()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
spin.Lock()
|
||||
//nolint:staticcheck
|
||||
spin.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkBackOffSpinLock(b *testing.B) {
|
||||
spin := NewSpinLock()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
spin.Lock()
|
||||
//nolint:staticcheck
|
||||
spin.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
25
pkg/sync/sync.go
Normal file
25
pkg/sync/sync.go
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Andy Pan. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package sync provides some handy implementations for synchronization access.
|
||||
// At the moment, there is only an implementation of spin-lock.
|
||||
package sync
|
||||
288
pool.go
288
pool.go
@ -22,280 +22,44 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Pool accept the tasks from client, it limits the total of goroutines to a given number by recycling goroutines.
|
||||
// Pool is a goroutine pool that limits and recycles a mass of goroutines.
|
||||
// The pool capacity can be fixed or unlimited.
|
||||
type Pool struct {
|
||||
// capacity of the pool.
|
||||
capacity int32
|
||||
|
||||
// running is the number of the currently running goroutines.
|
||||
running int32
|
||||
|
||||
// expiryDuration set the expired time (second) of every worker.
|
||||
expiryDuration time.Duration
|
||||
|
||||
// workers is a slice that store the available workers.
|
||||
workers []*goWorker
|
||||
|
||||
// release is used to notice the pool to closed itself.
|
||||
release int32
|
||||
|
||||
// lock for synchronous operation.
|
||||
lock sync.Mutex
|
||||
|
||||
// cond for waiting to get a idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// once makes sure releasing this pool will just be done for one time.
|
||||
once sync.Once
|
||||
|
||||
// workerCache speeds up the obtainment of the an usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// panicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
panicHandler func(interface{})
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
maxBlockingTasks int32
|
||||
|
||||
// goroutine already been blocked on pool.Submit
|
||||
// protected by pool.lock
|
||||
blockingNum int32
|
||||
|
||||
// When nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
nonblocking bool
|
||||
*poolCommon
|
||||
}
|
||||
|
||||
// Clear expired workers periodically.
|
||||
func (p *Pool) periodicallyPurge() {
|
||||
heartbeat := time.NewTicker(p.expiryDuration)
|
||||
defer heartbeat.Stop()
|
||||
|
||||
var expiredWorkers []*goWorker
|
||||
for range heartbeat.C {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED {
|
||||
break
|
||||
}
|
||||
currentTime := time.Now()
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers)
|
||||
var i int
|
||||
for i = 0; i < n && currentTime.Sub(idleWorkers[i].recycleTime) > p.expiryDuration; i++ {
|
||||
}
|
||||
expiredWorkers = append(expiredWorkers[:0], idleWorkers[:i]...)
|
||||
if i > 0 {
|
||||
m := copy(idleWorkers, idleWorkers[i:])
|
||||
for i = m; i < n; i++ {
|
||||
idleWorkers[i] = nil
|
||||
}
|
||||
p.workers = idleWorkers[:m]
|
||||
}
|
||||
p.lock.Unlock()
|
||||
|
||||
// Notify obsolete workers to stop.
|
||||
// This notification must be outside the p.lock, since w.task
|
||||
// may be blocking and may consume a lot of time if many workers
|
||||
// are located on non-local CPUs.
|
||||
for i, w := range expiredWorkers {
|
||||
w.task <- nil
|
||||
expiredWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation that all workers have been cleaned up(no any worker is running)
|
||||
// while some invokers still get stuck in "p.cond.Wait()",
|
||||
// then it ought to wakes all those invokers.
|
||||
if p.Running() == 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewPool generates an instance of ants pool.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidPoolSize
|
||||
}
|
||||
|
||||
opts := new(Options)
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = time.Duration(DEFAULT_CLEAN_INTERVAL_TIME) * time.Second
|
||||
}
|
||||
|
||||
var p *Pool
|
||||
if opts.PreAlloc {
|
||||
p = &Pool{
|
||||
capacity: int32(size),
|
||||
expiryDuration: opts.ExpiryDuration,
|
||||
workers: make([]*goWorker, 0, size),
|
||||
nonblocking: opts.Nonblocking,
|
||||
maxBlockingTasks: int32(opts.MaxBlockingTasks),
|
||||
panicHandler: opts.PanicHandler,
|
||||
}
|
||||
} else {
|
||||
p = &Pool{
|
||||
capacity: int32(size),
|
||||
expiryDuration: opts.ExpiryDuration,
|
||||
nonblocking: opts.Nonblocking,
|
||||
maxBlockingTasks: int32(opts.MaxBlockingTasks),
|
||||
panicHandler: opts.PanicHandler,
|
||||
}
|
||||
}
|
||||
p.cond = sync.NewCond(&p.lock)
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
go p.periodicallyPurge()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Submit submits a task to this pool.
|
||||
// Submit submits a task to the pool.
|
||||
//
|
||||
// Note that you are allowed to call Pool.Submit() from the current Pool.Submit(),
|
||||
// but what calls for special attention is that you will get blocked with the last
|
||||
// Pool.Submit() call once the current Pool runs out of its capacity, and to avoid this,
|
||||
// you should instantiate a Pool with ants.WithNonblocking(true).
|
||||
func (p *Pool) Submit(task func()) error {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
if w := p.retrieveWorker(); w == nil {
|
||||
return ErrPoolOverload
|
||||
} else {
|
||||
w.task <- task
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.inputFunc(task)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running goroutines.
|
||||
func (p *Pool) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns the available goroutines to work.
|
||||
func (p *Pool) Free() int {
|
||||
return int(atomic.LoadInt32(&p.capacity) - atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *Pool) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune changes the capacity of this pool.
|
||||
func (p *Pool) Tune(size int) {
|
||||
if p.Cap() == size {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
}
|
||||
|
||||
// Release Closes this pool.
|
||||
func (p *Pool) Release() {
|
||||
p.once.Do(func() {
|
||||
atomic.StoreInt32(&p.release, 1)
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
for i, w := range idleWorkers {
|
||||
w.task <- nil
|
||||
idleWorkers[i] = nil
|
||||
}
|
||||
p.workers = nil
|
||||
p.lock.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// incRunning increases the number of the currently running goroutines.
|
||||
func (p *Pool) incRunning() {
|
||||
atomic.AddInt32(&p.running, 1)
|
||||
}
|
||||
|
||||
// decRunning decreases the number of the currently running goroutines.
|
||||
func (p *Pool) decRunning() {
|
||||
atomic.AddInt32(&p.running, -1)
|
||||
}
|
||||
|
||||
// retrieveWorker returns a available worker to run the tasks.
|
||||
func (p *Pool) retrieveWorker() *goWorker {
|
||||
var w *goWorker
|
||||
spawnWorker := func() {
|
||||
if cacheWorker := p.workerCache.Get(); cacheWorker != nil {
|
||||
w = cacheWorker.(*goWorker)
|
||||
} else {
|
||||
w = &goWorker{
|
||||
pool: p,
|
||||
task: make(chan func(), workerChanCap),
|
||||
}
|
||||
}
|
||||
w.run()
|
||||
// NewPool instantiates a Pool with customized options.
|
||||
func NewPool(size int, options ...Option) (*Pool, error) {
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers) - 1
|
||||
if n >= 0 {
|
||||
w = idleWorkers[n]
|
||||
idleWorkers[n] = nil
|
||||
p.workers = idleWorkers[:n]
|
||||
p.lock.Unlock()
|
||||
} else if p.Running() < p.Cap() {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
} else {
|
||||
if p.nonblocking {
|
||||
p.lock.Unlock()
|
||||
return nil
|
||||
pool := &Pool{poolCommon: pc}
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorker{
|
||||
pool: pool,
|
||||
task: make(chan func(), workerChanCap),
|
||||
}
|
||||
Reentry:
|
||||
if p.maxBlockingTasks != 0 && p.blockingNum >= p.maxBlockingTasks {
|
||||
p.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
p.blockingNum++
|
||||
p.cond.Wait()
|
||||
p.blockingNum--
|
||||
if p.Running() == 0 {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return w
|
||||
}
|
||||
l := len(p.workers) - 1
|
||||
if l < 0 {
|
||||
goto Reentry
|
||||
}
|
||||
w = p.workers[l]
|
||||
p.workers[l] = nil
|
||||
p.workers = p.workers[:l]
|
||||
p.lock.Unlock()
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *Pool) revertWorker(worker *goWorker) bool {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED || p.Running() > p.Cap() {
|
||||
return false
|
||||
}
|
||||
worker.recycleTime = time.Now()
|
||||
p.lock.Lock()
|
||||
p.workers = append(p.workers, worker)
|
||||
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
return true
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
299
pool_func.go
299
pool_func.go
@ -22,289 +22,54 @@
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PoolWithFunc accept the tasks from client, it limits the total of goroutines to a given number by recycling goroutines.
|
||||
// PoolWithFunc is like Pool but accepts a unified function for all goroutines to execute.
|
||||
type PoolWithFunc struct {
|
||||
// capacity of the pool.
|
||||
capacity int32
|
||||
*poolCommon
|
||||
|
||||
// running is the number of the currently running goroutines.
|
||||
running int32
|
||||
|
||||
// expiryDuration set the expired time (second) of every worker.
|
||||
expiryDuration time.Duration
|
||||
|
||||
// workers is a slice that store the available workers.
|
||||
workers []*goWorkerWithFunc
|
||||
|
||||
// release is used to notice the pool to closed itself.
|
||||
release int32
|
||||
|
||||
// lock for synchronous operation.
|
||||
lock sync.Mutex
|
||||
|
||||
// cond for waiting to get a idle worker.
|
||||
cond *sync.Cond
|
||||
|
||||
// poolFunc is the function for processing tasks.
|
||||
poolFunc func(interface{})
|
||||
|
||||
// once makes sure releasing this pool will just be done for one time.
|
||||
once sync.Once
|
||||
|
||||
// workerCache speeds up the obtainment of the an usable worker in function:retrieveWorker.
|
||||
workerCache sync.Pool
|
||||
|
||||
// panicHandler is used to handle panics from each worker goroutine.
|
||||
// if nil, panics will be thrown out again from worker goroutines.
|
||||
panicHandler func(interface{})
|
||||
|
||||
// Max number of goroutine blocking on pool.Submit.
|
||||
// 0 (default value) means no such limit.
|
||||
maxBlockingTasks int32
|
||||
|
||||
// goroutine already been blocked on pool.Submit
|
||||
// protected by pool.lock
|
||||
blockingNum int32
|
||||
|
||||
// When nonblocking is true, Pool.Submit will never be blocked.
|
||||
// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
|
||||
// When nonblocking is true, MaxBlockingTasks is inoperative.
|
||||
nonblocking bool
|
||||
// fn is the unified function for processing tasks.
|
||||
fn func(any)
|
||||
}
|
||||
|
||||
// Clear expired workers periodically.
|
||||
func (p *PoolWithFunc) periodicallyPurge() {
|
||||
heartbeat := time.NewTicker(p.expiryDuration)
|
||||
defer heartbeat.Stop()
|
||||
|
||||
var expiredWorkers []*goWorkerWithFunc
|
||||
for range heartbeat.C {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED {
|
||||
break
|
||||
}
|
||||
currentTime := time.Now()
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers)
|
||||
var i int
|
||||
for i = 0; i < n && currentTime.Sub(idleWorkers[i].recycleTime) > p.expiryDuration; i++ {
|
||||
}
|
||||
expiredWorkers = append(expiredWorkers[:0], idleWorkers[:i]...)
|
||||
if i > 0 {
|
||||
m := copy(idleWorkers, idleWorkers[i:])
|
||||
for i = m; i < n; i++ {
|
||||
idleWorkers[i] = nil
|
||||
}
|
||||
p.workers = idleWorkers[:m]
|
||||
}
|
||||
p.lock.Unlock()
|
||||
|
||||
// Notify obsolete workers to stop.
|
||||
// This notification must be outside the p.lock, since w.task
|
||||
// may be blocking and may consume a lot of time if many workers
|
||||
// are located on non-local CPUs.
|
||||
for i, w := range expiredWorkers {
|
||||
w.args <- nil
|
||||
expiredWorkers[i] = nil
|
||||
}
|
||||
|
||||
// There might be a situation that all workers have been cleaned up(no any worker is running)
|
||||
// while some invokers still get stuck in "p.cond.Wait()",
|
||||
// then it ought to wakes all those invokers.
|
||||
if p.Running() == 0 {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
// Invoke passes arguments to the pool.
|
||||
//
|
||||
// Note that you are allowed to call Pool.Invoke() from the current Pool.Invoke(),
|
||||
// but what calls for special attention is that you will get blocked with the last
|
||||
// Pool.Invoke() call once the current Pool runs out of its capacity, and to avoid this,
|
||||
// you should instantiate a PoolWithFunc with ants.WithNonblocking(true).
|
||||
func (p *PoolWithFunc) Invoke(arg any) error {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.inputArg(arg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPoolWithFunc generates an instance of ants pool with a specific function.
|
||||
func NewPoolWithFunc(size int, pf func(interface{}), options ...Option) (*PoolWithFunc, error) {
|
||||
if size <= 0 {
|
||||
return nil, ErrInvalidPoolSize
|
||||
}
|
||||
|
||||
// NewPoolWithFunc instantiates a PoolWithFunc with customized options.
|
||||
func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) {
|
||||
if pf == nil {
|
||||
return nil, ErrLackPoolFunc
|
||||
}
|
||||
|
||||
opts := new(Options)
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiry := opts.ExpiryDuration; expiry < 0 {
|
||||
return nil, ErrInvalidPoolExpiry
|
||||
} else if expiry == 0 {
|
||||
opts.ExpiryDuration = time.Duration(DEFAULT_CLEAN_INTERVAL_TIME) * time.Second
|
||||
pool := &PoolWithFunc{
|
||||
poolCommon: pc,
|
||||
fn: pf,
|
||||
}
|
||||
|
||||
var p *PoolWithFunc
|
||||
if opts.PreAlloc {
|
||||
p = &PoolWithFunc{
|
||||
capacity: int32(size),
|
||||
expiryDuration: opts.ExpiryDuration,
|
||||
poolFunc: pf,
|
||||
workers: make([]*goWorkerWithFunc, 0, size),
|
||||
nonblocking: opts.Nonblocking,
|
||||
maxBlockingTasks: int32(opts.MaxBlockingTasks),
|
||||
panicHandler: opts.PanicHandler,
|
||||
}
|
||||
} else {
|
||||
p = &PoolWithFunc{
|
||||
capacity: int32(size),
|
||||
expiryDuration: opts.ExpiryDuration,
|
||||
poolFunc: pf,
|
||||
nonblocking: opts.Nonblocking,
|
||||
maxBlockingTasks: int32(opts.MaxBlockingTasks),
|
||||
panicHandler: opts.PanicHandler,
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorkerWithFunc{
|
||||
pool: pool,
|
||||
arg: make(chan any, workerChanCap),
|
||||
}
|
||||
}
|
||||
p.cond = sync.NewCond(&p.lock)
|
||||
|
||||
// Start a goroutine to clean up expired workers periodically.
|
||||
go p.periodicallyPurge()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Invoke submits a task to pool.
|
||||
func (p *PoolWithFunc) Invoke(args interface{}) error {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
if w := p.retrieveWorker(); w == nil {
|
||||
return ErrPoolOverload
|
||||
} else {
|
||||
w.args <- args
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Running returns the number of the currently running goroutines.
|
||||
func (p *PoolWithFunc) Running() int {
|
||||
return int(atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Free returns a available goroutines to work.
|
||||
func (p *PoolWithFunc) Free() int {
|
||||
return int(atomic.LoadInt32(&p.capacity) - atomic.LoadInt32(&p.running))
|
||||
}
|
||||
|
||||
// Cap returns the capacity of this pool.
|
||||
func (p *PoolWithFunc) Cap() int {
|
||||
return int(atomic.LoadInt32(&p.capacity))
|
||||
}
|
||||
|
||||
// Tune change the capacity of this pool.
|
||||
func (p *PoolWithFunc) Tune(size int) {
|
||||
if p.Cap() == size {
|
||||
return
|
||||
}
|
||||
atomic.StoreInt32(&p.capacity, int32(size))
|
||||
}
|
||||
|
||||
// Release Closed this pool.
|
||||
func (p *PoolWithFunc) Release() {
|
||||
p.once.Do(func() {
|
||||
atomic.StoreInt32(&p.release, 1)
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
for i, w := range idleWorkers {
|
||||
w.args <- nil
|
||||
idleWorkers[i] = nil
|
||||
}
|
||||
p.workers = nil
|
||||
p.lock.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// incRunning increases the number of the currently running goroutines.
|
||||
func (p *PoolWithFunc) incRunning() {
|
||||
atomic.AddInt32(&p.running, 1)
|
||||
}
|
||||
|
||||
// decRunning decreases the number of the currently running goroutines.
|
||||
func (p *PoolWithFunc) decRunning() {
|
||||
atomic.AddInt32(&p.running, -1)
|
||||
}
|
||||
|
||||
// retrieveWorker returns a available worker to run the tasks.
|
||||
func (p *PoolWithFunc) retrieveWorker() *goWorkerWithFunc {
|
||||
var w *goWorkerWithFunc
|
||||
spawnWorker := func() {
|
||||
if cacheWorker := p.workerCache.Get(); cacheWorker != nil {
|
||||
w = cacheWorker.(*goWorkerWithFunc)
|
||||
} else {
|
||||
w = &goWorkerWithFunc{
|
||||
pool: p,
|
||||
args: make(chan interface{}, workerChanCap),
|
||||
}
|
||||
}
|
||||
w.run()
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
idleWorkers := p.workers
|
||||
n := len(idleWorkers) - 1
|
||||
if n >= 0 {
|
||||
w = idleWorkers[n]
|
||||
idleWorkers[n] = nil
|
||||
p.workers = idleWorkers[:n]
|
||||
p.lock.Unlock()
|
||||
} else if p.Running() < p.Cap() {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
} else {
|
||||
if p.nonblocking {
|
||||
p.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
Reentry:
|
||||
if p.maxBlockingTasks != 0 && p.blockingNum >= p.maxBlockingTasks {
|
||||
p.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
p.blockingNum++
|
||||
p.cond.Wait()
|
||||
p.blockingNum--
|
||||
if p.Running() == 0 {
|
||||
p.lock.Unlock()
|
||||
spawnWorker()
|
||||
return w
|
||||
}
|
||||
l := len(p.workers) - 1
|
||||
if l < 0 {
|
||||
goto Reentry
|
||||
}
|
||||
w = p.workers[l]
|
||||
p.workers[l] = nil
|
||||
p.workers = p.workers[:l]
|
||||
p.lock.Unlock()
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// revertWorker puts a worker back into free pool, recycling the goroutines.
|
||||
func (p *PoolWithFunc) revertWorker(worker *goWorkerWithFunc) bool {
|
||||
if atomic.LoadInt32(&p.release) == CLOSED || p.Running() > p.Cap() {
|
||||
return false
|
||||
}
|
||||
worker.recycleTime = time.Now()
|
||||
p.lock.Lock()
|
||||
p.workers = append(p.workers, worker)
|
||||
|
||||
// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
|
||||
p.cond.Signal()
|
||||
p.lock.Unlock()
|
||||
return true
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2018 Andy Pan
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@ -20,62 +20,52 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package main
|
||||
package ants
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
// PoolWithFuncGeneric is the generic version of PoolWithFunc.
|
||||
type PoolWithFuncGeneric[T any] struct {
|
||||
*poolCommon
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var sum int32
|
||||
|
||||
func myFunc(i interface{}) {
|
||||
n := i.(int32)
|
||||
atomic.AddInt32(&sum, n)
|
||||
fmt.Printf("run with %d\n", n)
|
||||
// fn is the unified function for processing tasks.
|
||||
fn func(T)
|
||||
}
|
||||
|
||||
func demoFunc() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
fmt.Println("Hello World!")
|
||||
// Invoke passes the argument to the pool to start a new task.
|
||||
func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error {
|
||||
if p.IsClosed() {
|
||||
return ErrPoolClosed
|
||||
}
|
||||
|
||||
w, err := p.retrieveWorker()
|
||||
if w != nil {
|
||||
w.(*goWorkerWithFuncGeneric[T]).arg <- arg
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer ants.Release()
|
||||
|
||||
runTimes := 1000
|
||||
|
||||
// Use the common pool.
|
||||
var wg sync.WaitGroup
|
||||
syncCalculateSum := func() {
|
||||
demoFunc()
|
||||
wg.Done()
|
||||
// NewPoolWithFuncGeneric instantiates a PoolWithFuncGeneric[T] with customized options.
|
||||
func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Option) (*PoolWithFuncGeneric[T], error) {
|
||||
if pf == nil {
|
||||
return nil, ErrLackPoolFunc
|
||||
}
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = ants.Submit(syncCalculateSum)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", ants.Running())
|
||||
fmt.Printf("finish all tasks.\n")
|
||||
|
||||
// Use the pool with a method,
|
||||
// set 10 to the capacity of goroutine pool and 1 second for expired duration.
|
||||
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
|
||||
myFunc(i)
|
||||
wg.Done()
|
||||
})
|
||||
defer p.Release()
|
||||
// Submit tasks one by one.
|
||||
for i := 0; i < runTimes; i++ {
|
||||
wg.Add(1)
|
||||
_ = p.Invoke(int32(i))
|
||||
pc, err := newPool(size, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("running goroutines: %d\n", p.Running())
|
||||
fmt.Printf("finish all tasks, result is %d\n", sum)
|
||||
|
||||
pool := &PoolWithFuncGeneric[T]{
|
||||
poolCommon: pc,
|
||||
fn: pf,
|
||||
}
|
||||
|
||||
pool.workerCache.New = func() any {
|
||||
return &goWorkerWithFuncGeneric[T]{
|
||||
pool: pool,
|
||||
arg: make(chan T, workerChanCap),
|
||||
exit: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
56
worker.go
56
worker.go
@ -23,8 +23,7 @@
|
||||
package ants
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -32,44 +31,65 @@ import (
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorker struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *Pool
|
||||
|
||||
// task is a job should be done.
|
||||
task chan func()
|
||||
|
||||
// recycleTime will be update when putting a worker back into queue.
|
||||
recycleTime time.Time
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
// that performs the function calls.
|
||||
func (w *goWorker) run() {
|
||||
w.pool.incRunning()
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
w.pool.decRunning()
|
||||
if p := recover(); p != nil {
|
||||
if w.pool.panicHandler != nil {
|
||||
w.pool.panicHandler(p)
|
||||
} else {
|
||||
log.Printf("worker exits from a panic: %v\n", p)
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
log.Printf("worker exits from panic: %s\n", string(buf[:n]))
|
||||
}
|
||||
if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
|
||||
w.pool.once.Do(func() {
|
||||
close(w.pool.allDone)
|
||||
})
|
||||
}
|
||||
w.pool.workerCache.Put(w)
|
||||
if p := recover(); p != nil {
|
||||
if ph := w.pool.options.PanicHandler; ph != nil {
|
||||
ph(p)
|
||||
} else {
|
||||
w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
|
||||
}
|
||||
}
|
||||
// Call Signal() here in case there are goroutines waiting for available workers.
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for f := range w.task {
|
||||
if f == nil {
|
||||
for fn := range w.task {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
f()
|
||||
fn()
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorker) finish() {
|
||||
w.task <- nil
|
||||
}
|
||||
|
||||
func (w *goWorker) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorker) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
|
||||
func (w *goWorker) inputFunc(fn func()) {
|
||||
w.task <- fn
|
||||
}
|
||||
|
||||
@ -23,8 +23,7 @@
|
||||
package ants
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -32,44 +31,65 @@ import (
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorkerWithFunc struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *PoolWithFunc
|
||||
|
||||
// args is a job should be done.
|
||||
args chan interface{}
|
||||
// arg is the argument for the function.
|
||||
arg chan any
|
||||
|
||||
// recycleTime will be update when putting a worker back into queue.
|
||||
recycleTime time.Time
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
// that performs the function calls.
|
||||
func (w *goWorkerWithFunc) run() {
|
||||
w.pool.incRunning()
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
w.pool.decRunning()
|
||||
if p := recover(); p != nil {
|
||||
if w.pool.panicHandler != nil {
|
||||
w.pool.panicHandler(p)
|
||||
} else {
|
||||
log.Printf("worker with func exits from a panic: %v\n", p)
|
||||
var buf [4096]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
log.Printf("worker with func exits from panic: %s\n", string(buf[:n]))
|
||||
}
|
||||
if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
|
||||
w.pool.once.Do(func() {
|
||||
close(w.pool.allDone)
|
||||
})
|
||||
}
|
||||
w.pool.workerCache.Put(w)
|
||||
if p := recover(); p != nil {
|
||||
if ph := w.pool.options.PanicHandler; ph != nil {
|
||||
ph(p)
|
||||
} else {
|
||||
w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
|
||||
}
|
||||
}
|
||||
// Call Signal() here in case there are goroutines waiting for available workers.
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for args := range w.args {
|
||||
if args == nil {
|
||||
for arg := range w.arg {
|
||||
if arg == nil {
|
||||
return
|
||||
}
|
||||
w.pool.poolFunc(args)
|
||||
w.pool.fn(arg)
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) finish() {
|
||||
w.arg <- nil
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFunc) inputArg(arg any) {
|
||||
w.arg <- arg
|
||||
}
|
||||
|
||||
96
worker_func_generic.go
Normal file
96
worker_func_generic.go
Normal file
@ -0,0 +1,96 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2025 Andy Pan
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// goWorkerWithFunc is the actual executor who runs the tasks,
|
||||
// it starts a goroutine that accepts tasks and
|
||||
// performs function calls.
|
||||
type goWorkerWithFuncGeneric[T any] struct {
|
||||
worker
|
||||
|
||||
// pool who owns this worker.
|
||||
pool *PoolWithFuncGeneric[T]
|
||||
|
||||
// arg is a job should be done.
|
||||
arg chan T
|
||||
|
||||
// exit signals the goroutine to exit.
|
||||
exit chan struct{}
|
||||
|
||||
// lastUsed will be updated when putting a worker back into queue.
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
// run starts a goroutine to repeat the process
|
||||
// that performs the function calls.
|
||||
func (w *goWorkerWithFuncGeneric[T]) run() {
|
||||
w.pool.addRunning(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
|
||||
w.pool.once.Do(func() {
|
||||
close(w.pool.allDone)
|
||||
})
|
||||
}
|
||||
w.pool.workerCache.Put(w)
|
||||
if p := recover(); p != nil {
|
||||
if ph := w.pool.options.PanicHandler; ph != nil {
|
||||
ph(p)
|
||||
} else {
|
||||
w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
|
||||
}
|
||||
}
|
||||
// Call Signal() here in case there are goroutines waiting for available workers.
|
||||
w.pool.cond.Signal()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.exit:
|
||||
return
|
||||
case arg := <-w.arg:
|
||||
w.pool.fn(arg)
|
||||
if ok := w.pool.revertWorker(w); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) finish() {
|
||||
w.exit <- struct{}{}
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() time.Time {
|
||||
return w.lastUsed
|
||||
}
|
||||
|
||||
func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t time.Time) {
|
||||
w.lastUsed = t
|
||||
}
|
||||
175
worker_loop_queue.go
Normal file
175
worker_loop_queue.go
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
|
||||
type loopQueue struct {
|
||||
items []worker
|
||||
expiry []worker
|
||||
head int
|
||||
tail int
|
||||
size int
|
||||
isFull bool
|
||||
}
|
||||
|
||||
func newWorkerLoopQueue(size int) *loopQueue {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &loopQueue{
|
||||
items: make([]worker, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (wq *loopQueue) len() int {
|
||||
if wq.size == 0 || wq.isEmpty() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if wq.head == wq.tail && wq.isFull {
|
||||
return wq.size
|
||||
}
|
||||
|
||||
if wq.tail > wq.head {
|
||||
return wq.tail - wq.head
|
||||
}
|
||||
|
||||
return wq.size - wq.head + wq.tail
|
||||
}
|
||||
|
||||
func (wq *loopQueue) isEmpty() bool {
|
||||
return wq.head == wq.tail && !wq.isFull
|
||||
}
|
||||
|
||||
func (wq *loopQueue) insert(w worker) error {
|
||||
if wq.isFull {
|
||||
return errQueueIsFull
|
||||
}
|
||||
wq.items[wq.tail] = w
|
||||
wq.tail = (wq.tail + 1) % wq.size
|
||||
|
||||
if wq.tail == wq.head {
|
||||
wq.isFull = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wq *loopQueue) detach() worker {
|
||||
if wq.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := wq.items[wq.head]
|
||||
wq.items[wq.head] = nil
|
||||
wq.head = (wq.head + 1) % wq.size
|
||||
|
||||
wq.isFull = false
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (wq *loopQueue) refresh(duration time.Duration) []worker {
|
||||
expiryTime := time.Now().Add(-duration)
|
||||
index := wq.binarySearch(expiryTime)
|
||||
if index == -1 {
|
||||
return nil
|
||||
}
|
||||
wq.expiry = wq.expiry[:0]
|
||||
|
||||
if wq.head <= index {
|
||||
wq.expiry = append(wq.expiry, wq.items[wq.head:index+1]...)
|
||||
for i := wq.head; i < index+1; i++ {
|
||||
wq.items[i] = nil
|
||||
}
|
||||
} else {
|
||||
wq.expiry = append(wq.expiry, wq.items[0:index+1]...)
|
||||
wq.expiry = append(wq.expiry, wq.items[wq.head:]...)
|
||||
for i := 0; i < index+1; i++ {
|
||||
wq.items[i] = nil
|
||||
}
|
||||
for i := wq.head; i < wq.size; i++ {
|
||||
wq.items[i] = nil
|
||||
}
|
||||
}
|
||||
head := (index + 1) % wq.size
|
||||
wq.head = head
|
||||
if len(wq.expiry) > 0 {
|
||||
wq.isFull = false
|
||||
}
|
||||
|
||||
return wq.expiry
|
||||
}
|
||||
|
||||
func (wq *loopQueue) binarySearch(expiryTime time.Time) int {
|
||||
var mid, nlen, basel, tmid int
|
||||
nlen = len(wq.items)
|
||||
|
||||
// if no need to remove work, return -1
|
||||
if wq.isEmpty() || expiryTime.Before(wq.items[wq.head].lastUsedTime()) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// example
|
||||
// size = 8, head = 7, tail = 4
|
||||
// [ 2, 3, 4, 5, nil, nil, nil, 1] true position
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// tail head
|
||||
//
|
||||
// 1 2 3 4 nil nil nil 0 mapped position
|
||||
// r l
|
||||
|
||||
// base algorithm is a copy from worker_stack
|
||||
// map head and tail to effective left and right
|
||||
r := (wq.tail - 1 - wq.head + nlen) % nlen
|
||||
basel = wq.head
|
||||
l := 0
|
||||
for l <= r {
|
||||
mid = l + ((r - l) >> 1) // avoid overflow when computing mid
|
||||
// calculate true mid position from mapped mid position
|
||||
tmid = (mid + basel + nlen) % nlen
|
||||
if expiryTime.Before(wq.items[tmid].lastUsedTime()) {
|
||||
r = mid - 1
|
||||
} else {
|
||||
l = mid + 1
|
||||
}
|
||||
}
|
||||
// return true position from mapped position
|
||||
return (r + basel + nlen) % nlen
|
||||
}
|
||||
|
||||
func (wq *loopQueue) reset() {
|
||||
if wq.isEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
retry:
|
||||
if w := wq.detach(); w != nil {
|
||||
w.finish()
|
||||
goto retry
|
||||
}
|
||||
wq.head = 0
|
||||
wq.tail = 0
|
||||
}
|
||||
203
worker_loop_queue_test.go
Normal file
203
worker_loop_queue_test.go
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewLoopQueue(t *testing.T) {
|
||||
size := 100
|
||||
q := newWorkerLoopQueue(size)
|
||||
require.EqualValues(t, 0, q.len(), "Len error")
|
||||
require.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
require.Nil(t, q.detach(), "Dequeue error")
|
||||
|
||||
require.Nil(t, newWorkerLoopQueue(0))
|
||||
}
|
||||
|
||||
func TestLoopQueue(t *testing.T) {
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
require.EqualValues(t, 5, q.len(), "Len error")
|
||||
_ = q.detach()
|
||||
require.EqualValues(t, 4, q.len(), "Len error")
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
require.EqualValues(t, 10, q.len(), "Len error")
|
||||
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
require.Error(t, err, "Enqueue, error")
|
||||
|
||||
q.refresh(time.Second)
|
||||
require.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
|
||||
}
|
||||
|
||||
func TestRotatedQueueSearch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
|
||||
t.Skip("Skip this test on Windows platform")
|
||||
}
|
||||
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
|
||||
// 1
|
||||
expiry1 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
require.EqualValues(t, 0, q.binarySearch(time.Now()), "index should be 0")
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
|
||||
// 2
|
||||
expiry2 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")
|
||||
|
||||
require.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0")
|
||||
|
||||
require.EqualValues(t, 1, q.binarySearch(time.Now()), "index should be 1")
|
||||
|
||||
// more
|
||||
for i := 0; i < 5; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
expiry3 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: expiry3})
|
||||
|
||||
var err error
|
||||
for err != errQueueIsFull {
|
||||
err = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
require.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7")
|
||||
|
||||
// rotate
|
||||
for i := 0; i < 6; i++ {
|
||||
_ = q.detach()
|
||||
}
|
||||
|
||||
expiry4 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: expiry4})
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
// head = 6, tail = 5, insert direction ->
|
||||
// [expiry4, time, time, time, time, nil/tail, time/head, time, time, time]
|
||||
require.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.detach()
|
||||
}
|
||||
expiry5 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: expiry5})
|
||||
|
||||
// head = 6, tail = 5, insert direction ->
|
||||
// [expiry4, time, time, time, time, expiry5, nil/tail, nil, nil, time/head]
|
||||
require.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
// head = 9, tail = 9, insert direction ->
|
||||
// [expiry4, time, time, time, time, expiry5, time, time, time, time/head/tail]
|
||||
require.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1")
|
||||
|
||||
require.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9")
|
||||
require.EqualValues(t, 8, q.binarySearch(time.Now()), "index should be 8")
|
||||
}
|
||||
|
||||
func TestRetrieveExpiry(t *testing.T) {
|
||||
size := 10
|
||||
q := newWorkerLoopQueue(size)
|
||||
expirew := make([]worker, 0)
|
||||
u, _ := time.ParseDuration("1s")
|
||||
|
||||
// test [ time+1s, time+1s, time+1s, time+1s, time+1s, time, time, time, time, time]
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
expirew = append(expirew, q.items[:size/2]...)
|
||||
time.Sleep(u)
|
||||
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
workers := q.refresh(u)
|
||||
|
||||
require.EqualValues(t, expirew, workers, "expired workers aren't right")
|
||||
|
||||
// test [ time, time, time, time, time, time+1s, time+1s, time+1s, time+1s, time+1s]
|
||||
time.Sleep(u)
|
||||
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
expirew = expirew[:0]
|
||||
expirew = append(expirew, q.items[size/2:]...)
|
||||
|
||||
workers2 := q.refresh(u)
|
||||
|
||||
require.EqualValues(t, expirew, workers2, "expired workers aren't right")
|
||||
|
||||
// test [ time+1s, time+1s, time+1s, nil, nil, time+1s, time+1s, time+1s, time+1s, time+1s]
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
for i := 0; i < size/2; i++ {
|
||||
_ = q.detach()
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
time.Sleep(u)
|
||||
|
||||
expirew = expirew[:0]
|
||||
expirew = append(expirew, q.items[0:3]...)
|
||||
expirew = append(expirew, q.items[size/2:]...)
|
||||
|
||||
workers3 := q.refresh(u)
|
||||
|
||||
require.EqualValues(t, expirew, workers3, "expired workers aren't right")
|
||||
}
|
||||
67
worker_queue.go
Normal file
67
worker_queue.go
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// errQueueIsFull will be returned when the worker queue is full.
|
||||
var errQueueIsFull = errors.New("the queue is full")
|
||||
|
||||
type worker interface {
|
||||
run()
|
||||
finish()
|
||||
lastUsedTime() time.Time
|
||||
setLastUsedTime(t time.Time)
|
||||
inputFunc(func())
|
||||
inputArg(any)
|
||||
}
|
||||
|
||||
type workerQueue interface {
|
||||
len() int
|
||||
isEmpty() bool
|
||||
insert(worker) error
|
||||
detach() worker
|
||||
refresh(duration time.Duration) []worker // clean up the stale workers and return them
|
||||
reset()
|
||||
}
|
||||
|
||||
type queueType int
|
||||
|
||||
const (
|
||||
queueTypeStack queueType = 1 << iota
|
||||
queueTypeLoopQueue
|
||||
)
|
||||
|
||||
func newWorkerQueue(qType queueType, size int) workerQueue {
|
||||
switch qType {
|
||||
case queueTypeStack:
|
||||
return newWorkerStack(size)
|
||||
case queueTypeLoopQueue:
|
||||
return newWorkerLoopQueue(size)
|
||||
default:
|
||||
return newWorkerStack(size)
|
||||
}
|
||||
}
|
||||
103
worker_stack.go
Normal file
103
worker_stack.go
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import "time"
|
||||
|
||||
type workerStack struct {
|
||||
items []worker
|
||||
expiry []worker
|
||||
}
|
||||
|
||||
func newWorkerStack(size int) *workerStack {
|
||||
return &workerStack{
|
||||
items: make([]worker, 0, size),
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *workerStack) len() int {
|
||||
return len(ws.items)
|
||||
}
|
||||
|
||||
func (ws *workerStack) isEmpty() bool {
|
||||
return len(ws.items) == 0
|
||||
}
|
||||
|
||||
func (ws *workerStack) insert(w worker) error {
|
||||
ws.items = append(ws.items, w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *workerStack) detach() worker {
|
||||
l := ws.len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := ws.items[l-1]
|
||||
ws.items[l-1] = nil // avoid memory leaks
|
||||
ws.items = ws.items[:l-1]
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func (ws *workerStack) refresh(duration time.Duration) []worker {
|
||||
n := ws.len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
expiryTime := time.Now().Add(-duration)
|
||||
index := ws.binarySearch(0, n-1, expiryTime)
|
||||
|
||||
ws.expiry = ws.expiry[:0]
|
||||
if index != -1 {
|
||||
ws.expiry = append(ws.expiry, ws.items[:index+1]...)
|
||||
m := copy(ws.items, ws.items[index+1:])
|
||||
for i := m; i < n; i++ {
|
||||
ws.items[i] = nil
|
||||
}
|
||||
ws.items = ws.items[:m]
|
||||
}
|
||||
return ws.expiry
|
||||
}
|
||||
|
||||
func (ws *workerStack) binarySearch(l, r int, expiryTime time.Time) int {
|
||||
for l <= r {
|
||||
mid := l + ((r - l) >> 1) // avoid overflow when computing mid
|
||||
if expiryTime.Before(ws.items[mid].lastUsedTime()) {
|
||||
r = mid - 1
|
||||
} else {
|
||||
l = mid + 1
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (ws *workerStack) reset() {
|
||||
for i := 0; i < ws.len(); i++ {
|
||||
ws.items[i].finish()
|
||||
ws.items[i] = nil
|
||||
}
|
||||
ws.items = ws.items[:0]
|
||||
}
|
||||
113
worker_stack_test.go
Normal file
113
worker_stack_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2019. Ants Authors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ants
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewWorkerStack(t *testing.T) {
|
||||
size := 100
|
||||
q := newWorkerStack(size)
|
||||
require.EqualValues(t, 0, q.len(), "Len error")
|
||||
require.Equal(t, true, q.isEmpty(), "IsEmpty error")
|
||||
require.Nil(t, q.detach(), "Dequeue error")
|
||||
}
|
||||
|
||||
func TestWorkerStack(t *testing.T) {
|
||||
q := newWorkerQueue(queueType(-1), 0)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
require.EqualValues(t, 5, q.len(), "Len error")
|
||||
|
||||
expired := time.Now()
|
||||
|
||||
err := q.insert(&goWorker{lastUsed: expired})
|
||||
if err != nil {
|
||||
t.Fatal("Enqueue error")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
err := q.insert(&goWorker{lastUsed: time.Now()})
|
||||
if err != nil {
|
||||
t.Fatal("Enqueue error")
|
||||
}
|
||||
}
|
||||
require.EqualValues(t, 12, q.len(), "Len error")
|
||||
q.refresh(time.Second)
|
||||
require.EqualValues(t, 6, q.len(), "Len error")
|
||||
}
|
||||
|
||||
// It seems that something wrong with time.Now() on Windows, not sure whether it is a bug on Windows,
|
||||
// so exclude this test from Windows platform temporarily.
|
||||
func TestSearch(t *testing.T) {
|
||||
if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows
|
||||
t.Skip("Skip this test on Windows platform")
|
||||
}
|
||||
|
||||
q := newWorkerStack(0)
|
||||
|
||||
// 1
|
||||
expiry1 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, time.Now()), "index should be 0")
|
||||
require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
|
||||
// 2
|
||||
expiry2 := time.Now()
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
|
||||
require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1")
|
||||
|
||||
require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, expiry2), "index should be 0")
|
||||
|
||||
require.EqualValues(t, 1, q.binarySearch(0, q.len()-1, time.Now()), "index should be 1")
|
||||
|
||||
// more
|
||||
for i := 0; i < 5; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
expiry3 := time.Now()
|
||||
|
||||
_ = q.insert(&goWorker{lastUsed: expiry3})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = q.insert(&goWorker{lastUsed: time.Now()})
|
||||
}
|
||||
|
||||
require.EqualValues(t, 7, q.binarySearch(0, q.len()-1, expiry3), "index should be 7")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user