├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── doc.yml ├── .gitignore ├── LICENSE ├── readme.md ├── tests ├── nim.cfg ├── tatomics.nim ├── tbarrier.nim ├── tchannels_cooperative.nim ├── tchannels_simple.nim ├── tchannels_singlebuf.nim ├── tonce.nim ├── tonce2.nim ├── trwlock.nim ├── trwlock1.nim ├── tsemaphore.nim ├── tsemaphore1.nim ├── tsmartptrs.nim ├── tsmartptrsleak.nim └── twaitgroups.nim ├── threading.nimble └── threading ├── atomics.nim ├── barrier.nim ├── channels.nim ├── once.nim ├── rwlock.nim ├── semaphore.nim ├── smartptrs.nim └── waitgroups.nim /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: threading 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | fail-fast: false 8 | max-parallel: 20 9 | matrix: 10 | branch: [master] 11 | target: 12 | - os: linux 13 | cpu: amd64 14 | nim_branch: devel 15 | - os: linux 16 | cpu: amd64 17 | nim_branch: version-2-0 18 | - os: linux 19 | cpu: amd64 20 | nim_branch: version-1-6 21 | # - os: linux 22 | # cpu: i386 23 | # nim_branch: devel 24 | # - os: linux 25 | # cpu: i386 26 | # nim_branch: version-2-0 27 | # - os: linux 28 | # cpu: i386 29 | # nim_branch: version-1-6 30 | - os: macos 31 | cpu: amd64 32 | nim_branch: devel 33 | - os: macos 34 | cpu: amd64 35 | nim_branch: version-2-0 36 | - os: macos 37 | cpu: amd64 38 | nim_branch: version-1-6 39 | - os: windows 40 | cpu: amd64 41 | nim_branch: devel 42 | - os: windows 43 | cpu: amd64 44 | nim_branch: version-2-0 45 | - os: windows 46 | cpu: amd64 47 | nim_branch: version-1-6 48 | - os: windows 49 | cpu: i386 50 | nim_branch: devel 51 | - os: windows 52 | cpu: i386 53 | nim_branch: version-2-0 54 | - os: windows 55 | cpu: i386 56 | nim_branch: version-1-6 57 | include: 58 | - target: 59 | os: linux 60 | builder: ubuntu-latest 61 | - target: 62 | os: macos 63 | builder: macos-latest 64 | - target: 65 | os: windows 66 | builder: windows-latest 67 | 68 | name: '${{ matrix.target.os }}-${{ matrix.target.cpu }}-nim-${{ matrix.target.nim_branch }} (${{ matrix.branch }})' 69 | runs-on: ${{ matrix.builder }} 70 | env: 71 | NIM_DIR: nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }} 72 | NIM_BRANCH: ${{ matrix.target.nim_branch }} 73 | NIM_ARCH: ${{ matrix.target.cpu }} 74 | steps: 75 | - name: Checkout threading 76 | uses: actions/checkout@v4 77 | with: 78 | path: threading 79 | submodules: false 80 | 81 | - name: Restore MinGW-W64 (Windows) from cache 82 | if: runner.os == 'Windows' 83 | id: windows-mingw-cache 84 | uses: actions/cache@v3 85 | with: 86 | path: external/mingw-${{ matrix.target.cpu }} 87 | key: 'mingw-${{ matrix.target.cpu }}' 88 | 89 | - name: Restore Nim DLLs dependencies (Windows) from cache 90 | if: runner.os == 'Windows' 91 | id: windows-dlls-cache 92 | uses: actions/cache@v3 93 | with: 94 | path: external/dlls-${{ matrix.target.cpu }} 95 | key: 'dlls-${{ matrix.target.cpu }}' 96 | 97 | - name: Install MinGW64 dependency (Windows) 98 | if: > 99 | steps.windows-mingw-cache.outputs.cache-hit != 'true' && 100 | runner.os == 'Windows' 101 | shell: bash 102 | run: | 103 | mkdir -p external 104 | if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then 105 | MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.1.0posix-18.1.8-12.0.0-ucrt-r3/winlibs-x86_64-posix-seh-gcc-14.1.0-mingw-w64ucrt-12.0.0-r3.7z" 106 | ARCH=64 107 | else 108 | MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.1.0posix-18.1.8-12.0.0-ucrt-r3/winlibs-i686-posix-dwarf-gcc-14.1.0-mingw-w64ucrt-12.0.0-r3.7z" 109 | ARCH=32 110 | fi 111 | curl -L "$MINGW_URL" -o "external/mingw-${{ matrix.target.cpu }}.7z" 112 | 7z x -y "external/mingw-${{ matrix.target.cpu }}.7z" -oexternal/ 113 | mv external/mingw$ARCH external/mingw-${{ matrix.target.cpu }} 114 | 115 | - name: Install DLLs dependencies (Windows) 116 | if: > 117 | steps.windows-dlls-cache.outputs.cache-hit != 'true' && 118 | runner.os == 'Windows' 119 | shell: bash 120 | run: | 121 | mkdir -p external 122 | curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip 123 | 7z x -y external/windeps.zip -oexternal/dlls-${{ matrix.target.cpu }} 124 | 125 | - name: Path to cached dependencies (Windows) 126 | if: > 127 | runner.os == 'Windows' 128 | shell: bash 129 | run: | 130 | echo '${{ github.workspace }}'"/external/mingw-${{ matrix.target.cpu }}/bin" >> $GITHUB_PATH 131 | echo '${{ github.workspace }}'"/external/dlls-${{ matrix.target.cpu }}" >> $GITHUB_PATH 132 | 133 | - name: Restore Nim from cache 134 | if: > 135 | steps.nim-compiler-cache.outputs.cache-hit != 'true' && 136 | matrix.target.nim_branch != 'devel' 137 | id: nim-compiler-cache 138 | uses: actions/cache@v3 139 | with: 140 | path: '${{ github.workspace }}/nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }}' 141 | key: 'nim-${{ matrix.target.cpu }}-${{ matrix.target.nim_branch }}' 142 | 143 | - name: Setup Nim 144 | uses: alaviss/setup-nim@0.1.1 145 | with: 146 | path: 'nim' 147 | version: ${{ matrix.target.nim_branch }} 148 | architecture: ${{ matrix.target.cpu }} 149 | 150 | - name: Run tests 151 | run: | 152 | cd threading 153 | nimble --threads:on --gc:arc test 154 | 155 | # - name: Build docs 156 | # if: matrix.branch == 'devel' 157 | # run: nimble docs 158 | 159 | # - name: Deploy docs 160 | # # to view docs on your own fork: push a gh-pages branch on your fork, 161 | # # enable gh-pages in your fork 162 | # # and remove `github.ref == 'refs/heads/master'` below 163 | # if: | 164 | # github.event_name == 'push' && github.ref == 'refs/heads/master' && 165 | # matrix.target.os == 'linux' && matrix.target.cpu == 'amd64' && 166 | # matrix.branch == 'devel' 167 | # uses: crazy-max/ghaction-github-pages@v1 168 | # with: 169 | # build_dir: fusion/htmldocs 170 | # target_branch: gh-pages 171 | # env: 172 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 173 | 174 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: docs 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | push: 7 | branches: 8 | - master 9 | env: 10 | nim-src: ${{ github.event.repository.name }} 11 | deploy-dir: .gh-pages 12 | jobs: 13 | docs: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: jiro4989/setup-nim-action@v2 18 | with: 19 | nim-version: 'devel' 20 | - run: nimble install -Y 21 | - run: for file in ${{ env.nim-src }}/*; do nimble doc --index:on --project --git.url:https://github.com/${{ github.repository }} --git.commit:master --out:${{ env.deploy-dir }} $file; done 22 | - name: "Copy to index.html" 23 | run: cp ${{ env.deploy-dir }}/theindex.html ${{ env.deploy-dir }}/index.html 24 | - name: Deploy documents 25 | uses: peaceiris/actions-gh-pages@v4 26 | with: 27 | github_token: ${{ secrets.GITHUB_TOKEN }} 28 | publish_dir: ${{ env.deploy-dir }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | tests/tsmartptrsleak 5 | tests/tchannels_simple 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The Nim programming language 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # threading 2 | 3 | New threading primitives for Nim's `arc`/`orc` memory management modes: atomics, channels, smart pointers and wait groups. 4 | 5 | ## Documentation 6 | 7 | > These modules require `--threads:on`, and one of the `--mm:arc`, `--mm:orc` or `--mm:atomicArc` compilation flags! 8 | 9 | Documentation: https://nim-lang.github.io/threading 10 | 11 | See [tests/](./tests/) for some usage examples. 12 | -------------------------------------------------------------------------------- /tests/nim.cfg: -------------------------------------------------------------------------------- 1 | --path:"../" 2 | --gc:arc 3 | --threads:on 4 | --define:useMalloc 5 | --cc:clang 6 | @if not windows: 7 | --debugger:native 8 | --passc:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" 9 | --passl:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" 10 | @end 11 | @if windows and i386: 12 | --passc:"-m32" 13 | --passl:"-m32" 14 | @end -------------------------------------------------------------------------------- /tests/tatomics.nim: -------------------------------------------------------------------------------- 1 | import std/bitops, threading/atomics 2 | # Atomic operations for trivial objects 3 | 4 | block trivialLoad: 5 | var location: Atomic[int] 6 | location.store(1) 7 | doAssert location.load == 1 8 | location.store(2) 9 | doAssert location.load(Relaxed) == 2 10 | location.store(3) 11 | doAssert location.load(Acquire) == 3 12 | 13 | block trivialStore: 14 | var location: Atomic[int] 15 | location.store(1) 16 | doAssert location.load == 1 17 | location.store(2, Relaxed) 18 | doAssert location.load == 2 19 | location.store(3, Release) 20 | doAssert location.load == 3 21 | 22 | block trivialExchange: 23 | var location: Atomic[int] 24 | location.store(1) 25 | doAssert location.exchange(2) == 1 26 | doAssert location.exchange(3, Relaxed) == 2 27 | doAssert location.exchange(4, Acquire) == 3 28 | doAssert location.exchange(5, Release) == 4 29 | doAssert location.exchange(6, AcqRel) == 5 30 | doAssert location.load == 6 31 | 32 | block trivialCompareExchangeDoesExchange: 33 | var location: Atomic[int] 34 | var expected = 1 35 | location.store(1) 36 | doAssert location.compareExchange(expected, 2) 37 | doAssert expected == 1 38 | doAssert location.load == 2 39 | expected = 2 40 | doAssert location.compareExchange(expected, 3, Relaxed) 41 | doAssert expected == 2 42 | doAssert location.load == 3 43 | expected = 3 44 | doAssert location.compareExchange(expected, 4, Acquire) 45 | doAssert expected == 3 46 | doAssert location.load == 4 47 | expected = 4 48 | doAssert location.compareExchange(expected, 5, Release) 49 | doAssert expected == 4 50 | doAssert location.load == 5 51 | expected = 5 52 | doAssert location.compareExchange(expected, 6, AcqRel) 53 | doAssert expected == 5 54 | doAssert location.load == 6 55 | 56 | block trivialCompareExchangeDoesNotExchange: 57 | var location: Atomic[int] 58 | var expected = 10 59 | location.store(1) 60 | doAssert not location.compareExchange(expected, 2) 61 | doAssert expected == 1 62 | doAssert location.load == 1 63 | expected = 10 64 | doAssert not location.compareExchange(expected, 3, Relaxed) 65 | doAssert expected == 1 66 | doAssert location.load == 1 67 | expected = 10 68 | doAssert not location.compareExchange(expected, 4, Acquire) 69 | doAssert expected == 1 70 | doAssert location.load == 1 71 | expected = 10 72 | doAssert not location.compareExchange(expected, 5, Release) 73 | doAssert expected == 1 74 | doAssert location.load == 1 75 | expected = 10 76 | doAssert not location.compareExchange(expected, 6, AcqRel) 77 | doAssert expected == 1 78 | doAssert location.load == 1 79 | 80 | block trivialCompareExchangeSuccessFailureDoesExchange: 81 | var location: Atomic[int] 82 | var expected = 1 83 | location.store(1) 84 | doAssert location.compareExchange(expected, 2, SeqCst, SeqCst) 85 | doAssert expected == 1 86 | doAssert location.load == 2 87 | expected = 2 88 | doAssert location.compareExchange(expected, 3, Relaxed, Relaxed) 89 | doAssert expected == 2 90 | doAssert location.load == 3 91 | expected = 3 92 | doAssert location.compareExchange(expected, 4, Acquire, Acquire) 93 | doAssert expected == 3 94 | doAssert location.load == 4 95 | expected = 4 96 | doAssert location.compareExchange(expected, 5, Release, Release) 97 | doAssert expected == 4 98 | doAssert location.load == 5 99 | expected = 5 100 | doAssert location.compareExchange(expected, 6, AcqRel, AcqRel) 101 | doAssert expected == 5 102 | doAssert location.load == 6 103 | 104 | block trivialCompareExchangeSuccessFailureDoesNotExchange: 105 | var location: Atomic[int] 106 | var expected = 10 107 | location.store(1) 108 | doAssert not location.compareExchange(expected, 2, SeqCst, SeqCst) 109 | doAssert expected == 1 110 | doAssert location.load == 1 111 | expected = 10 112 | doAssert not location.compareExchange(expected, 3, Relaxed, Relaxed) 113 | doAssert expected == 1 114 | doAssert location.load == 1 115 | expected = 10 116 | doAssert not location.compareExchange(expected, 4, Acquire, Acquire) 117 | doAssert expected == 1 118 | doAssert location.load == 1 119 | expected = 10 120 | doAssert not location.compareExchange(expected, 5, Release, Release) 121 | doAssert expected == 1 122 | doAssert location.load == 1 123 | expected = 10 124 | doAssert not location.compareExchange(expected, 6, AcqRel, AcqRel) 125 | doAssert expected == 1 126 | doAssert location.load == 1 127 | 128 | block trivialCompareExchangeWeakDoesExchange: 129 | var location: Atomic[int] 130 | var expected = 1 131 | location.store(1) 132 | doAssert location.compareExchangeWeak(expected, 2) 133 | doAssert expected == 1 134 | doAssert location.load == 2 135 | expected = 2 136 | doAssert location.compareExchangeWeak(expected, 3, Relaxed) 137 | doAssert expected == 2 138 | doAssert location.load == 3 139 | expected = 3 140 | doAssert location.compareExchangeWeak(expected, 4, Acquire) 141 | doAssert expected == 3 142 | doAssert location.load == 4 143 | expected = 4 144 | doAssert location.compareExchangeWeak(expected, 5, Release) 145 | doAssert expected == 4 146 | doAssert location.load == 5 147 | expected = 5 148 | doAssert location.compareExchangeWeak(expected, 6, AcqRel) 149 | doAssert expected == 5 150 | doAssert location.load == 6 151 | 152 | block trivialCompareExchangeWeakDoesNotExchange: 153 | var location: Atomic[int] 154 | var expected = 10 155 | location.store(1) 156 | doAssert not location.compareExchangeWeak(expected, 2) 157 | doAssert expected == 1 158 | doAssert location.load == 1 159 | expected = 10 160 | doAssert not location.compareExchangeWeak(expected, 3, Relaxed) 161 | doAssert expected == 1 162 | doAssert location.load == 1 163 | expected = 10 164 | doAssert not location.compareExchangeWeak(expected, 4, Acquire) 165 | doAssert expected == 1 166 | doAssert location.load == 1 167 | expected = 10 168 | doAssert not location.compareExchangeWeak(expected, 5, Release) 169 | doAssert expected == 1 170 | doAssert location.load == 1 171 | expected = 10 172 | doAssert not location.compareExchangeWeak(expected, 6, AcqRel) 173 | doAssert expected == 1 174 | doAssert location.load == 1 175 | 176 | block trivialCompareExchangeWeakSuccessFailureDoesExchange: 177 | var location: Atomic[int] 178 | var expected = 1 179 | location.store(1) 180 | doAssert location.compareExchangeWeak(expected, 2, SeqCst, SeqCst) 181 | doAssert expected == 1 182 | doAssert location.load == 2 183 | expected = 2 184 | doAssert location.compareExchangeWeak(expected, 3, Relaxed, Relaxed) 185 | doAssert expected == 2 186 | doAssert location.load == 3 187 | expected = 3 188 | doAssert location.compareExchangeWeak(expected, 4, Acquire, Acquire) 189 | doAssert expected == 3 190 | doAssert location.load == 4 191 | expected = 4 192 | doAssert location.compareExchangeWeak(expected, 5, Release, Release) 193 | doAssert expected == 4 194 | doAssert location.load == 5 195 | expected = 5 196 | doAssert location.compareExchangeWeak(expected, 6, AcqRel, AcqRel) 197 | doAssert expected == 5 198 | doAssert location.load == 6 199 | 200 | block trivialCompareExchangeWeakSuccessFailureDoesNotExchange: 201 | var location: Atomic[int] 202 | var expected = 10 203 | location.store(1) 204 | doAssert not location.compareExchangeWeak(expected, 2, SeqCst, SeqCst) 205 | doAssert expected == 1 206 | doAssert location.load == 1 207 | expected = 10 208 | doAssert not location.compareExchangeWeak(expected, 3, Relaxed, Relaxed) 209 | doAssert expected == 1 210 | doAssert location.load == 1 211 | expected = 10 212 | doAssert not location.compareExchangeWeak(expected, 4, Acquire, Acquire) 213 | doAssert expected == 1 214 | doAssert location.load == 1 215 | expected = 10 216 | doAssert not location.compareExchangeWeak(expected, 5, Release, Release) 217 | doAssert expected == 1 218 | doAssert location.load == 1 219 | expected = 10 220 | doAssert not location.compareExchangeWeak(expected, 6, AcqRel, AcqRel) 221 | doAssert expected == 1 222 | doAssert location.load == 1 223 | 224 | # Numerical operations 225 | 226 | block fetchAdd: 227 | var location: Atomic[int] 228 | doAssert location.fetchAdd(1) == 0 229 | doAssert location.fetchAdd(1, Relaxed) == 1 230 | doAssert location.fetchAdd(1, Release) == 2 231 | doAssert location.load == 3 232 | 233 | block fetchSub: 234 | var location: Atomic[int] 235 | doAssert location.fetchSub(1) == 0 236 | doAssert location.fetchSub(1, Relaxed) == -1 237 | doAssert location.fetchSub(1, Release) == -2 238 | doAssert location.load == -3 239 | 240 | block fetchAnd: 241 | var location: Atomic[int] 242 | 243 | for i in 0..16: 244 | for j in 0..16: 245 | location.store(i) 246 | doAssert(location.fetchAnd(j) == i) 247 | doAssert(location.load == i.bitand(j)) 248 | 249 | for i in 0..16: 250 | for j in 0..16: 251 | location.store(i) 252 | doAssert(location.fetchAnd(j, Relaxed) == i) 253 | doAssert(location.load == i.bitand(j)) 254 | 255 | for i in 0..16: 256 | for j in 0..16: 257 | location.store(i) 258 | doAssert(location.fetchAnd(j, Release) == i) 259 | doAssert(location.load == i.bitand(j)) 260 | 261 | block fetchOr: 262 | var location: Atomic[int] 263 | 264 | for i in 0..16: 265 | for j in 0..16: 266 | location.store(i) 267 | doAssert(location.fetchOr(j) == i) 268 | doAssert(location.load == i.bitor(j)) 269 | 270 | for i in 0..16: 271 | for j in 0..16: 272 | location.store(i) 273 | doAssert(location.fetchOr(j, Relaxed) == i) 274 | doAssert(location.load == i.bitor(j)) 275 | 276 | for i in 0..16: 277 | for j in 0..16: 278 | location.store(i) 279 | doAssert(location.fetchOr(j, Release) == i) 280 | doAssert(location.load == i.bitor(j)) 281 | 282 | block fetchXor: 283 | var location: Atomic[int] 284 | 285 | for i in 0..16: 286 | for j in 0..16: 287 | location.store(i) 288 | doAssert(location.fetchXor(j) == i) 289 | doAssert(location.load == i.bitxor(j)) 290 | 291 | for i in 0..16: 292 | for j in 0..16: 293 | location.store(i) 294 | doAssert(location.fetchXor(j, Relaxed) == i) 295 | doAssert(location.load == i.bitxor(j)) 296 | 297 | for i in 0..16: 298 | for j in 0..16: 299 | location.store(i) 300 | doAssert(location.fetchXor(j, Release) == i) 301 | doAssert(location.load == i.bitxor(j)) 302 | 303 | block atomicInc: 304 | var location: Atomic[int] 305 | location.atomicInc 306 | doAssert location.load == 1 307 | location.atomicInc(1) 308 | doAssert location.load == 2 309 | location += 1 310 | doAssert location.load == 3 311 | 312 | block atomicDec: 313 | var location: Atomic[int] 314 | location.atomicDec 315 | doAssert location.load == -1 316 | location.atomicDec(1) 317 | doAssert location.load == -2 318 | location -= 1 319 | doAssert location.load == -3 320 | -------------------------------------------------------------------------------- /tests/tbarrier.nim: -------------------------------------------------------------------------------- 1 | import threading/barrier, std/[os, strformat] 2 | 3 | const 4 | NumThreads = 10 5 | NumIters = 100 6 | 7 | var 8 | b = createBarrier(NumThreads) 9 | phases: array[NumThreads, int] 10 | threads: array[NumThreads, Thread[int]] 11 | 12 | proc routine(id: int) = 13 | for i in 0..= 2 60 | 61 | 62 | block: 63 | let chan0 = newChan[int]() 64 | let chan1 = chan0 65 | block: 66 | let chan3 = chan0 67 | let chan4 = chan0 68 | -------------------------------------------------------------------------------- /tests/tchannels_singlebuf.nim: -------------------------------------------------------------------------------- 1 | ## Test for and edge case of a channel with a single-element buffer: 2 | ## https://github.com/nim-lang/threading/pull/27#issue-1652851878 3 | ## Also tests `trySend` and `tryRecv` templates. 4 | 5 | import threading/channels, std/os 6 | const Message = "Hello" 7 | 8 | block trySend_recv: 9 | var attempts = 0 10 | 11 | proc test(chan: Chan[string]) {.thread.} = 12 | var notSent = true 13 | let msg = Message 14 | while notSent: 15 | notSent = not chan.trySend(msg) 16 | if notSent: 17 | atomicInc(attempts) 18 | 19 | var chan = newChan[string](elements = 1) 20 | # Fill the channel before spawning the thread 21 | chan.send("Dummy message") 22 | 23 | var thread: Thread[Chan[string]] 24 | createThread(thread, test, chan) 25 | sleep 10 26 | 27 | # Receive the dummy message to make room for the real message 28 | discard chan.recv() 29 | 30 | var dest: string 31 | chan.recv(dest) 32 | doAssert dest == Message 33 | 34 | thread.joinThread() 35 | doAssert attempts > 0, "trySend should have been attempted multiple times" 36 | 37 | 38 | block send_tryRecv: 39 | var attempts = 0 40 | 41 | proc test(chan: Chan[string]) {.thread.} = 42 | var notReceived = true 43 | var msg: string 44 | while notReceived: 45 | notReceived = not chan.tryRecv(msg) 46 | if notReceived: 47 | atomicInc(attempts) 48 | doAssert msg == Message 49 | 50 | var chan = newChan[string](elements = 1) 51 | 52 | var thread: Thread[Chan[string]] 53 | createThread(thread, test, chan) 54 | sleep 10 55 | 56 | let src = Message 57 | chan.send(src) 58 | 59 | thread.joinThread() 60 | doAssert attempts > 0, "tryRecv should have been attempted multiple times" 61 | 62 | -------------------------------------------------------------------------------- /tests/tonce.nim: -------------------------------------------------------------------------------- 1 | import threading/once 2 | 3 | var o = createOnce() 4 | proc smokeOnce() = 5 | var a = 0 6 | o.once(a += 1) 7 | assert a == 1 8 | o.once(a += 1) 9 | assert a == 1 10 | 11 | smokeOnce() 12 | -------------------------------------------------------------------------------- /tests/tonce2.nim: -------------------------------------------------------------------------------- 1 | import threading/once 2 | 3 | const 4 | NumThreads = 10 5 | NumIters = 1000 6 | 7 | type 8 | Singleton = object 9 | data: int 10 | 11 | var 12 | threads: array[NumThreads, Thread[void]] 13 | counter = 1 14 | instance: ptr Singleton 15 | o = createOnce() 16 | 17 | proc getInstance(): ptr Singleton = 18 | once(o): 19 | instance = createSharedU(Singleton) 20 | instance.data = counter 21 | inc counter 22 | result = instance 23 | 24 | proc routine {.thread.} = 25 | for i in 1..NumIters: 26 | assert getInstance().data == 1 27 | 28 | proc main = 29 | for i in 0..= 0) 25 | joinThreads(threads) 26 | assert data == NumIters * NumThreads 27 | 28 | frob() 29 | -------------------------------------------------------------------------------- /tests/trwlock1.nim: -------------------------------------------------------------------------------- 1 | import threading/rwlock, std/[random, os] 2 | 3 | const 4 | NumThreads = 10 5 | NumIters = 100 6 | 7 | var 8 | rw = createRwLock() 9 | data = 0 10 | threads: array[NumThreads, Thread[void]] 11 | 12 | proc routine = 13 | var r = initRand(getThreadId()) 14 | for i in 0..= 0 24 | 25 | proc frob = 26 | for i in 0..= 1.6.0" 11 | -------------------------------------------------------------------------------- /threading/atomics.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2021 Nim Contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## Types and operations for atomic operations and lockless algorithms. 11 | runnableExamples("--threads:on"): 12 | # Atomic 13 | var loc: Atomic[int] 14 | loc.store(4) 15 | assert loc.load == 4 16 | loc.store(2) 17 | assert loc.load(Relaxed) == 2 18 | loc.store(9) 19 | assert loc.load(Acquire) == 9 20 | loc.store(0, Release) 21 | assert loc.load == 0 22 | 23 | assert loc.exchange(7) == 0 24 | assert loc.load == 7 25 | 26 | var expected = 7 27 | assert loc.compareExchange(expected, 5, Relaxed, Relaxed) 28 | assert expected == 7 29 | assert loc.load == 5 30 | 31 | assert not loc.compareExchange(expected, 12, Relaxed, Relaxed) 32 | assert expected == 5 33 | assert loc.load == 5 34 | 35 | assert loc.fetchAdd(1) == 5 36 | assert loc.fetchAdd(2) == 6 37 | assert loc.fetchSub(3) == 8 38 | 39 | loc.atomicInc(1) 40 | assert loc.load == 6 41 | 42 | {.deprecated: "use `std/atomics` instead".} 43 | 44 | when not compileOption("threads"): 45 | {.error: "This module requires --threads:on compilation flag".} 46 | 47 | type 48 | Ordering* {.pure.} = enum 49 | ## Specifies how non-atomic operations can be reordered around atomic 50 | ## operations. 51 | Relaxed 52 | ## No ordering constraints. Only the atomicity and ordering against 53 | ## other atomic operations is guaranteed. 54 | Consume 55 | ## This ordering is currently discouraged as it's semantics are 56 | ## being revised. Acquire operations should be preferred. 57 | Acquire 58 | ## When applied to a load operation, no reads or writes in the 59 | ## current thread can be reordered before this operation. 60 | Release 61 | ## When applied to a store operation, no reads or writes in the 62 | ## current thread can be reorderd after this operation. 63 | AcqRel 64 | ## When applied to a read-modify-write operation, this behaves like 65 | ## both an acquire and a release operation. 66 | SeqCst 67 | ## Behaves like Acquire when applied to load, like Release when 68 | ## applied to a store and like AcquireRelease when applied to a 69 | ## read-modify-write operation. 70 | ## Also guarantees that all threads observe the same total ordering 71 | ## with other SeqCst operations. 72 | 73 | type 74 | Atomic*[T: AtomType] = distinct T ## An atomic object with underlying type `T`. 75 | 76 | proc `=copy`*[T](dst: var Atomic[T]; src: Atomic[T]) = 77 | atomicStoreN(addr T(dst), T(src), AtomicSeqCst) 78 | 79 | proc `=sink`*[T](dst: var Atomic[T]; src: Atomic[T]) = 80 | atomicStoreN(addr T(dst), T(src), AtomicSeqCst) 81 | 82 | proc load*[T](location: var Atomic[T]; order: Ordering = SeqCst): T = 83 | ## Atomically obtains the value of the atomic object. 84 | atomicLoadN(addr T(location), AtomMemModel(order)) 85 | 86 | proc store*[T](location: var Atomic[T]; desired: T; order: Ordering = SeqCst) = 87 | ## Atomically replaces the value of the atomic object with the `desired` 88 | ## value. 89 | atomicStoreN(addr T(location), desired, AtomMemModel(order)) 90 | 91 | proc exchange*[T](location: var Atomic[T]; desired: T; 92 | order: Ordering = SeqCst): T = 93 | ## Atomically replaces the value of the atomic object with the `desired` 94 | ## value and returns the old value. 95 | atomicExchangeN(addr T(location), desired, AtomMemModel(order)) 96 | 97 | proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; 98 | order: Ordering = SeqCst): bool = 99 | ## Atomically compares the value of the atomic object with the `expected` 100 | ## value and performs exchange with the `desired` one if equal or load if 101 | ## not. Returns true if the exchange was successful. 102 | atomicCompareExchangeN(addr T(location), addr expected, desired, 103 | false, AtomMemModel(order), AtomMemModel(order)) 104 | 105 | proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; 106 | success, failure: Ordering): bool = 107 | ## Same as above, but allows for different memory orders for success and 108 | ## failure. 109 | atomicCompareExchangeN(addr T(location), addr expected, desired, 110 | false, AtomMemModel(success), AtomMemModel(failure)) 111 | 112 | proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; 113 | desired: T; order: Ordering = SeqCst): bool = 114 | ## Same as above, but is allowed to fail spuriously. 115 | atomicCompareExchangeN(addr T(location), addr expected, desired, 116 | true, AtomMemModel(order), AtomMemModel(order)) 117 | 118 | proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; 119 | desired: T; success, failure: Ordering): bool = 120 | ## Same as above, but allows for different memory orders for success and 121 | ## failure. 122 | atomicCompareExchangeN(addr T(location), addr expected, desired, 123 | true, AtomMemModel(success), AtomMemModel(failure)) 124 | 125 | # Numerical operations 126 | 127 | proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; 128 | order: Ordering = SeqCst): T = 129 | ## Atomically adds a `value` to the atomic integer and returns the 130 | ## original value. 131 | atomicFetchAdd(addr T(location), value, AtomMemModel(order)) 132 | 133 | proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; 134 | order: Ordering = SeqCst): T = 135 | ## Atomically subtracts a `value` to the atomic integer and returns the 136 | ## original value. 137 | atomicFetchSub(addr T(location), value, AtomMemModel(order)) 138 | 139 | proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; 140 | order: Ordering = SeqCst): T = 141 | ## Atomically replaces the atomic integer with it's bitwise AND 142 | ## with the specified `value` and returns the original value. 143 | atomicFetchAnd(addr T(location), value, AtomMemModel(order)) 144 | 145 | proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; 146 | order: Ordering = SeqCst): T = 147 | ## Atomically replaces the atomic integer with it's bitwise OR 148 | ## with the specified `value` and returns the original value. 149 | atomicFetchOr(addr T(location), value, AtomMemModel(order)) 150 | 151 | proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; 152 | order: Ordering = SeqCst): T = 153 | ## Atomically replaces the atomic integer with it's bitwise XOR 154 | ## with the specified `value` and returns the original value. 155 | atomicFetchXor(addr T(location), value, AtomMemModel(order)) 156 | 157 | proc atomicInc*[T: SomeInteger](location: var Atomic[T]; 158 | value: T = 1) {.inline.} = 159 | ## Atomically increments the atomic integer by some `value`. 160 | discard location.fetchAdd(value) 161 | 162 | proc atomicDec*[T: SomeInteger](location: var Atomic[T]; 163 | value: T = 1) {.inline.} = 164 | ## Atomically decrements the atomic integer by some `value`. 165 | discard location.fetchSub(value) 166 | 167 | proc `+=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = 168 | ## Atomically increments the atomic integer by some `value`. 169 | discard location.fetchAdd(value) 170 | 171 | proc `-=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = 172 | ## Atomically decrements the atomic integer by some `value`. 173 | discard location.fetchSub(value) 174 | -------------------------------------------------------------------------------- /threading/barrier.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2023 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## Barrier for Nim. 10 | 11 | runnableExamples: 12 | 13 | import std / os 14 | 15 | var phases: array[10, int] 16 | var b = createBarrier(10) 17 | 18 | proc worker(id: int) = 19 | for i in 0 ..< 100: 20 | phases[id] = i 21 | if (id + i) mod 10 == 0: 22 | sleep 1 23 | wait b 24 | for j in 0 ..< 10: 25 | assert phases[j] == i 26 | wait b 27 | 28 | var threads: array[10, Thread[int]] 29 | for i in 0..<10: 30 | createThread(threads[i], worker, i) 31 | 32 | joinThreads(threads) 33 | 34 | 35 | import std / locks 36 | 37 | type 38 | Barrier* = object 39 | ## A barrier is a synchronization mechanism that allows a set of threads to 40 | ## all wait for each other to reach a common point. Barriers are useful in 41 | ## programs involving a fixed-size party of cooperating threads that must 42 | ## occasionally wait for each other. The barrier is called a cyclic barrier 43 | ## if it can be reused after the waiting threads are released. 44 | ## 45 | ## The barrier is initialized for a given number of threads. Each thread 46 | ## that calls `wait` on the barrier will block until all the threads have 47 | ## made that call. At this point, the barrier is reset to its initial state 48 | ## and all threads are released. 49 | c: Cond 50 | L: Lock 51 | required: int # number of threads needed for the barrier to continue 52 | left: int # current barrier count, number of threads still needed. 53 | cycle: uint # generation count 54 | 55 | when defined(nimAllowNonVarDestructor): 56 | proc `=destroy`*(b: Barrier) {.inline.} = 57 | let x = addr(b) 58 | deinitCond(x.c) 59 | deinitLock(x.L) 60 | else: 61 | proc `=destroy`*(b: var Barrier) {.inline.} = 62 | deinitCond(b.c) 63 | deinitLock(b.L) 64 | 65 | proc `=sink`*(dest: var Barrier; src: Barrier) {.error.} 66 | proc `=copy`*(dest: var Barrier; src: Barrier) {.error.} 67 | 68 | proc createBarrier*(parties: Natural): Barrier = 69 | result = default(Barrier) 70 | result.required = parties 71 | result.left = parties 72 | initCond(result.c) 73 | initLock(result.L) 74 | 75 | proc wait*(b: var Barrier) = 76 | ## Wait for all threads to reach the barrier. When the last thread reaches 77 | ## the barrier, all threads are released. 78 | acquire(b.L) 79 | dec b.left 80 | if b.left == 0: 81 | inc b.cycle 82 | b.left = b.required 83 | broadcast(b.c) 84 | else: 85 | let cycle = b.cycle 86 | while cycle == b.cycle: 87 | wait(b.c, b.L) 88 | release(b.L) 89 | -------------------------------------------------------------------------------- /threading/channels.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2021 Andreas Prell, Mamy André-Ratsimbazafy & Nim Contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | # This Channel implementation is a shared memory, fixed-size, concurrent queue using 10 | # a circular buffer for data. Based on channels implementation[1]_ by 11 | # Mamy André-Ratsimbazafy (@mratsim), which is a C to Nim translation of the 12 | # original[2]_ by Andreas Prell (@aprell) 13 | # 14 | # .. [1] https://github.com/mratsim/weave/blob/5696d94e6358711e840f8c0b7c684fcc5cbd4472/unused/channels/channels_legacy.nim 15 | # .. [2] https://github.com/aprell/tasking-2.0/blob/master/src/channel_shm/channel.c 16 | 17 | ## This module works only with one of `--mm:arc` / `--mm:atomicArc` / `--mm:orc` 18 | ## compilation flags. 19 | ## 20 | ## .. warning:: This module is experimental and its interface may change. 21 | ## 22 | ## This module implements multi-producer multi-consumer channels - a concurrency 23 | ## primitive with a high-level interface intended for communication and 24 | ## synchronization between threads. It allows sending and receiving typed, isolated 25 | ## data, enabling safe and efficient concurrency. 26 | ## 27 | ## The `Chan` type represents a generic fixed-size channel object that internally manages 28 | ## the underlying resources and synchronization. It has to be initialized using 29 | ## the `newChan` proc. Sending and receiving operations are provided by the 30 | ## blocking `send` and `recv` procs, and non-blocking `trySend` and `tryRecv` 31 | ## procs. Send operations add messages to the channel, receiving operations 32 | ## remove them. 33 | ## 34 | ## See also: 35 | ## * [std/isolation](https://nim-lang.org/docs/isolation.html) 36 | ## 37 | ## The following is a simple example of two different ways to use channels: 38 | ## blocking and non-blocking. 39 | 40 | runnableExamples("--threads:on --gc:orc"): 41 | import std/os 42 | 43 | # In this example a channel is declared at module scope. 44 | # Channels are generic, and they include support for passing objects between 45 | # threads. 46 | # Note that isolated data passed through channels is moved around. 47 | var chan = newChan[string]() 48 | 49 | block example_blocking: 50 | # This proc will be run in another thread. 51 | proc basicWorker() = 52 | chan.send("Hello World!") 53 | 54 | # Launch the worker. 55 | var worker: Thread[void] 56 | createThread(worker, basicWorker) 57 | 58 | # Block until the message arrives, then print it out. 59 | var dest = "" 60 | dest = chan.recv() 61 | assert dest == "Hello World!" 62 | 63 | # Wait for the thread to exit before moving on to the next example. 64 | worker.joinThread() 65 | 66 | block example_non_blocking: 67 | # This is another proc to run in a background thread. This proc takes a while 68 | # to send the message since it first sleeps for some time. 69 | proc slowWorker(delay: Natural) = 70 | # `delay` is a period in milliseconds 71 | sleep(delay) 72 | chan.send("Another message") 73 | 74 | # Launch the worker with a delay set to 2 seconds (2000 ms). 75 | var worker: Thread[Natural] 76 | createThread(worker, slowWorker, 2000) 77 | 78 | # This time, use a non-blocking approach with tryRecv. 79 | # Since the main thread is not blocked, it could be used to perform other 80 | # useful work while it waits for data to arrive on the channel. 81 | var messages: seq[string] 82 | while true: 83 | var msg = "" 84 | if chan.tryRecv(msg): 85 | messages.add msg # "Another message" 86 | break 87 | messages.add "Pretend I'm doing useful work..." 88 | # For this example, sleep in order not to flood the sequence with too many 89 | # "pretend" messages. 90 | sleep(400) 91 | 92 | # Wait for the second thread to exit before cleaning up the channel. 93 | worker.joinThread() 94 | 95 | # Thread exits right after receiving the message 96 | assert messages[^1] == "Another message" 97 | # At least one non-successful attempt to receive the message had to occur. 98 | assert messages.len >= 2 99 | 100 | when not (defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc) or defined(nimdoc)): 101 | {.error: "This module requires one of --mm:arc / --mm:atomicArc / --mm:orc compilation flags".} 102 | 103 | import std/[locks, isolation, atomics] 104 | 105 | # Channel 106 | # ------------------------------------------------------------------------------ 107 | 108 | type 109 | ChannelRaw = ptr ChannelObj 110 | ChannelObj = object 111 | lock: Lock 112 | spaceAvailableCV, dataAvailableCV: Cond 113 | slots: int ## Number of item slots in the buffer 114 | head: Atomic[int] ## Write/enqueue/send index 115 | tail: Atomic[int] ## Read/dequeue/receive index 116 | buffer: ptr UncheckedArray[byte] 117 | atomicCounter: Atomic[int] 118 | 119 | # ------------------------------------------------------------------------------ 120 | 121 | proc getTail(chan: ChannelRaw, order: MemoryOrder = moRelaxed): int {.inline.} = 122 | chan.tail.load(order) 123 | 124 | proc getHead(chan: ChannelRaw, order: MemoryOrder = moRelaxed): int {.inline.} = 125 | chan.head.load(order) 126 | 127 | proc setTail(chan: ChannelRaw, value: int, order: MemoryOrder = moRelaxed) {.inline.} = 128 | chan.tail.store(value, order) 129 | 130 | proc setHead(chan: ChannelRaw, value: int, order: MemoryOrder = moRelaxed) {.inline.} = 131 | chan.head.store(value, order) 132 | 133 | proc setAtomicCounter(chan: ChannelRaw, value: int, order: MemoryOrder = moRelaxed) {.inline.} = 134 | chan.atomicCounter.store(value, order) 135 | 136 | proc numItems(chan: ChannelRaw): int {.inline.} = 137 | result = chan.getHead() - chan.getTail() 138 | if result < 0: 139 | inc(result, 2 * chan.slots) 140 | 141 | assert result <= chan.slots 142 | 143 | template isFull(chan: ChannelRaw): bool = 144 | abs(chan.getHead() - chan.getTail()) == chan.slots 145 | 146 | template isEmpty(chan: ChannelRaw): bool = 147 | chan.getHead() == chan.getTail() 148 | 149 | # Channels memory ops 150 | # ------------------------------------------------------------------------------ 151 | 152 | proc allocChannel(size, n: int): ChannelRaw = 153 | result = cast[ChannelRaw](allocShared(sizeof(ChannelObj))) 154 | 155 | # To buffer n items, we allocate for n 156 | result.buffer = cast[ptr UncheckedArray[byte]](allocShared(n*size)) 157 | 158 | initLock(result.lock) 159 | initCond(result.spaceAvailableCV) 160 | initCond(result.dataAvailableCV) 161 | 162 | result.slots = n 163 | result.setHead(0) 164 | result.setTail(0) 165 | result.setAtomicCounter(0) 166 | 167 | proc freeChannel(chan: ChannelRaw) = 168 | if chan.isNil: 169 | return 170 | 171 | if not chan.buffer.isNil: 172 | deallocShared(chan.buffer) 173 | 174 | deinitLock(chan.lock) 175 | deinitCond(chan.spaceAvailableCV) 176 | deinitCond(chan.dataAvailableCV) 177 | 178 | deallocShared(chan) 179 | 180 | # MPMC Channels (Multi-Producer Multi-Consumer) 181 | # ------------------------------------------------------------------------------ 182 | 183 | proc channelSend(chan: ChannelRaw, data: pointer, size: int, blocking: static bool): bool = 184 | assert not chan.isNil 185 | assert not data.isNil 186 | 187 | when not blocking: 188 | if chan.isFull(): return false 189 | 190 | acquire(chan.lock) 191 | 192 | # check for when another thread was faster to fill 193 | when blocking: 194 | while chan.isFull(): 195 | wait(chan.spaceAvailableCV, chan.lock) 196 | else: 197 | if chan.isFull(): 198 | release(chan.lock) 199 | return false 200 | 201 | assert not chan.isFull() 202 | 203 | let writeIdx = if chan.getHead() < chan.slots: 204 | chan.getHead() 205 | else: 206 | chan.getHead() - chan.slots 207 | 208 | copyMem(chan.buffer[writeIdx * size].addr, data, size) 209 | atomicInc(chan.head) 210 | if chan.getHead() == 2 * chan.slots: 211 | chan.setHead(0) 212 | 213 | signal(chan.dataAvailableCV) 214 | release(chan.lock) 215 | result = true 216 | 217 | proc channelReceive(chan: ChannelRaw, data: pointer, size: int, blocking: static bool): bool = 218 | assert not chan.isNil 219 | assert not data.isNil 220 | 221 | when not blocking: 222 | if chan.isEmpty(): return false 223 | 224 | acquire(chan.lock) 225 | 226 | # check for when another thread was faster to empty 227 | when blocking: 228 | while chan.isEmpty(): 229 | wait(chan.dataAvailableCV, chan.lock) 230 | else: 231 | if chan.isEmpty(): 232 | release(chan.lock) 233 | return false 234 | 235 | assert not chan.isEmpty() 236 | 237 | let readIdx = if chan.getTail() < chan.slots: 238 | chan.getTail() 239 | else: 240 | chan.getTail() - chan.slots 241 | 242 | copyMem(data, chan.buffer[readIdx * size].addr, size) 243 | 244 | atomicInc(chan.tail) 245 | if chan.getTail() == 2 * chan.slots: 246 | chan.setTail(0) 247 | 248 | signal(chan.spaceAvailableCV) 249 | release(chan.lock) 250 | result = true 251 | 252 | # Public API 253 | # ------------------------------------------------------------------------------ 254 | 255 | type 256 | Chan*[T] = object ## Typed channel 257 | d: ChannelRaw 258 | 259 | template frees(c) = 260 | if c.d != nil: 261 | # this `fetchSub` returns current val then subs 262 | # so count == 0 means we're the last 263 | if c.d.atomicCounter.fetchSub(1, moAcquireRelease) == 0: 264 | freeChannel(c.d) 265 | 266 | when defined(nimAllowNonVarDestructor): 267 | proc `=destroy`*[T](c: Chan[T]) = 268 | frees(c) 269 | else: 270 | proc `=destroy`*[T](c: var Chan[T]) = 271 | frees(c) 272 | 273 | proc `=wasMoved`*[T](x: var Chan[T]) = 274 | x.d = nil 275 | 276 | proc `=dup`*[T](src: Chan[T]): Chan[T] = 277 | if src.d != nil: 278 | discard fetchAdd(src.d.atomicCounter, 1, moRelaxed) 279 | result.d = src.d 280 | 281 | proc `=copy`*[T](dest: var Chan[T], src: Chan[T]) = 282 | ## Shares `Channel` by reference counting. 283 | if src.d != nil: 284 | discard fetchAdd(src.d.atomicCounter, 1, moRelaxed) 285 | `=destroy`(dest) 286 | dest.d = src.d 287 | 288 | proc trySend*[T](c: Chan[T], src: sink Isolated[T]): bool {.inline.} = 289 | ## Tries to send the message `src` to the channel `c`. 290 | ## 291 | ## The memory of `src` will be moved if possible. 292 | ## Doesn't block waiting for space in the channel to become available. 293 | ## Instead returns after an attempt to send a message was made. 294 | ## 295 | ## .. warning:: In high-concurrency situations, consider using an exponential 296 | ## backoff strategy to reduce contention and improve the success rate of 297 | ## operations. 298 | ## 299 | ## Returns `false` if the message was not sent because the number of pending 300 | ## messages in the channel exceeded its capacity. 301 | result = channelSend(c.d, src.addr, sizeof(T), false) 302 | if result: 303 | wasMoved(src) 304 | 305 | template trySend*[T](c: Chan[T], src: T): bool = 306 | ## Helper template for `trySend <#trySend,Chan[T],sinkIsolated[T]>`_. 307 | ## 308 | ## .. warning:: For repeated sends of the same value, consider using the 309 | ## `tryTake <#tryTake,Chan[T],Isolated[T]>`_ proc with a pre-isolated 310 | ## value to avoid unnecessary copying. 311 | mixin isolate 312 | trySend(c, isolate(src)) 313 | 314 | proc tryTake*[T](c: Chan[T], src: var Isolated[T]): bool {.inline.} = 315 | ## Tries to send the message `src` to the channel `c`. 316 | ## 317 | ## The memory of `src` is moved directly. Be careful not to reuse `src` afterwards. 318 | ## This proc is suitable when `src` cannot be copied. 319 | ## 320 | ## Doesn't block waiting for space in the channel to become available. 321 | ## Instead returns after an attempt to send a message was made. 322 | ## 323 | ## .. warning:: In high-concurrency situations, consider using an exponential 324 | ## backoff strategy to reduce contention and improve the success rate of 325 | ## operations. 326 | ## 327 | ## Returns `false` if the message was not sent because the number of pending 328 | ## messages in the channel exceeded its capacity. 329 | result = channelSend(c.d, src.addr, sizeof(T), false) 330 | if result: 331 | wasMoved(src) 332 | 333 | proc tryRecv*[T](c: Chan[T], dst: var T): bool {.inline.} = 334 | ## Tries to receive a message from the channel `c` and fill `dst` with its value. 335 | ## 336 | ## Doesn't block waiting for messages in the channel to become available. 337 | ## Instead returns after an attempt to receive a message was made. 338 | ## 339 | ## .. warning:: In high-concurrency situations, consider using an exponential 340 | ## backoff strategy to reduce contention and improve the success rate of 341 | ## operations. 342 | ## 343 | ## Returns `false` and does not change `dist` if no message was received. 344 | channelReceive(c.d, dst.addr, sizeof(T), false) 345 | 346 | proc send*[T](c: Chan[T], src: sink Isolated[T]) {.inline.} = 347 | ## Sends the message `src` to the channel `c`. 348 | ## This blocks the sending thread until `src` was successfully sent. 349 | ## 350 | ## The memory of `src` is moved, not copied. 351 | ## 352 | ## If the channel is already full with messages this will block the thread until 353 | ## messages from the channel are removed. 354 | when defined(gcOrc) and defined(nimSafeOrcSend): 355 | GC_runOrc() 356 | discard channelSend(c.d, src.addr, sizeof(T), true) 357 | wasMoved(src) 358 | 359 | template send*[T](c: Chan[T]; src: T) = 360 | ## Helper template for `send`. 361 | mixin isolate 362 | send(c, isolate(src)) 363 | 364 | proc recv*[T](c: Chan[T], dst: var T) {.inline.} = 365 | ## Receives a message from the channel `c` and fill `dst` with its value. 366 | ## 367 | ## This blocks the receiving thread until a message was successfully received. 368 | ## 369 | ## If the channel does not contain any messages this will block the thread until 370 | ## a message get sent to the channel. 371 | discard channelReceive(c.d, dst.addr, sizeof(T), true) 372 | 373 | proc recv*[T](c: Chan[T]): T {.inline.} = 374 | ## Receives a message from the channel. 375 | ## A version of `recv`_ that returns the message. 376 | discard channelReceive(c.d, result.addr, sizeof(T), true) 377 | 378 | proc recvIso*[T](c: Chan[T]): Isolated[T] {.inline.} = 379 | ## Receives a message from the channel. 380 | ## A version of `recv`_ that returns the message and isolates it. 381 | discard channelReceive(c.d, result.addr, sizeof(T), true) 382 | 383 | proc peek*[T](c: Chan[T]): int {.inline.} = 384 | ## Returns an estimation of the current number of messages held by the channel. 385 | numItems(c.d) 386 | 387 | proc newChan*[T](elements: Positive = 30): Chan[T] = 388 | ## An initialization procedure, necessary for acquiring resources and 389 | ## initializing internal state of the channel. 390 | ## 391 | ## `elements` is the capacity of the channel and thus how many messages it can hold 392 | ## before it refuses to accept any further messages. 393 | result = Chan[T](d: allocChannel(sizeof(T), elements)) 394 | -------------------------------------------------------------------------------- /threading/once.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2023 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## Once for Nim. 10 | 11 | runnableExamples: 12 | 13 | type 14 | Singleton = object 15 | data: int 16 | 17 | var 18 | counter = 1 19 | instance: ptr Singleton 20 | exceptionOccurred = false 21 | o = createOnce() 22 | 23 | proc getInstance(): ptr Singleton = 24 | once(o): 25 | if not exceptionOccurred: 26 | # Simulate an exception on the first call 27 | exceptionOccurred = true 28 | raise newException(ValueError, "Simulated error") 29 | instance = createSharedU(Singleton) 30 | instance.data = counter 31 | inc counter 32 | result = instance 33 | 34 | proc worker {.thread.} = 35 | try: 36 | for i in 1..1000: 37 | let inst = getInstance() 38 | assert inst.data == 1 39 | except ValueError: 40 | echo "Caught expected ValueError" 41 | 42 | var threads: array[10, Thread[void]] 43 | for i in 0..<10: 44 | createThread(threads[i], worker) 45 | joinThreads(threads) 46 | deallocShared(instance) 47 | echo "All threads completed" 48 | 49 | import std / locks 50 | 51 | type 52 | Once* = object 53 | ## Once is a type that allows you to execute a block of code exactly once. 54 | ## The first call to `once` will execute the block of code and all other 55 | ## calls will be ignored. All concurrent calls to `once` are guaranteed to 56 | ## observe any side-effects made by the active call, with no additional 57 | ## synchronization. 58 | state: int 59 | L: Lock 60 | c: Cond 61 | 62 | const 63 | Unset = 0 64 | Pending = 1 65 | Complete = -1 66 | 67 | when defined(nimAllowNonVarDestructor): 68 | proc `=destroy`*(o: Once) {.inline.} = 69 | let x = addr(o) 70 | deinitLock(x.L) 71 | deinitCond(x.c) 72 | else: 73 | proc `=destroy`*(o: var Once) {.inline.} = 74 | deinitLock(o.L) 75 | deinitCond(o.c) 76 | 77 | proc `=sink`*(dest: var Once; source: Once) {.error.} 78 | proc `=copy`*(dest: var Once; source: Once) {.error.} 79 | 80 | proc createOnce*(): Once = 81 | result = default(Once) 82 | initLock(result.L) 83 | initCond(result.c) 84 | 85 | template once*(o: Once, body: untyped) = 86 | ## Executes `body` exactly once. 87 | acquire(o.L) 88 | while o.state == Pending: 89 | wait(o.c, o.L) 90 | if o.state == Unset: 91 | o.state = Pending 92 | release(o.L) 93 | try: 94 | body 95 | acquire(o.L) 96 | o.state = Complete 97 | broadcast(o.c) 98 | release(o.L) 99 | except: 100 | acquire(o.L) 101 | o.state = Unset 102 | broadcast(o.c) 103 | release(o.L) 104 | raise 105 | else: 106 | release(o.L) 107 | -------------------------------------------------------------------------------- /threading/rwlock.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2023 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## Readers-writer lock for Nim. 10 | 11 | runnableExamples: 12 | 13 | import std / os 14 | 15 | var rw = createRwLock() 16 | var data = 0 17 | 18 | proc worker = 19 | for i in 0..<100: 20 | writeWith rw: 21 | let tmp = data 22 | data = -1 23 | sleep 1 24 | data = tmp + 1 25 | 26 | var threads: array[10, Thread[void]] 27 | for i in 0..<10: 28 | createThread(threads[i], worker) 29 | for i in 0..<100: 30 | readWith(rw, assert data >= 0) 31 | joinThreads(threads) 32 | assert data == 1000 33 | 34 | 35 | import std / locks 36 | 37 | type 38 | RwLock* = object 39 | ## Readers-writer lock. Multiple readers can acquire the lock at the same 40 | ## time, but only one writer can acquire the lock at a time. 41 | c: Cond 42 | L: Lock 43 | activeReaders, waitingWriters: int 44 | isWriterActive: bool 45 | 46 | when defined(nimAllowNonVarDestructor): 47 | proc `=destroy`*(rw: RwLock) {.inline.} = 48 | let x = addr(rw) 49 | deinitCond(x.c) 50 | deinitLock(x.L) 51 | else: 52 | proc `=destroy`*(rw: var RwLock) {.inline.} = 53 | deinitCond(rw.c) 54 | deinitLock(rw.L) 55 | 56 | proc `=sink`*(dest: var RwLock; source: RwLock) {.error.} 57 | proc `=copy`*(dest: var RwLock; source: RwLock) {.error.} 58 | 59 | proc createRwLock*(): RwLock = 60 | result = default(RwLock) 61 | initCond(result.c) 62 | initLock(result.L) 63 | 64 | proc beginRead*(rw: var RwLock) = 65 | ## Acquire a read lock. 66 | acquire(rw.L) 67 | while rw.waitingWriters > 0 or rw.isWriterActive: 68 | wait(rw.c, rw.L) 69 | inc rw.activeReaders 70 | release(rw.L) 71 | 72 | proc beginWrite*(rw: var RwLock) = 73 | ## Acquire a write lock. 74 | acquire(rw.L) 75 | inc rw.waitingWriters 76 | while rw.activeReaders > 0 or rw.isWriterActive: 77 | wait(rw.c, rw.L) 78 | dec rw.waitingWriters 79 | rw.isWriterActive = true 80 | release(rw.L) 81 | 82 | proc endRead*(rw: var RwLock) {.inline.} = 83 | ## Release a read lock. 84 | acquire(rw.L) 85 | dec rw.activeReaders 86 | broadcast(rw.c) 87 | release(rw.L) 88 | 89 | proc endWrite*(rw: var RwLock) {.inline.} = 90 | ## Release a write lock. 91 | acquire(rw.L) 92 | rw.isWriterActive = false 93 | broadcast(rw.c) 94 | release(rw.L) 95 | 96 | template readWith*(a: RwLock, body: untyped) = 97 | ## Acquire a read lock and execute `body`. Release the lock afterwards. 98 | beginRead(a) 99 | {.locks: [a].}: 100 | try: 101 | body 102 | finally: 103 | endRead(a) 104 | 105 | template writeWith*(a: RwLock, body: untyped) = 106 | ## Acquire a write lock and execute `body`. Release the lock afterwards. 107 | beginWrite(a) 108 | {.locks: [a].}: 109 | try: 110 | body 111 | finally: 112 | endWrite(a) 113 | -------------------------------------------------------------------------------- /threading/semaphore.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2023 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## Semaphore for Nim. 10 | 11 | runnableExamples: 12 | 13 | import std / os 14 | 15 | var arrived = createSemaphore(2) 16 | 17 | proc worker(i: int) = 18 | echo i, " starts" 19 | wait arrived 20 | sleep 1 21 | echo i, " progresses" 22 | signal arrived 23 | 24 | var threads: array[4, Thread[int]] 25 | for i in 0..<4: 26 | createThread(threads[i], worker, i) 27 | joinThreads(threads) 28 | 29 | 30 | import std/locks 31 | 32 | type 33 | Semaphore* = object 34 | ## A semaphore is a synchronization primitive that controls access to a 35 | ## shared resource through the use of a counter. If the counter is greater 36 | ## than zero, then access is allowed. If it is zero, then access is denied. 37 | ## What the access is depends on the use of the semaphore. 38 | ## 39 | ## Semaphores are typically used to limit the number of threads than can 40 | ## access some (physical or logical) resource. 41 | ## 42 | ## Semaphores are of two types: counting semaphores and binary semaphores. 43 | ## Counting semaphores can take non-negative integer values to indicate the 44 | ## number of units of a particular resource that are available. Binary 45 | ## semaphores can only take the values 0 and 1 and are used to implement 46 | ## locks. 47 | c: Cond 48 | L: Lock 49 | counter: int 50 | 51 | when defined(nimAllowNonVarDestructor): 52 | proc `=destroy`*(s: Semaphore) {.inline.} = 53 | let x = addr(s) 54 | deinitCond(x.c) 55 | deinitLock(x.L) 56 | else: 57 | proc `=destroy`*(s: var Semaphore) {.inline.} = 58 | deinitCond(s.c) 59 | deinitLock(s.L) 60 | 61 | proc `=sink`*(dest: var Semaphore; src: Semaphore) {.error.} 62 | proc `=copy`*(dest: var Semaphore; src: Semaphore) {.error.} 63 | 64 | proc createSemaphore*(count: Natural = 0): Semaphore = 65 | result = default(Semaphore) 66 | result.counter = count 67 | initCond(result.c) 68 | initLock(result.L) 69 | 70 | proc wait*(s: var Semaphore) = 71 | ## Wait for the semaphore to be signaled. If the semaphore's counter is zero, 72 | ## wait blocks until it becomes greater than zero. 73 | acquire(s.L) 74 | while s.counter <= 0: 75 | wait(s.c, s.L) 76 | dec s.counter 77 | release(s.L) 78 | 79 | proc signal*(s: var Semaphore) {.inline.} = 80 | ## Signal the semaphore. 81 | acquire(s.L) 82 | inc s.counter 83 | signal(s.c) 84 | release(s.L) 85 | -------------------------------------------------------------------------------- /threading/smartptrs.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2021 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## C++11 like smart pointers. They always use the shared allocator. 10 | import std/[isolation, atomics] 11 | from typetraits import supportsCopyMem 12 | 13 | proc raiseNilAccess() {.noinline.} = 14 | raise newException(NilAccessDefect, "dereferencing nil smart pointer") 15 | 16 | template checkNotNil(p: typed) = 17 | when compileOption("boundChecks"): 18 | {.line.}: 19 | if p.isNil: 20 | raiseNilAccess() 21 | 22 | type 23 | UniquePtr*[T] = object 24 | ## Non copyable pointer to a value of type `T` with exclusive ownership. 25 | val: ptr T 26 | 27 | when defined(nimAllowNonVarDestructor): 28 | proc `=destroy`*[T](p: UniquePtr[T]) = 29 | if p.val != nil: 30 | `=destroy`(p.val[]) 31 | deallocShared(p.val) 32 | else: 33 | proc `=destroy`*[T](p: var UniquePtr[T]) = 34 | if p.val != nil: 35 | `=destroy`(p.val[]) 36 | deallocShared(p.val) 37 | 38 | proc `=dup`*[T](src: UniquePtr[T]): UniquePtr[T] {.error.} 39 | ## The dup operation is disallowed for `UniquePtr`, it 40 | ## can only be moved. 41 | 42 | proc `=copy`*[T](dest: var UniquePtr[T], src: UniquePtr[T]) {.error.} 43 | ## The copy operation is disallowed for `UniquePtr`, it 44 | ## can only be moved. 45 | 46 | proc newUniquePtr*[T](val: sink Isolated[T]): UniquePtr[T] {.nodestroy.} = 47 | ## Returns a unique pointer which has exclusive ownership of the value. 48 | result.val = cast[ptr T](allocShared(sizeof(T))) 49 | # thanks to '.nodestroy' we don't have to use allocShared0 here. 50 | # This is compiled into a copyMem operation, no need for a sink 51 | # here either. 52 | result.val[] = extract val 53 | # no destructor call for 'val: sink T' here either. 54 | 55 | template newUniquePtr*[T](val: T): UniquePtr[T] = 56 | newUniquePtr(isolate(val)) 57 | 58 | proc newUniquePtr*[T](t: typedesc[T]): UniquePtr[T] = 59 | ## Returns a unique pointer. It is not initialized, 60 | ## so reading from it before writing to it is undefined behaviour! 61 | when not supportsCopyMem(T): 62 | result.val = cast[ptr T](allocShared0(sizeof(T))) 63 | else: 64 | result.val = cast[ptr T](allocShared(sizeof(T))) 65 | 66 | proc isNil*[T](p: UniquePtr[T]): bool {.inline.} = 67 | p.val == nil 68 | 69 | proc `[]`*[T](p: UniquePtr[T]): var T {.inline.} = 70 | ## Returns a mutable view of the internal value of `p`. 71 | checkNotNil(p) 72 | p.val[] 73 | 74 | proc `[]=`*[T](p: UniquePtr[T], val: sink Isolated[T]) {.inline.} = 75 | checkNotNil(p) 76 | p.val[] = extract val 77 | 78 | template `[]=`*[T](p: UniquePtr[T]; val: T) = 79 | `[]=`(p, isolate(val)) 80 | 81 | proc `$`*[T](p: UniquePtr[T]): string {.inline.} = 82 | if p.val == nil: "nil" 83 | else: "(val: " & $p.val[] & ")" 84 | 85 | #------------------------------------------------------------------------------ 86 | 87 | type 88 | SharedPtr*[T] = object 89 | ## Shared ownership reference counting pointer. 90 | val: ptr tuple[value: T, counter: Atomic[int]] 91 | 92 | template frees(p) = 93 | if p.val != nil: 94 | # this `fetchSub` returns current val then subs 95 | # so count == 0 means we're the last 96 | if p.val.counter.fetchSub(1, moAcquireRelease) == 0: 97 | `=destroy`(p.val.value) 98 | deallocShared(p.val) 99 | 100 | when defined(nimAllowNonVarDestructor): 101 | proc `=destroy`*[T](p: SharedPtr[T]) = 102 | frees(p) 103 | else: 104 | proc `=destroy`*[T](p: var SharedPtr[T]) = 105 | frees(p) 106 | 107 | proc `=wasMoved`*[T](p: var SharedPtr[T]) = 108 | p.val = nil 109 | 110 | proc `=dup`*[T](src: SharedPtr[T]): SharedPtr[T] = 111 | if src.val != nil: 112 | discard fetchAdd(src.val.counter, 1, moRelaxed) 113 | result.val = src.val 114 | 115 | proc `=copy`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = 116 | if src.val != nil: 117 | discard fetchAdd(src.val.counter, 1, moRelaxed) 118 | `=destroy`(dest) 119 | dest.val = src.val 120 | 121 | proc newSharedPtr*[T](val: sink Isolated[T]): SharedPtr[T] {.nodestroy.} = 122 | ## Returns a shared pointer which shares 123 | ## ownership of the object by reference counting. 124 | result.val = cast[typeof(result.val)](allocShared(sizeof(result.val[]))) 125 | result.val.counter.store(0, moRelaxed) 126 | result.val.value = extract val 127 | 128 | template newSharedPtr*[T](val: T): SharedPtr[T] = 129 | newSharedPtr(isolate(val)) 130 | 131 | proc newSharedPtr*[T](t: typedesc[T]): SharedPtr[T] = 132 | ## Returns a shared pointer. It is not initialized, 133 | ## so reading from it before writing to it is undefined behaviour! 134 | when not supportsCopyMem(T): 135 | result.val = cast[typeof(result.val)](allocShared0(sizeof(result.val[]))) 136 | else: 137 | result.val = cast[typeof(result.val)](allocShared(sizeof(result.val[]))) 138 | result.val.counter.store(0, moRelaxed) 139 | 140 | proc isNil*[T](p: SharedPtr[T]): bool {.inline.} = 141 | p.val == nil 142 | 143 | proc `[]`*[T](p: SharedPtr[T]): var T {.inline.} = 144 | checkNotNil(p) 145 | p.val.value 146 | 147 | proc `[]=`*[T](p: SharedPtr[T], val: sink Isolated[T]) {.inline.} = 148 | checkNotNil(p) 149 | p.val.value = extract val 150 | 151 | template `[]=`*[T](p: SharedPtr[T]; val: T) = 152 | `[]=`(p, isolate(val)) 153 | 154 | proc isUniqueRef*[T](p: SharedPtr[T]): bool = 155 | if p.val == nil: 156 | return true 157 | p.val.counter.load(moAcquireRelease) == 0 158 | 159 | 160 | proc `$`*[T](p: SharedPtr[T]): string {.inline.} = 161 | if p.val == nil: "nil" 162 | else: "(val: " & $p.val.value & ")" 163 | 164 | #------------------------------------------------------------------------------ 165 | 166 | type 167 | ConstPtr*[T] = distinct SharedPtr[T] 168 | ## Distinct version of `SharedPtr[T]`, which doesn't allow mutating the underlying value. 169 | 170 | proc newConstPtr*[T](val: sink Isolated[T]): ConstPtr[T] {.nodestroy, inline.} = 171 | ## Similar to `newSharedPtr<#newSharedPtr,T>`_, but the underlying value can't be mutated. 172 | ConstPtr[T](newSharedPtr(val)) 173 | 174 | template newConstPtr*[T](val: T): ConstPtr[T] = 175 | newConstPtr(isolate(val)) 176 | 177 | proc isNil*[T](p: ConstPtr[T]): bool {.inline.} = 178 | SharedPtr[T](p).val == nil 179 | 180 | proc `[]`*[T](p: ConstPtr[T]): lent T {.inline.} = 181 | ## Returns an immutable view of the internal value of `p`. 182 | checkNotNil(p) 183 | SharedPtr[T](p).val.value 184 | 185 | proc `[]=`*[T](p: ConstPtr[T], v: T) {.error: "`ConstPtr` cannot be assigned.".} 186 | 187 | proc `$`*[T](p: ConstPtr[T]): string {.inline.} = 188 | $SharedPtr[T](p) 189 | -------------------------------------------------------------------------------- /threading/waitgroups.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2021 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | 9 | ## Wait groups for Nim. 10 | 11 | runnableExamples: 12 | 13 | var data: array[10, int] 14 | var wg = createWaitGroup() 15 | 16 | proc worker(i: int) = 17 | data[i] = 42 18 | wg.leave() 19 | 20 | var threads: array[10, Thread[int]] 21 | wg.enter(10) 22 | for i in 0..<10: 23 | createThread(threads[i], worker, i) 24 | 25 | wg.wait() 26 | for x in data: 27 | assert x == 42 28 | 29 | joinThreads(threads) 30 | 31 | 32 | import std / [locks] 33 | 34 | type 35 | WaitGroup* = object 36 | ## A `WaitGroup` is a synchronization object that can be used to `wait` until 37 | ## all workers have completed. 38 | c: Cond 39 | L: Lock 40 | runningTasks: int 41 | 42 | when defined(nimAllowNonVarDestructor): 43 | proc `=destroy`(b: WaitGroup) {.inline.} = 44 | let x = addr(b) 45 | deinitCond(x.c) 46 | deinitLock(x.L) 47 | else: 48 | proc `=destroy`(b: var WaitGroup) {.inline.} = 49 | deinitCond(b.c) 50 | deinitLock(b.L) 51 | 52 | proc `=copy`(dest: var WaitGroup; src: WaitGroup) {.error.} 53 | proc `=sink`(dest: var WaitGroup; src: WaitGroup) {.error.} 54 | 55 | proc createWaitGroup*(): WaitGroup = 56 | result = default(WaitGroup) 57 | initCond(result.c) 58 | initLock(result.L) 59 | 60 | proc enter*(b: var WaitGroup; delta: Natural = 1) {.inline.} = 61 | ## Tells the `WaitGroup` that one or more workers (the `delta` parameter says 62 | ## how many) "entered", which means to increase the counter that counts how 63 | ## many workers to wait for. 64 | acquire(b.L) 65 | inc b.runningTasks, delta 66 | release(b.L) 67 | 68 | proc leave*(b: var WaitGroup) {.inline.} = 69 | ## Tells the `WaitGroup` that one worker has finished its task. 70 | acquire(b.L) 71 | if b.runningTasks > 0: 72 | dec b.runningTasks 73 | if b.runningTasks == 0: 74 | broadcast(b.c) 75 | release(b.L) 76 | 77 | proc wait*(b: var WaitGroup) = 78 | ## Waits until all workers have completed. 79 | acquire(b.L) 80 | while b.runningTasks > 0: 81 | wait(b.c, b.L) 82 | release(b.L) 83 | --------------------------------------------------------------------------------