├── LICENSE ├── README.md ├── atom.nim ├── barrier.nim ├── bpool.nim ├── bpool.nims ├── conds.nim ├── latch.nim ├── libtasks.nim ├── mutexes.nim ├── pool.nim ├── pool.nims ├── queue.nim ├── semaphore.nim ├── smart.nim ├── test.nim ├── tweave.nim └── utils.nim /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # concurrent 2 | upcoming concurrent library for Nim stdlib 3 | 4 | Ref https://github.com/timotheecour/Nim/issues/693 5 | 6 | The standalone library is under GPL v2 licence. 7 | 8 | This library is only for experimenting not for use. 9 | -------------------------------------------------------------------------------- /atom.nim: -------------------------------------------------------------------------------- 1 | # For the C backend, atomics map to C11 built-ins on GCC and Clang for 2 | # trivial Nim types. Other types are implemented using spin locks. 3 | # This could be overcome by supporting advanced importc-patterns. 4 | 5 | # Since MSVC does not implement C11, we fall back to MS intrinsics 6 | # where available. 7 | 8 | type 9 | Trivial = SomeNumber | bool | enum | ptr | pointer 10 | # A type that is known to be atomic and whose size is known at 11 | # compile time to be 8 bytes or less 12 | 13 | template nonAtomicType*(T: typedesc[Trivial]): untyped = 14 | # Maps types to integers of the same size 15 | when sizeof(T) == 1: int8 16 | elif sizeof(T) == 2: int16 17 | elif sizeof(T) == 4: int32 18 | elif sizeof(T) == 8: int64 19 | 20 | when defined(vcc): 21 | 22 | # TODO: Trivial types should be volatile and use VC's special volatile 23 | # semantics for store and loads. 24 | 25 | type 26 | MemoryOrder* = enum 27 | moRelaxed 28 | moConsume 29 | moAcquire 30 | moRelease 31 | moAcquireRelease 32 | moSequentiallyConsistent 33 | 34 | Atomic*[T] = object 35 | when T is Trivial: 36 | value: T.nonAtomicType 37 | else: 38 | nonAtomicValue: T 39 | guard: AtomicFlag 40 | 41 | AtomicFlag* = distinct int8 42 | 43 | {.push header: "".} 44 | 45 | # MSVC intrinsics 46 | proc interlockedExchange(location: pointer; desired: int8): int8 {.importc: "_InterlockedExchange8".} 47 | proc interlockedExchange(location: pointer; desired: int16): int16 {.importc: "_InterlockedExchange".} 48 | proc interlockedExchange(location: pointer; desired: int32): int32 {.importc: "_InterlockedExchange16".} 49 | proc interlockedExchange(location: pointer; desired: int64): int64 {.importc: "_InterlockedExchange64".} 50 | 51 | proc interlockedCompareExchange(location: pointer; desired, expected: int8): int8 {.importc: "_InterlockedCompareExchange8".} 52 | proc interlockedCompareExchange(location: pointer; desired, expected: int16): int16 {.importc: "_InterlockedCompareExchange16".} 53 | proc interlockedCompareExchange(location: pointer; desired, expected: int32): int32 {.importc: "_InterlockedCompareExchange".} 54 | proc interlockedCompareExchange(location: pointer; desired, expected: int64): int64 {.importc: "_InterlockedCompareExchange64".} 55 | 56 | proc interlockedAnd(location: pointer; value: int8): int8 {.importc: "_InterlockedAnd8".} 57 | proc interlockedAnd(location: pointer; value: int16): int16 {.importc: "_InterlockedAnd16".} 58 | proc interlockedAnd(location: pointer; value: int32): int32 {.importc: "_InterlockedAnd".} 59 | proc interlockedAnd(location: pointer; value: int64): int64 {.importc: "_InterlockedAnd64".} 60 | 61 | proc interlockedOr(location: pointer; value: int8): int8 {.importc: "_InterlockedOr8".} 62 | proc interlockedOr(location: pointer; value: int16): int16 {.importc: "_InterlockedOr16".} 63 | proc interlockedOr(location: pointer; value: int32): int32 {.importc: "_InterlockedOr".} 64 | proc interlockedOr(location: pointer; value: int64): int64 {.importc: "_InterlockedOr64".} 65 | 66 | proc interlockedXor(location: pointer; value: int8): int8 {.importc: "_InterlockedXor8".} 67 | proc interlockedXor(location: pointer; value: int16): int16 {.importc: "_InterlockedXor16".} 68 | proc interlockedXor(location: pointer; value: int32): int32 {.importc: "_InterlockedXor".} 69 | proc interlockedXor(location: pointer; value: int64): int64 {.importc: "_InterlockedXor64".} 70 | 71 | proc fence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} 72 | proc signalFence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} 73 | 74 | {.pop.} 75 | 76 | proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool = 77 | interlockedOr(addr(location), 1'i8) == 1'i8 78 | proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) = 79 | discard interlockedAnd(addr(location), 0'i8) 80 | 81 | proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 82 | cast[T](interlockedOr(addr(location.value), (nonAtomicType(T))0)) 83 | proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = 84 | discard interlockedExchange(addr(location.value), cast[nonAtomicType(T)](desired)) 85 | 86 | proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 87 | cast[T](interlockedExchange(addr(location.value), cast[int64](desired))) 88 | proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 89 | cast[T](interlockedCompareExchange(addr(location.value), cast[nonAtomicType(T)](desired), cast[nonAtomicType(T)](expected))) == expected 90 | proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 91 | compareExchange(location, expected, desired, order, order) 92 | proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 93 | compareExchange(location, expected, desired, success, failure) 94 | proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 95 | compareExchangeWeak(location, expected, desired, order, order) 96 | 97 | proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 98 | var currentValue = location.load() 99 | while not compareExchangeWeak(location, currentValue, currentValue + value): discard 100 | proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 101 | fetchAdd(location, -value, order) 102 | proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 103 | cast[T](interlockedAnd(addr(location.value), cast[nonAtomicType(T)](value))) 104 | proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 105 | cast[T](interlockedOr(addr(location.value), cast[nonAtomicType(T)](value))) 106 | proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 107 | cast[T](interlockedXor(addr(location.value), cast[nonAtomicType(T)](value))) 108 | 109 | else: 110 | {.push, header: "".} 111 | 112 | type 113 | MemoryOrder* {.importc: "memory_order".} = enum 114 | moRelaxed 115 | moConsume 116 | moAcquire 117 | moRelease 118 | moAcquireRelease 119 | moSequentiallyConsistent 120 | 121 | type 122 | AtomicInt8 {.importc: "_Atomic NI8", size: 1.} = object 123 | AtomicInt16 {.importc: "_Atomic NI16", size: 2.} = object 124 | AtomicInt32 {.importc: "_Atomic NI32", size: 4.} = object 125 | AtomicInt64 {.importc: "_Atomic NI64", size: 8.} = object 126 | 127 | template atomicType*(T: typedesc[Trivial]): untyped = 128 | # Maps the size of a trivial type to it's internal atomic type 129 | when sizeof(T) == 1: AtomicInt8 130 | elif sizeof(T) == 2: AtomicInt16 131 | elif sizeof(T) == 4: AtomicInt32 132 | elif sizeof(T) == 8: AtomicInt64 133 | 134 | type 135 | AtomicFlag* {.importc: "atomic_flag", size: 1.} = object 136 | 137 | Atomic*[T] = object 138 | when T is Trivial: 139 | value: T.atomicType 140 | else: 141 | nonAtomicValue: T 142 | guard: AtomicFlag 143 | 144 | #proc init*[T](location: var Atomic[T]; value: T): T {.importcpp: "atomic_init(@)".} 145 | proc atomic_load_explicit[T, A](location: ptr A; order: MemoryOrder): T {.importc.} 146 | proc atomic_store_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.importc.} 147 | proc atomic_exchange_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 148 | proc atomic_compare_exchange_strong_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} 149 | proc atomic_compare_exchange_weak_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} 150 | 151 | # Numerical operations 152 | proc atomic_fetch_add_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 153 | proc atomic_fetch_sub_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 154 | proc atomic_fetch_and_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 155 | proc atomic_fetch_or_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 156 | proc atomic_fetch_xor_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} 157 | 158 | proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool {.importc: "atomic_flag_test_and_set_explicit".} 159 | proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) {.importc: "atomic_flag_clear_explicit".} 160 | 161 | proc fence*(order: MemoryOrder) {.importc: "atomic_thread_fence".} 162 | proc signalFence*(order: MemoryOrder) {.importc: "atomic_signal_fence".} 163 | 164 | {.pop.} 165 | 166 | proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 167 | cast[T](atomic_load_explicit[nonAtomicType(T), typeof(location.value)](addr(location.value), order)) 168 | proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = 169 | atomic_store_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order) 170 | proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 171 | cast[T](atomic_exchange_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order)) 172 | proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 173 | atomic_compare_exchange_strong_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) 174 | proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 175 | compareExchange(location, expected, desired, order, order) 176 | 177 | proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 178 | atomic_compare_exchange_weak_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) 179 | proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 180 | compareExchangeWeak(location, expected, desired, order, order) 181 | 182 | # Numerical operations 183 | proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 184 | cast[T](atomic_fetch_add_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) 185 | proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 186 | cast[T](atomic_fetch_sub_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) 187 | proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 188 | cast[T](atomic_fetch_and_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) 189 | proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 190 | cast[T](atomic_fetch_or_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) 191 | proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 192 | cast[T](atomic_fetch_xor_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) 193 | 194 | 195 | proc `=copy`*[T](x: var Atomic[T], y: Atomic[T]) {.error.} 196 | 197 | # Flag operations 198 | proc init*(location: var AtomicFlag) {.inline.} = clear(location) 199 | 200 | template withLock[T: not Trivial](location: var Atomic[T]; order: MemoryOrder; body: untyped): untyped = 201 | while location.guard.testAndSet(moAcquire): discard 202 | try: 203 | body 204 | finally: 205 | location.guard.clear(moRelease) 206 | 207 | proc load*[T: not Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 208 | withLock(location, order): 209 | result = location.nonAtomicValue 210 | 211 | proc store*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = 212 | withLock(location, order): 213 | location.nonAtomicValue = desired 214 | 215 | proc exchange*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = 216 | withLock(location, order): 217 | result = location.nonAtomicValue 218 | location.nonAtomicValue = desired 219 | 220 | proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 221 | withLock(location, success): 222 | if location.nonAtomicValue != expected: 223 | expected = location.nonAtomicValue 224 | return false 225 | expected = desired 226 | swap(location.nonAtomicValue, expected) 227 | return true 228 | 229 | proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = 230 | compareExchange(location, expected, desired, success, failure) 231 | 232 | proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 233 | compareExchange(location, expected, desired, order, order) 234 | 235 | proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = 236 | compareExchangeWeak(location, expected, desired, order, order) 237 | 238 | proc atomicInc*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = 239 | ## Atomically increments the atomic integer by some `value`. 240 | discard location.fetchAdd(value) 241 | 242 | proc atomicDec*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = 243 | ## Atomically decrements the atomic integer by some `value`. 244 | discard location.fetchSub(value) 245 | 246 | proc `+=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = 247 | ## Atomically increments the atomic integer by some `value`. 248 | discard location.fetchAdd(value) 249 | 250 | proc `-=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = 251 | ## Atomically decrements the atomic integer by some `value`. 252 | discard location.fetchSub(value) 253 | 254 | 255 | import std/os 256 | 257 | proc wakeByAddressAll(address: pointer) {.stdcall, importc: "WakeByAddressAll", 258 | dynlib: "API-MS-Win-Core-Synch-l1-2-0.dll".} 259 | 260 | 261 | proc wakeByAddressSingle(address: pointer) {.stdcall, importc: "WakeByAddressSingle", 262 | dynlib: "API-MS-Win-Core-Synch-l1-2-0.dll".} 263 | 264 | proc signal*[T](location: var Atomic[T]) = 265 | when T is Trivial: 266 | wakeByAddressSingle(addr location.value) 267 | else: 268 | wakeByAddressSingle(addr location.nonAtomicValue) 269 | 270 | 271 | proc broadcast*[T](location: var Atomic[T]) = 272 | when T is Trivial: 273 | wakeByAddressAll(addr location.value) 274 | else: 275 | wakeByAddressAll(addr location.nonAtomicValue) 276 | 277 | proc wait*[T](location: var Atomic[T]) = 278 | sleep(0) 279 | -------------------------------------------------------------------------------- /barrier.nim: -------------------------------------------------------------------------------- 1 | block: # Locked based barrier 2 | type 3 | Barrier = object 4 | 5 | 6 | 7 | # import nlock 8 | 9 | # type 10 | # Barrier* = object 11 | # counter: int 12 | # cv: Cond 13 | # size: int 14 | 15 | # proc `=copy`*(x: var Barrier, y: Barrier) {.error.} 16 | 17 | # proc `=destroy`*(b: var Barrier) = 18 | # `=destroy`(b.cv) 19 | 20 | # proc init*(b: var Barrier, size: int) = 21 | # b.counter = 0 22 | # b.cv.init 23 | 24 | # proc enter*(b: var Barrier) = 25 | # atomicInc b.counter 26 | 27 | # proc leave*(b: var Barrier) = 28 | # atomicDec b.counter 29 | # if b.counter <= 0: signal(b.cv) 30 | -------------------------------------------------------------------------------- /bpool.nim: -------------------------------------------------------------------------------- 1 | import std/tasks 2 | import system/ansi_c 3 | 4 | import std/locks 5 | import macros 6 | 7 | import queue 8 | 9 | 10 | type 11 | StaticThreadPool = object 12 | pool: seq[Thread[pointer]] 13 | tasks: BoundedQueue[Task] 14 | lock: Lock 15 | isStop: bool 16 | notEmptyCond: Cond 17 | 18 | proc `=copy`(x: var StaticThreadPool, y: StaticThreadPool) {.error.} 19 | 20 | proc `=destroy`(x: var StaticThreadPool) = 21 | `=destroy`(x.tasks) 22 | `=destroy`(x.notEmptyCond) 23 | 24 | proc work(t: pointer) {.gcsafe.} = 25 | var pool = cast[ptr StaticThreadPool](t) 26 | assert pool != nil 27 | while true: 28 | withLock pool.lock: 29 | while pool.tasks.len == 0 and not pool.isStop: 30 | wait(pool.notEmptyCond, pool.lock) 31 | 32 | if pool.isStop: 33 | if pool.tasks.len != 0: 34 | let task = pool.tasks.pop 35 | release(pool.lock) 36 | task.invoke() 37 | else: 38 | release(pool.lock) 39 | break 40 | 41 | # if pool.tasks.len == 0: 42 | # continue 43 | let task = pool.tasks.pop 44 | release(pool.lock) 45 | task.invoke() 46 | 47 | proc init(result: var StaticThreadPool, num: Positive) = 48 | result.pool.setLen(num) 49 | init(result.tasks, 200) 50 | initLock(result.lock) 51 | initCond(result.notEmptyCond) 52 | 53 | for t in result.pool.mitems: 54 | createThread(t, work, cast[pointer](addr result)) 55 | 56 | 57 | macro spawn(pool: var StaticThreadPool, call: typed) = 58 | result = quote do: 59 | var task = toTask(`call`) 60 | withLock `pool`.lock: 61 | `pool`.tasks.add move task 62 | signal(`pool`.notEmptyCond) 63 | 64 | proc stop(pool: var StaticThreadPool) = 65 | withLock pool.lock: 66 | pool.isStop = true 67 | broadcast(pool.notEmptyCond) 68 | for t in mitems(pool.pool): 69 | joinThread(t) 70 | 71 | proc hello(a: int) = echo a 72 | 73 | 74 | var pool: StaticThreadPool 75 | init(pool, 12) 76 | for i in 0 .. 10: 77 | pool.spawn hello(i) 78 | 79 | proc foo(x: string) = echo x 80 | 81 | for i in 0 .. 10: 82 | pool.spawn foo("string: " & $i) 83 | 84 | 85 | import os 86 | 87 | sleep(1000) 88 | 89 | pool.stop() 90 | -------------------------------------------------------------------------------- /bpool.nims: -------------------------------------------------------------------------------- 1 | --threads:on -------------------------------------------------------------------------------- /conds.nim: -------------------------------------------------------------------------------- 1 | type 2 | Cond* = object 3 | cond: SysCond ## Nim condition variable 4 | 5 | 6 | proc init*(cond: var Cond) {.inline.} = 7 | ## Initializes the given condition variable. 8 | initSysCond(cond.cond) 9 | 10 | proc `=destroy`*(cond: var Cond) {.inline.} = 11 | ## Frees the resources associated with the condition variable. 12 | deinitSysCond(cond.cond) 13 | 14 | proc wait*(cond: var Cond, lock: var Mutex) {.inline.} = 15 | ## waits on the condition variable `cond`. 16 | waitSysCond(cond.cond, lock.lock) 17 | 18 | proc wait*(cond: var Cond, lock: var RLock) {.inline.} = 19 | ## waits on the condition variable `cond`. 20 | waitSysCond(cond.cond, lock.lock) 21 | 22 | proc signal*(cond: var Cond) {.inline.} = 23 | ## sends a signal to the condition variable `cond`. 24 | signalSysCond(cond.cond) 25 | 26 | proc broadcast*(cond: var Cond) {.inline.} = 27 | ## Unblocks all threads currently blocked on the 28 | ## specified condition variable `cond`. 29 | broadcastSysCond(cond.cond) 30 | -------------------------------------------------------------------------------- /latch.nim: -------------------------------------------------------------------------------- 1 | import nlock 2 | import utils 3 | 4 | # Locked based barrier 5 | type 6 | Latch* = object of NonCopyable 7 | cond: Cond 8 | lock: Lock 9 | count: int 10 | 11 | proc init*(x: var Latch, count: int) = 12 | init(x.cond) 13 | init(x.lock) 14 | x.count = count 15 | 16 | proc `=destroy`*(x: var Latch) = 17 | `=destroy`(x.cond) 18 | `=destroy`(x.lock) 19 | 20 | proc dec*(x: var Latch) = 21 | acquire x.lock 22 | dec x.count 23 | if x.count == 0: 24 | broadcast(x.cond) 25 | release x.lock 26 | 27 | proc wait*(x: var Latch) = 28 | acquire x.lock 29 | while x.count != 0: 30 | x.cond.wait(x.lock) 31 | release x.lock 32 | 33 | 34 | proc main() = 35 | var x: Latch 36 | init(x, 10) 37 | var y = x 38 | var z = y 39 | 40 | 41 | main() 42 | 43 | 44 | type 45 | Barrier* = object of Latch 46 | generation: int 47 | size: int 48 | 49 | 50 | proc init*(x: var Barrier, count: int) = 51 | init(x.cond) 52 | init(x.lock) 53 | x.count = count 54 | x.size = count 55 | x.generation = 0 56 | 57 | proc wait*(x: var Barrier) = 58 | withLock x.lock: 59 | dec x.count 60 | if x.count == 0: 61 | inc x.generation 62 | x.count = x.size 63 | broadcast(x.cond) 64 | else: 65 | let generation = x.generation 66 | while generation == x.generation: 67 | x.cond.wait(x.lock) 68 | 69 | 70 | when isMainModule: 71 | import std/threadpool 72 | 73 | block: 74 | var workDone: Latch 75 | var cleanup: Latch 76 | 77 | init(workDone, 3) 78 | init(cleanup, 1) 79 | 80 | proc hello(name: string) = 81 | echo "work: " & name 82 | dec workDone 83 | wait cleanup 84 | echo "clean: " & name 85 | 86 | 87 | spawn hello("1") 88 | spawn hello("2") 89 | spawn hello("3") 90 | 91 | workDone.wait() 92 | 93 | dec cleanup 94 | 95 | sync() 96 | 97 | block: 98 | var workDone: Barrier 99 | 100 | init(workDone, 3) 101 | 102 | proc hello(name: string) = 103 | echo "work: " & name 104 | wait workDone 105 | echo "clean: " & name 106 | 107 | 108 | spawn hello("1") 109 | spawn hello("2") 110 | spawn hello("3") 111 | 112 | sync() 113 | 114 | 115 | # import atom 116 | 117 | # type 118 | # Latch* = object 119 | # value: Atomic[int] 120 | 121 | 122 | # proc `=copy`*(x: var Latch, y: Latch) {.error.} 123 | 124 | # proc init*(latch: var Latch, count: int) = 125 | # latch.value.store(count, moRelaxed) 126 | 127 | # proc countDown(latch: var Latch, value = 1) = 128 | # if fetchSub(latch.value, value, moRelease) == 1: 129 | # broadcast(latch.value) 130 | 131 | # proc tryWait(latch: var Latch): bool = 132 | # latch.value.load(moAcquire) == 0 133 | 134 | # proc wait(latch: var Latch) = 135 | # latch.value.wait() -------------------------------------------------------------------------------- /libtasks.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 | ## This module provides basic primitives for creating parallel programs. 11 | ## A `Task` should be only owned by a single Thread, it cannot be shared by threads. 12 | 13 | import std/[macros, isolation, typetraits] 14 | import system/ansi_c 15 | 16 | export isolation 17 | 18 | 19 | when compileOption("threads"): 20 | from std/effecttraits import isGcSafe 21 | 22 | 23 | # 24 | # proc hello(a: int, b: string) = 25 | # echo $a & b 26 | # 27 | # let literal = "Nim" 28 | # let t = toTask(hello(521, literal)) 29 | # 30 | # 31 | # is roughly converted to 32 | # 33 | # type 34 | # ScratchObj_369098780 = object 35 | # a: int 36 | # b: string 37 | # 38 | # let scratch_369098762 = cast[ptr ScratchObj_369098780](c_calloc(csize_t 1, 39 | # csize_t sizeof(ScratchObj_369098780))) 40 | # if scratch_369098762.isNil: 41 | # raise newException(OutOfMemDefect, "Could not allocate memory") 42 | # block: 43 | # var isolate_369098776 = isolate(521) 44 | # scratch_369098762.a = extract(isolate_369098776) 45 | # var isolate_369098778 = isolate(literal) 46 | # scratch_369098762.b = extract(isolate_369098778) 47 | # proc hello_369098781(args`gensym3: pointer) {.nimcall.} = 48 | # let objTemp_369098775 = cast[ptr ScratchObj_369098780](args`gensym3) 49 | # let :tmp_369098777 = objTemp_369098775.a 50 | # let :tmp_369098779 = objTemp_369098775.b 51 | # hello(a = :tmp_369098777, b = :tmp_369098779) 52 | # 53 | # proc destroyScratch_369098782(args`gensym3: pointer) {.nimcall.} = 54 | # let obj_369098783 = cast[ptr ScratchObj_369098780](args`gensym3) 55 | # =destroy(obj_369098783[]) 56 | # let t = Task(callback: hello_369098781, args: scratch_369098762, destroy: destroyScratch_369098782) 57 | # 58 | 59 | 60 | type 61 | Task* = object ## `Task` contains the callback and its arguments. 62 | callback: proc (args: pointer) {.nimcall, gcsafe.} 63 | args: pointer 64 | destroy: proc (args: pointer) {.nimcall.} 65 | 66 | 67 | RawTask* = distinct Task 68 | 69 | 70 | proc `=copy`*(x: var Task, y: Task) {.error.} 71 | 72 | proc `=destroy`*(t: var Task) {.inline.} = 73 | ## Frees the resources allocated for a `Task`. 74 | if t.args != nil: 75 | if t.destroy != nil: 76 | t.destroy(t.args) 77 | c_free(t.args) 78 | 79 | proc invoke*(task: Task) {.inline, gcsafe.} = 80 | ## Invokes the `task`. 81 | assert task.callback != nil 82 | task.callback(task.args) 83 | 84 | proc `=copy`*(x: var RawTask, y: RawTask) {.error.} 85 | 86 | proc `=destroy`*(t: var RawTask) {.inline.} = 87 | `=destroy`(Task(t)) 88 | 89 | proc invoke*(task: RawTask) {.borrow, gcsafe.} 90 | 91 | template checkIsolate(scratchAssignList: seq[NimNode], procParam, scratchDotExpr: NimNode, isRaw: bool) = 92 | # block: 93 | # var isoTempA = isolate(521) 94 | # scratch.a = extract(isolateA) 95 | # var isoTempB = isolate(literal) 96 | # scratch.b = extract(isolateB) 97 | 98 | if isRaw: 99 | scratchAssignList.add newAssignment(scratchDotExpr, 100 | procParam) 101 | else: 102 | let isolatedTemp = genSym(nskTemp, "isoTemp") 103 | scratchAssignList.add newVarStmt(isolatedTemp, newCall(newidentNode("isolate"), procParam)) 104 | scratchAssignList.add newAssignment(scratchDotExpr, 105 | newcall(newIdentNode("extract"), isolatedTemp)) 106 | 107 | template addAllNode(assignParam: NimNode, procParam: NimNode, isRaw: bool) = 108 | let scratchDotExpr = newDotExpr(scratchIdent, formalParams[i][0]) 109 | 110 | checkIsolate(scratchAssignList, procParam, scratchDotExpr, isRaw) 111 | 112 | let tempNode = genSym(kind = nskTemp, ident = formalParams[i][0].strVal) 113 | callNode.add nnkExprEqExpr.newTree(formalParams[i][0], tempNode) 114 | tempAssignList.add newLetStmt(tempNode, newDotExpr(objTemp, formalParams[i][0])) 115 | scratchRecList.add newIdentDefs(newIdentNode(formalParams[i][0].strVal), assignParam) 116 | 117 | macro toTaskImpl(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}, isRaw: static bool): Task = 118 | ## Converts the call and its arguments to `Task`. 119 | runnableExamples("--gc:orc"): 120 | proc hello(a: int) = echo a 121 | 122 | let b = toTask hello(13) 123 | assert b is Task 124 | 125 | doAssert getTypeInst(e).typeKind == ntyVoid 126 | 127 | when compileOption("threads"): 128 | if not isGcSafe(e[0]): 129 | error("'toTask' takes a GC safe call expression") 130 | 131 | if hasClosure(e[0]): 132 | error("closure call is not allowed") 133 | 134 | if e.len > 1: 135 | let scratchIdent = genSym(kind = nskTemp, ident = "scratch") 136 | let impl = e[0].getTypeInst 137 | 138 | when defined(nimTasksDebug): 139 | echo impl.treeRepr 140 | echo e.treeRepr 141 | let formalParams = impl[0] 142 | 143 | var 144 | scratchRecList = newNimNode(nnkRecList) 145 | scratchAssignList: seq[NimNode] 146 | tempAssignList: seq[NimNode] 147 | callNode: seq[NimNode] 148 | 149 | let 150 | objTemp = genSym(nskTemp, ident = "objTemp") 151 | 152 | for i in 1 ..< formalParams.len: 153 | var param = formalParams[i][1] 154 | 155 | if param.kind == nnkBracketExpr and param[0].eqIdent("sink"): 156 | param = param[0] 157 | 158 | if param.typeKind in {ntyExpr, ntyStmt}: 159 | error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter") 160 | 161 | case param.kind 162 | of nnkVarTy: 163 | if isRaw: 164 | addAllNode(param[0], e[i], isRaw) 165 | else: 166 | error("'toTask'ed function cannot have a 'var' parameter") 167 | of nnkBracketExpr: 168 | if param[0].typeKind == ntyTypeDesc: 169 | callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i]) 170 | elif param[0].typeKind in {ntyVarargs, ntyOpenArray}: 171 | if param[1].typeKind in {ntyExpr, ntyStmt}: 172 | error("'toTask'ed function cannot have a 'typed' or 'untyped' parameter") 173 | let 174 | seqType = nnkBracketExpr.newTree(newIdentNode("seq"), param[1]) 175 | seqCallNode = newcall("@", e[i]) 176 | addAllNode(seqType, seqCallNode, isRaw) 177 | else: 178 | addAllNode(param, e[i], isRaw) 179 | of nnkBracket, nnkObjConstr: 180 | # passing by static parameters 181 | # so we pass them directly instead of passing by scratchObj 182 | callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i]) 183 | of nnkSym, nnkPtrTy: 184 | addAllNode(param, e[i], isRaw) 185 | of nnkCharLit..nnkNilLit: 186 | callNode.add nnkExprEqExpr.newTree(formalParams[i][0], e[i]) 187 | else: 188 | error("not supported type kinds") 189 | 190 | let scratchObjType = genSym(kind = nskType, ident = "ScratchObj") 191 | let scratchObj = nnkTypeSection.newTree( 192 | nnkTypeDef.newTree( 193 | scratchObjType, 194 | newEmptyNode(), 195 | nnkObjectTy.newTree( 196 | newEmptyNode(), 197 | newEmptyNode(), 198 | scratchRecList 199 | ) 200 | ) 201 | ) 202 | 203 | 204 | let scratchObjPtrType = quote do: 205 | cast[ptr `scratchObjType`](c_calloc(csize_t 1, csize_t sizeof(`scratchObjType`))) 206 | 207 | let scratchLetSection = newLetStmt( 208 | scratchIdent, 209 | scratchObjPtrType 210 | ) 211 | 212 | let scratchCheck = quote do: 213 | if `scratchIdent`.isNil: 214 | raise newException(OutOfMemDefect, "Could not allocate memory") 215 | 216 | var stmtList = newStmtList() 217 | stmtList.add(scratchObj) 218 | stmtList.add(scratchLetSection) 219 | stmtList.add(scratchCheck) 220 | stmtList.add(nnkBlockStmt.newTree(newEmptyNode(), newStmtList(scratchAssignList))) 221 | 222 | var functionStmtList = newStmtList() 223 | let funcCall = newCall(e[0], callNode) 224 | functionStmtList.add tempAssignList 225 | functionStmtList.add funcCall 226 | 227 | let funcName = genSym(nskProc, e[0].strVal) 228 | let destroyName = genSym(nskProc, "destroyScratch") 229 | let objTemp2 = genSym(ident = "obj") 230 | let tempNode = quote("@") do: 231 | `=destroy`(@objTemp2[]) 232 | 233 | result = quote do: 234 | `stmtList` 235 | 236 | proc `funcName`(args: pointer) {.gcsafe, nimcall.} = 237 | let `objTemp` = cast[ptr `scratchObjType`](args) 238 | `functionStmtList` 239 | 240 | proc `destroyName`(args: pointer) {.nimcall.} = 241 | let `objTemp2` = cast[ptr `scratchObjType`](args) 242 | `tempNode` 243 | 244 | Task(callback: `funcName`, args: `scratchIdent`, destroy: `destroyName`) 245 | else: 246 | let funcCall = newCall(e[0]) 247 | let funcName = genSym(nskProc, e[0].strVal) 248 | 249 | result = quote do: 250 | proc `funcName`(args: pointer) {.gcsafe, nimcall.} = 251 | `funcCall` 252 | 253 | Task(callback: `funcName`, args: nil) 254 | 255 | when defined(nimTasksDebug): 256 | echo result.repr 257 | 258 | template toTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): Task = 259 | toTaskImpl(e, false) 260 | 261 | template toRawTask*(e: typed{nkCall | nkInfix | nkPrefix | nkPostfix | nkCommand | nkCallStrLit}): RawTask = 262 | RawTask(toTaskImpl(e, true)) 263 | 264 | runnableExamples("--gc:orc"): 265 | block: 266 | var num = 0 267 | proc hello(a: int) = inc num, a 268 | 269 | let b = toTask hello(13) 270 | b.invoke() 271 | assert num == 13 272 | # A task can be invoked multiple times 273 | b.invoke() 274 | assert num == 26 275 | 276 | block: 277 | type 278 | Runnable = ref object 279 | data: int 280 | 281 | var data: int 282 | proc hello(a: Runnable) {.nimcall.} = 283 | a.data += 2 284 | data = a.data 285 | 286 | 287 | when false: 288 | # the parameters of call must be isolated. 289 | let x = Runnable(data: 12) 290 | let b = toTask hello(x) # error ----> expression cannot be isolated: x 291 | b.invoke() 292 | 293 | let b = toTask(hello(Runnable(data: 12))) 294 | b.invoke() 295 | assert data == 14 296 | b.invoke() 297 | assert data == 16 298 | -------------------------------------------------------------------------------- /mutexes.nim: -------------------------------------------------------------------------------- 1 | import std/locks 2 | import std/rlocks 3 | 4 | type 5 | Mutex* = object 6 | lock: Lock ## Nim lock; whether this is re-entrant 7 | ## or not is unspecified! 8 | 9 | RecursiveMutex* = object 10 | lock: RLock 11 | 12 | proc init*(mutex: var Mutex) {.inline.} = 13 | ## Initializes the given lock. 14 | initLock(mutex.lock) 15 | 16 | proc `=copy`*(x: var Mutex, y: Mutex) {.error.} 17 | 18 | proc `=destroy`*(mutex: var Mutex) {.inline.} = 19 | ## Frees the resources associated with the lock. 20 | deinitLock(mutex.lock) 21 | 22 | proc tryAcquire*(mutex: var Mutex): bool {.inline.} = 23 | ## Tries to acquire the given lock. Returns `true` on success. 24 | result = tryAcquire(mutex.lock) 25 | 26 | proc acquire*(mutex: var Mutex) {.inline.} = 27 | ## Acquires the given lock. 28 | acquire(mutex.lock) 29 | 30 | proc release*(mutex: var Mutex) {.inline.} = 31 | ## Releases the given lock. 32 | release(mutex.lock) 33 | 34 | proc init*(mutex: var RecursiveMutex) {.inline.} = 35 | ## Initializes the given lock. 36 | initRLock(mutex.lock) 37 | 38 | proc `=copy`*(x: var RecursiveMutex, y: RecursiveMutex) {.error.} 39 | 40 | proc `=destroy`*(mutex: var RecursiveMutex) {.inline.} = 41 | ## Frees the resources associated with the lock. 42 | deinitRlock(mutex.lock) 43 | 44 | proc tryAcquire*(mutex: var RecursiveMutex): bool {.inline.} = 45 | ## Tries to acquire the given lock. Returns `true` on success. 46 | result = tryAcquire(mutex.lock) 47 | 48 | proc acquire*(mutex: var RecursiveMutex) {.inline.} = 49 | ## Acquires the given lock. 50 | acquire(mutex.lock) 51 | 52 | proc release*(mutex: var RecursiveMutex) {.inline.} = 53 | ## Releases the given lock. 54 | release(mutex.lock) 55 | -------------------------------------------------------------------------------- /pool.nim: -------------------------------------------------------------------------------- 1 | import std/tasks 2 | import system/ansi_c 3 | 4 | import std/[deques, locks] 5 | import macros 6 | 7 | type 8 | StaticThreadPool = object 9 | pool: seq[Thread[pointer]] 10 | tasks: Deque[Task] 11 | lock: Lock 12 | isStop: bool 13 | notEmptyCond: Cond 14 | 15 | proc `=copy`(x: var StaticThreadPool, y: StaticThreadPool) {.error.} 16 | 17 | 18 | proc `=destroy`(x: var StaticThreadPool) = 19 | discard 20 | 21 | proc work(t: pointer) {.gcsafe.} = 22 | var pool = cast[ptr StaticThreadPool](t) 23 | assert pool != nil 24 | while true: 25 | acquire pool.lock 26 | while pool.tasks.len == 0 and not pool.isStop: 27 | wait(pool.notEmptyCond, pool.lock) 28 | 29 | if pool.isStop: 30 | if pool.tasks.len != 0: 31 | let task = pool.tasks.popFirst 32 | release(pool.lock) 33 | task.invoke() 34 | else: 35 | release(pool.lock) 36 | break 37 | 38 | # if pool.tasks.len == 0: 39 | # continue 40 | let task = pool.tasks.popFirst 41 | release(pool.lock) 42 | task.invoke() 43 | 44 | proc init(result: var StaticThreadPool, num: Positive) = 45 | result.pool.setLen(num) 46 | initLock(result.lock) 47 | initCond(result.notEmptyCond) 48 | 49 | for t in result.pool.mitems: 50 | createThread(t, work, cast[pointer](addr result)) 51 | 52 | 53 | macro spawn(pool: var StaticThreadPool, call: typed) = 54 | result = quote do: 55 | var task = toTask(`call`) 56 | acquire `pool`.lock 57 | try: 58 | `pool`.tasks.addLast move task 59 | signal(`pool`.notEmptyCond) 60 | finally: 61 | release `pool`.lock 62 | 63 | proc stop(pool: var StaticThreadPool) = 64 | acquire pool.lock 65 | pool.isStop = true 66 | broadcast(pool.notEmptyCond) 67 | release pool.lock 68 | for t in mitems(pool.pool): 69 | joinThread(t) 70 | 71 | 72 | proc hello(a: int) = echo a 73 | 74 | 75 | var pool: StaticThreadPool 76 | init(pool, 12) 77 | for i in 0 .. 10: 78 | pool.spawn hello(i) 79 | 80 | proc foo(x: string) = echo x 81 | 82 | for i in 0 .. 10: 83 | pool.spawn foo("string: " & $i) 84 | 85 | 86 | import os 87 | 88 | sleep(1000) 89 | 90 | pool.stop() 91 | -------------------------------------------------------------------------------- /pool.nims: -------------------------------------------------------------------------------- 1 | --threads:on -------------------------------------------------------------------------------- /queue.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # The Nim Compiler 4 | # (c) Copyright 2021 Mamy André-Ratsimbazafy & Nim Contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | import system/ansi_c 11 | 12 | type 13 | BoundedQueue*[T] = object 14 | head: int # Items are taken from head and new items are inserted at tail 15 | tail: int 16 | size: int 17 | buffer: ptr UncheckedArray[T] 18 | 19 | func init*[T](result: var BoundedQueue[T], size: int) {.inline.} = 20 | result.size = size 21 | result.buffer = cast[ptr UncheckedArray[T]](c_calloc(1, csize_t (size * sizeof(T)))) 22 | if result.buffer == nil: 23 | raise newException(OutOfMemDefect, "Could not allocate memory") 24 | 25 | proc `=copy`*[T](x: var BoundedQueue[T], y: BoundedQueue[T]) {.error.} 26 | 27 | proc `=destroy`*[T](x: var BoundedQueue[T]) {.inline.} = 28 | if x.buffer != nil: 29 | for i in 0 ..< x.size: 30 | `=destroy`(x.buffer[i]) 31 | c_free(x.buffer) 32 | 33 | proc size*[T](x: BoundedQueue[T]): int {.inline.} = 34 | result = x.size 35 | 36 | proc len*[T](x: BoundedQueue[T]): int {.inline.} = 37 | result = x.tail - x.head 38 | if result < 0: 39 | inc(result, 2 * x.size) 40 | 41 | assert result <= x.size 42 | 43 | func isEmpty*[T](x: BoundedQueue[T]): bool {.inline.} = 44 | x.head == x.tail 45 | 46 | func isFull*[T](x: BoundedQueue[T]): bool {.inline.} = 47 | abs(x.tail - x.head) == x.size 48 | 49 | func add*[T](q: var BoundedQueue[T], elem: sink T) {.inline.} = 50 | let writeIdx = if q.tail < q.size: q.tail 51 | else: q.tail - q.size 52 | q.buffer[writeIdx] = elem 53 | q.tail += 1 54 | if q.tail == 2*q.size: 55 | q.tail = 0 56 | 57 | func pop*[T]( 58 | q: var BoundedQueue[T] 59 | ): T {.inline.} = 60 | let readIdx = if q.head < q.size: q.head 61 | else: q.head - q.size 62 | result = move q.buffer[readIdx] 63 | q.head += 1 64 | if q.head == 2*q.size: 65 | q.head = 0 66 | 67 | func peek*[T](q: BoundedQueue[T]): lent T {.inline.} = 68 | let readIdx = if q.head < q.size: q.head 69 | else: q.head - q.size 70 | result = q.buffer[readIdx] 71 | -------------------------------------------------------------------------------- /semaphore.nim: -------------------------------------------------------------------------------- 1 | import nlock 2 | 3 | type 4 | Semaphore* = object 5 | c: Cond 6 | L: Lock 7 | counter: int 8 | 9 | proc `=copy`*(x: var Semaphore, y: Semaphore) {.error.} 10 | 11 | proc `=destroy`*(cv: var Semaphore) = 12 | `=destroy`(cv.c) 13 | `=destroy`(cv.L) 14 | 15 | proc init*(cv: var Semaphore) = 16 | init(cv.c) 17 | init(cv.L) 18 | 19 | proc blockUntil*(cv: var Semaphore) = 20 | acquire(cv.L) 21 | while cv.counter <= 0: 22 | wait(cv.c, cv.L) 23 | dec cv.counter 24 | release(cv.L) 25 | 26 | proc signal*(cv: var Semaphore) = 27 | acquire(cv.L) 28 | inc cv.counter 29 | release(cv.L) 30 | signal(cv.c) 31 | -------------------------------------------------------------------------------- /smart.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 | 11 | 12 | import std/isolation 13 | 14 | 15 | type 16 | Deleter*[T] = proc (val: var T) 17 | UniquePtr*[T] = object 18 | ## Non copyable pointer to a value of type `T` with exclusive ownership. 19 | val: ptr T 20 | deleter: Deleter[T] 21 | 22 | proc `=destroy`*[T](p: var UniquePtr[T]) = 23 | if p.val != nil: 24 | if p.deleter != nil: 25 | p.deleter(p.val[]) 26 | else: 27 | `=destroy`(p.val[]) 28 | when compileOption("threads"): 29 | deallocShared(p.val) 30 | else: 31 | dealloc(p.val) 32 | 33 | proc `=copy`*[T](dest: var UniquePtr[T], src: UniquePtr[T]) {.error.} 34 | ## The copy operation is disallowed for `UniquePtr`, it 35 | ## can only be moved. 36 | 37 | proc newUniquePtr[T](val: sink Isolated[T], deleter: Deleter[T] = nil): UniquePtr[T] {.nodestroy.} = 38 | ## Returns a unique pointer which has exclusive ownership of the value. 39 | when compileOption("threads"): 40 | result.val = cast[ptr T](allocShared(sizeof(T))) 41 | else: 42 | result.val = cast[ptr T](alloc(sizeof(T))) 43 | # thanks to '.nodestroy' we don't have to use allocShared0 here. 44 | # This is compiled into a copyMem operation, no need for a sink 45 | # here either. 46 | result.val[] = val.extract 47 | result.deleter = deleter 48 | # no destructor call for 'val: sink T' here either. 49 | 50 | template newUniquePtr*[T](val: T, deleter: Deleter[T] = nil): UniquePtr[T] = 51 | ## Returns a unique pointer which has exclusive ownership of the value. 52 | newUniquePtr(isolate(val), deleter) 53 | 54 | proc get*[T](p: UniquePtr[T]): var T {.inline.} = 55 | ## Returns a mutable view of the internal value of `p`. 56 | when compileOption("boundChecks"): 57 | doAssert(p.val != nil, "dereferencing a nil unique pointer") 58 | p.val[] 59 | 60 | proc isNil*[T](p: UniquePtr[T]): bool {.inline.} = 61 | p.val == nil 62 | 63 | proc `[]`*[T](p: UniquePtr[T]): var T {.inline.} = 64 | p.get 65 | 66 | proc `$`*[T](p: UniquePtr[T]): string {.inline.} = 67 | if p.val == nil: "(nil)" 68 | else: "(" & $p.val[] & ")" 69 | 70 | #------------------------------------------------------------------------------ 71 | 72 | type 73 | SharedPtr*[T] = object 74 | ## Shared ownership reference counting pointer. 75 | val: ptr tuple[value: T, atomicCounter: int] 76 | deleter: Deleter[T] 77 | 78 | proc `=destroy`*[T](p: var SharedPtr[T]) = 79 | if p.val != nil: 80 | if (when compileOption("threads"): 81 | atomicLoadN(addr p.val[].atomicCounter, ATOMIC_CONSUME) == 0 else: 82 | p.val[].atomicCounter == 0): 83 | if p.deleter != nil: 84 | p.deleter(p.val[].value) 85 | else: 86 | `=destroy`(p.val[]) 87 | when compileOption("threads"): 88 | deallocShared(p.val) 89 | else: 90 | dealloc(p.val) 91 | else: 92 | when compileOption("threads"): 93 | discard atomicDec(p.val[].atomicCounter) 94 | else: 95 | dec(p.val[].atomicCounter) 96 | 97 | proc `=copy`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = 98 | if src.val != nil: 99 | when compileOption("threads"): 100 | discard atomicInc(src.val[].atomicCounter) 101 | else: 102 | inc(src.val[].atomicCounter) 103 | if dest.val != nil: 104 | `=destroy`(dest) 105 | dest.val = src.val 106 | 107 | proc newSharedPtr[T](val: sink Isolated[T], deleter: Deleter[T] = nil): SharedPtr[T] {.nodestroy.} = 108 | ## Returns a shared pointer which shares 109 | ## ownership of the object by reference counting. 110 | when compileOption("threads"): 111 | result.val = cast[typeof(result.val)](allocShared(sizeof(result.val[]))) 112 | else: 113 | result.val = cast[typeof(result.val)](alloc(sizeof(result.val[]))) 114 | result.val.atomicCounter = 0 115 | result.val.value = val.extract 116 | result.deleter = deleter 117 | 118 | template newSharedPtr*[T](val: T, deleter: Deleter[T] = nil): SharedPtr[T] = 119 | ## Returns a shared pointer which shares 120 | ## ownership of the object by reference counting. 121 | newSharedPtr(isolate(val), deleter) 122 | 123 | proc get*[T](p: SharedPtr[T]): var T {.inline.} = 124 | ## Returns a mutable view of the internal value of `p`. 125 | when compileOption("boundChecks"): 126 | doAssert(p.val != nil, "dereferencing a nil shared pointer") 127 | p.val.value 128 | 129 | proc isNil*[T](p: SharedPtr[T]): bool {.inline.} = 130 | p.val == nil 131 | 132 | proc `[]`*[T](p: SharedPtr[T]): var T {.inline.} = 133 | p.get 134 | 135 | proc `$`*[T](p: SharedPtr[T]): string {.inline.} = 136 | if p.val == nil: "(nil)" 137 | else: "(" & $p.val.value & ")" 138 | 139 | #------------------------------------------------------------------------------ 140 | 141 | type 142 | ConstPtr*[T] = distinct SharedPtr[T] 143 | ## Distinct version of `SharedPtr[T]`, which doesn't allow mutating the underlying value. 144 | 145 | template newConstPtr*[T](val: T, deleter: Deleter[T] = nil): ConstPtr[T] = 146 | ## Similar to `newSharedPtr<#newSharedPtr,T>`_, but the underlying value can't be mutated. 147 | ConstPtr[T](newSharedPtr(isolate(val), deleter)) 148 | 149 | proc get*[T](p: ConstPtr[T]): lent T {.inline.} = 150 | ## Returns an immutable view of the internal value of `p`. 151 | when compileOption("boundChecks"): 152 | doAssert(SharedPtr[T](p).val != nil, "dereferencing nil const pointer") 153 | SharedPtr[T](p).val.value 154 | 155 | proc isNil*[T](p: ConstPtr[T]): bool {.inline.} = 156 | SharedPtr[T](p).val == nil 157 | 158 | proc `[]`*[T](p: ConstPtr[T]): lent T {.inline.} = 159 | p.get 160 | 161 | proc `$`*[T](p: ConstPtr[T]): string {.inline.} = 162 | if SharedPtr[T](p).val == nil: "(nil)" 163 | else: "(" & $SharedPtr[T](p).val.value & ")" 164 | 165 | 166 | runnableExamples("-d:nimExperimentalSmartptrs"): 167 | import std/isolation 168 | 169 | block: 170 | var a1: UniquePtr[float] 171 | var a2 = newUniquePtr(0) 172 | 173 | assert $a1 == "(nil)" 174 | assert a1.isNil 175 | assert $a2 == "(0)" 176 | assert not a2.isNil 177 | assert a2[] == 0 178 | assert a2.get == 0 179 | 180 | # UniquePtr can't be copied but can be moved 181 | let a3 = move a2 # a2 will be destroyed 182 | 183 | assert $a2 == "(nil)" 184 | assert a2.isNil 185 | 186 | assert $a3 == "(0)" 187 | assert not a3.isNil 188 | assert a3[] == 0 189 | assert a3.get == 0 190 | 191 | block: 192 | var a1: SharedPtr[float] 193 | let a2 = newSharedPtr(0) 194 | let a3 = a2 195 | 196 | assert $a1 == "(nil)" 197 | assert a1.isNil 198 | assert $a2 == "(0)" 199 | assert not a2.isNil 200 | assert a2[] == 0 201 | assert a2.get == 0 202 | assert $a3 == "(0)" 203 | assert not a3.isNil 204 | assert a3[] == 0 205 | assert a3.get == 0 206 | 207 | block: 208 | var a1: ConstPtr[float] 209 | let a2 = newConstPtr(0) 210 | let a3 = a2 211 | 212 | assert $a1 == "(nil)" 213 | assert a1.isNil 214 | assert $a2 == "(0)" 215 | assert not a2.isNil 216 | assert a2[] == 0 217 | assert a2.get == 0 218 | assert $a3 == "(0)" 219 | assert not a3.isNil 220 | assert a3[] == 0 221 | assert a3.get == 0 222 | -------------------------------------------------------------------------------- /test.nim: -------------------------------------------------------------------------------- 1 | when compileOption("threads"): 2 | import std/[atomics, locks] 3 | 4 | 5 | type 6 | Once* = object ## A object that ensures a block of code is executed once. 7 | when compileOption("threads"): 8 | finished: Atomic[bool] 9 | lock: Lock 10 | else: 11 | finished: bool 12 | 13 | proc `=destroy`*(once: var Once) {.inline.} = 14 | when compileOption("threads"): 15 | deinitLock(once.lock) 16 | 17 | proc init*(once: var Once) = 18 | ## Initializes a `Once` object. 19 | when compileOption("threads"): 20 | once.finished.store(false, moRelaxed) 21 | initLock(once.lock) 22 | 23 | template once*(cond: var Once, body: untyped) = 24 | ## Executes a block of code only once (the first time the block is reached). 25 | ## It is thread-safe. 26 | runnableExamples("--gc:orc --threads:on"): 27 | var block1: Once 28 | var count = 0 29 | init(block1) 30 | 31 | for i in 1 .. 10: 32 | once(block1): 33 | inc count 34 | 35 | # only the first `block1` is executed 36 | once(block1): 37 | count = 888 38 | 39 | assert count == 1 40 | 41 | when compileOption("threads"): 42 | if not cond.finished.load(moAcquire): 43 | withLock cond.lock: 44 | if not cond.finished.load(moRelaxed): 45 | try: 46 | body 47 | finally: 48 | cond.finished.store(true, moRelease) 49 | else: 50 | if not cond.finished: 51 | try: 52 | body 53 | finally: 54 | cond.finished = true 55 | 56 | 57 | ## The code block is executed only once among threads. 58 | runnableExamples("--gc:orc --threads:on"): 59 | block: 60 | var thr: array[0..4, Thread[void]] 61 | var block1: Once 62 | var count = 0 63 | init(block1) 64 | proc threadFunc() {.thread.} = 65 | for i in 1 .. 10: 66 | once(block1): 67 | inc count 68 | for i in 0..high(thr): 69 | createThread(thr[i], threadFunc) 70 | joinThreads(thr) 71 | assert count == 1 72 | 73 | ## The code blocks is executed per thread. 74 | runnableExamples("--gc:orc --threads:on"): 75 | block: 76 | var thr: array[0..4, Thread[void]] 77 | var count = 0 78 | proc threadFunc() {.thread.} = 79 | var block1: Once 80 | init(block1) 81 | for i in 1 .. 10: 82 | once(block1): 83 | inc count 84 | for i in 0..high(thr): 85 | createThread(thr[i], threadFunc) 86 | joinThreads(thr) 87 | assert count == thr.len 88 | 89 | -------------------------------------------------------------------------------- /tweave.nim: -------------------------------------------------------------------------------- 1 | type 2 | Ref = ref object 3 | id: int 4 | Test = object 5 | id: Ref 6 | 7 | proc `=copy`(x: var Test, y: Test) = 8 | deepCopy(x.id, y.id) 9 | 10 | proc `=destroy`(x: var Test) = 11 | echo 1234 12 | x.id.id = 9999999 13 | 14 | var x = Test(id: Ref()) 15 | 16 | block: 17 | var y = x 18 | 19 | echo x.repr 20 | -------------------------------------------------------------------------------- /utils.nim: -------------------------------------------------------------------------------- 1 | type 2 | NonCopyable* = object of RootObj 3 | 4 | proc `=copy`*(x: var NonCopyable, y: NonCopyable) {.error.} 5 | --------------------------------------------------------------------------------