primitives.odin 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. package sync
  2. import "core:time"
  3. /*
  4. Obtain the current thread ID.
  5. */
  6. current_thread_id :: proc "contextless" () -> int {
  7. return _current_thread_id()
  8. }
  9. /*
  10. Mutual exclusion lock.
  11. A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]]
  12. It can be used to prevent more than one thread from entering the critical
  13. section, and thus prevent access to same piece of memory by multiple threads, at
  14. the same time.
  15. Mutex's zero-initializzed value represents an initial, *unlocked* state.
  16. If another thread tries to acquire the lock, while it's already held (typically
  17. by another thread), the thread's execution will be blocked, until the lock is
  18. released. Code or memory that is "surrounded" by a mutex lock and unlock
  19. operations is said to be "guarded by a mutex".
  20. **Note**: A Mutex must not be copied after first use (e.g., after locking it the
  21. first time). This is because, in order to coordinate with other threads, all
  22. threads must watch the same memory address to know when the lock has been
  23. released. Trying to use a copy of the lock at a different memory address will
  24. result in broken and unsafe behavior. For this reason, Mutexes are marked as
  25. `#no_copy`.
  26. **Note**: If the current thread attempts to lock a mutex, while it's already
  27. holding another lock, that will cause a trivial case of deadlock. Do not use
  28. `Mutex` in recursive functions. In case multiple locks by the same thread are
  29. desired, use `Recursive_Mutex`.
  30. */
  31. Mutex :: struct #no_copy {
  32. impl: _Mutex,
  33. }
  34. /*
  35. Acquire a lock on a mutex.
  36. This procedure acquires a lock with the specified mutex. If the mutex has been
  37. already locked by any thread, this procedure also blocks until the lock can be
  38. acquired.
  39. Once the lock is acquired, all other threads that attempt to acquire a lock will
  40. be blocked from entering any critical sections associated with the same mutex,
  41. until the the lock is released.
  42. **Note**: If the mutex is already locked by the current thread, a call to this
  43. procedure will block indefinately. Do not use this in recursive procedures.
  44. */
  45. mutex_lock :: proc "contextless" (m: ^Mutex) {
  46. _mutex_lock(m)
  47. }
  48. /*
  49. Release a lock on a mutex.
  50. This procedure releases the lock associated with the specified mutex. If the
  51. mutex was not locked, this operation is a no-op.
  52. When the current thread, that holds a lock to the mutex calls `mutex_unlock`,
  53. this allows one other thread waiting on the mutex to enter any critical sections
  54. associated with the mutex. If there are no threads waiting on the mutex, the
  55. critical sections will remain open.
  56. */
  57. mutex_unlock :: proc "contextless" (m: ^Mutex) {
  58. _mutex_unlock(m)
  59. }
  60. /*
  61. Try to acquire a lock on a mutex.
  62. This procedure tries to acquire a lock on the specified mutex. If it was already
  63. locked, then the returned value is `false`, otherwise the lock is acquired and
  64. the procedure returns `true`.
  65. If the lock is acquired, all threads that attempt to acquire a lock will be
  66. blocked from entering any critical sections associated with the same mutex,
  67. until the lock is released.
  68. */
  69. mutex_try_lock :: proc "contextless" (m: ^Mutex) -> bool {
  70. return _mutex_try_lock(m)
  71. }
  72. /*
  73. Guard the current scope with a lock on a mutex.
  74. This procedure acquires a mutex lock. The lock is automatically released
  75. at the end of callee's scope. If the mutex was already locked, this procedure
  76. also blocks until the lock can be acquired.
  77. When a lock has been acquired, all threads attempting to acquire a lock will be
  78. blocked from entering any critical sections associated with the mutex, until
  79. the lock is released.
  80. This procedure always returns `true`. This makes it easy to define a critical
  81. section by putting the function inside the `if` statement.
  82. **Example**:
  83. if mutex_guard(&m) {
  84. ...
  85. }
  86. */
  87. @(deferred_in=mutex_unlock)
  88. mutex_guard :: proc "contextless" (m: ^Mutex) -> bool {
  89. mutex_lock(m)
  90. return true
  91. }
  92. /*
  93. Read-write mutual exclusion lock.
  94. An `RW_Mutex` is a reader/writer mutual exclusion lock. The lock can be held by
  95. any number of readers or a single writer.
  96. This type of synchronization primitive supports two kinds of lock operations:
  97. - Exclusive lock (write lock)
  98. - Shared lock (read lock)
  99. When an exclusive lock is acquired by any thread, all other threads, attempting
  100. to acquire either an exclusive or shared lock, will be blocked from entering the
  101. critical sections associated with the read-write mutex, until the exclusive
  102. owner of the lock releases the lock.
  103. When a shared lock is acquired by any thread, any other thread attempting to
  104. acquire a shared lock will also be able to enter all the critical sections
  105. associated with the read-write mutex. However threads attempting to acquire
  106. an exclusive lock will be blocked from entering those critical sections, until
  107. all shared locks are released.
  108. **Note**: A read-write mutex must not be copied after first use (e.g., after
  109. acquiring a lock). This is because, in order to coordinate with other threads,
  110. all threads must watch the same memory address to know when the lock has been
  111. released. Trying to use a copy of the lock at a different memory address will
  112. result in broken and unsafe behavior. For this reason, mutexes are marked as
  113. `#no_copy`.
  114. **Note**: A read-write mutex is not recursive. Do not attempt to acquire an
  115. exclusive lock more than once from the same thread, or an exclusive and shared
  116. lock on the same thread. Taking a shared lock multiple times is acceptable.
  117. */
  118. RW_Mutex :: struct #no_copy {
  119. impl: _RW_Mutex,
  120. }
  121. /*
  122. Acquire an exclusive lock.
  123. This procedure acquires an exclusive lock on the specified read-write mutex. If
  124. the lock is already held by any thread, this procedure also blocks until the
  125. lock can be acquired.
  126. After a lock has been acquired, any thread attempting to acquire any lock
  127. will be blocked from entering any critical sections associated with the same
  128. read-write mutex, until the exclusive lock is released.
  129. */
  130. rw_mutex_lock :: proc "contextless" (rw: ^RW_Mutex) {
  131. _rw_mutex_lock(rw)
  132. }
  133. /*
  134. Release an exclusive lock.
  135. This procedure releases an exclusive lock associated with the specified
  136. read-write mutex.
  137. When the exclusive lock is released, all critical sections, associated with the
  138. same read-write mutex, become open to other threads.
  139. */
  140. rw_mutex_unlock :: proc "contextless" (rw: ^RW_Mutex) {
  141. _rw_mutex_unlock(rw)
  142. }
  143. /*
  144. Try to acquire an exclusive lock on a read-write mutex.
  145. This procedure tries to acquire an exclusive lock on the specified read-write
  146. mutex. If the mutex was already locked, the procedure returns `false`. Otherwise
  147. it acquires the exclusive lock and returns `true`.
  148. If the lock has been acquired, all threads attempting to acquire any lock
  149. will be blocked from entering any critical sections associated with the same
  150. read-write mutex, until the exclusive locked is released.
  151. */
  152. rw_mutex_try_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool {
  153. return _rw_mutex_try_lock(rw)
  154. }
  155. /*
  156. Acquire a shared lock on a read-write mutex.
  157. This procedure acquires a shared lock on the specified read-write mutex. If the
  158. mutex already has an exclusive lock held, this procedure also blocks until the
  159. lock can be acquired.
  160. After the shared lock is obtained, all threads attempting to acquire an
  161. exclusive lock will be blocked from entering any critical sections associated
  162. with the same read-write mutex, until all shared locks associated with the
  163. specified read-write mutex are released.
  164. */
  165. rw_mutex_shared_lock :: proc "contextless" (rw: ^RW_Mutex) {
  166. _rw_mutex_shared_lock(rw)
  167. }
  168. /*
  169. Release the shared lock on a read-write mutex.
  170. This procedure releases shared lock on the specified read-write mutex. When all
  171. shared locks are released, all critical sections associated with the same
  172. read-write mutex become open to other threads.
  173. */
  174. rw_mutex_shared_unlock :: proc "contextless" (rw: ^RW_Mutex) {
  175. _rw_mutex_shared_unlock(rw)
  176. }
  177. /*
  178. Try to acquire a shared lock on a read-write mutex.
  179. This procedure attempts to acquire a lock on the specified read-write mutex. If
  180. the mutex already has an exclusive lock held, this procedure returns `false`.
  181. Otherwise, it acquires the lock on the mutex and returns `true`.
  182. If the shared lock has been acquired, it causes all threads attempting to
  183. acquire the exclusive lock to be blocked from entering any critical sections
  184. associated with the same read-write mutex, until all shared locks are released.
  185. */
  186. rw_mutex_try_shared_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool {
  187. return _rw_mutex_try_shared_lock(rw)
  188. }
  189. /*
  190. Guard the current scope with an exclusive lock on a read-write mutex.
  191. This procedure acquires an exclusive lock on the specified read-write mutex.
  192. This procedure automatically releases the lock at the end of the callee's scope.
  193. If the mutex was already locked by readers or a writer, this procedure blocks,
  194. until a lock can be acquired.
  195. When an exclusive lock is acquired, all other threads attempting to acquire an
  196. exclusive lock will be blocked from entering any critical sections associated
  197. with the same read-write mutex, until the exclusive lock is released.
  198. This procedure always returns `true`, which makes it easy to define a critical
  199. section by running this procedure inside an `if` statement.
  200. **Example**:
  201. if rw_mutex_guard(&m) {
  202. ...
  203. }
  204. */
  205. @(deferred_in=rw_mutex_unlock)
  206. rw_mutex_guard :: proc "contextless" (m: ^RW_Mutex) -> bool {
  207. rw_mutex_lock(m)
  208. return true
  209. }
  210. /*
  211. Guard the current scope with a shared lock on a read-write mutex.
  212. This procedure acquires a shared lock on the specified read-write mutex. This
  213. procedure automatically releases the lock at the end of the callee's scope. If
  214. the mutex already has an associated exclusive lock, this procedure blocks, until
  215. a lock can be acquired.
  216. When a shared lock is obtained, all other threads attempting to obtain an
  217. exclusive lock will be blocked from any critical sections, associated with the
  218. same read-write mutex, until all shared locks are released.
  219. This procedure always returns `true`, which makes it easy to define a critical
  220. section by running this procedure inside an `if` statement.
  221. **Example**:
  222. if rw_mutex_guard(&m) {
  223. ...
  224. }
  225. */
  226. @(deferred_in=rw_mutex_shared_unlock)
  227. rw_mutex_shared_guard :: proc "contextless" (m: ^RW_Mutex) -> bool {
  228. rw_mutex_shared_lock(m)
  229. return true
  230. }
  231. /*
  232. Recursive mutual exclusion lock.
  233. Recurisve mutex is just like a plain mutex, except it allows reentrancy. In
  234. order for a thread to release the mutex for other threads, the mutex needs to
  235. be unlocked as many times, as it was locked.
  236. When a lock is acquired on a recursive mutex, all other threads attempting to
  237. acquire a lock on the same mutex will be blocked from any critical sections,
  238. associated with the same recrusive mutex.
  239. When a lock is acquired on a recursive mutex by a thread, that thread is allowed
  240. to acquire another lock on the same mutex. When a thread has acquired the lock
  241. on a recursive mutex, the recursive mutex will stay locked until the thread
  242. releases the lock as many times as it has been locked by the thread.
  243. **Note**: A recursive mutex must not be copied after first use (e.g., after
  244. acquiring a lock). This is because, in order to coordinate with other threads,
  245. all threads must watch the same memory address to know when the lock has been
  246. released. Trying to use a copy of the lock at a different memory address will
  247. result in broken and unsafe behavior. For this reason, mutexes are marked as
  248. `#no_copy`.
  249. */
  250. Recursive_Mutex :: struct #no_copy {
  251. impl: _Recursive_Mutex,
  252. }
  253. /*
  254. Acquire a lock on a recursive mutex.
  255. This procedure acquires a lock on the specified recursive mutex. If the lock is
  256. acquired by a different thread, this procedure also blocks until the lock can be
  257. acquired.
  258. When the lock is acquired, all other threads attempting to acquire a lock will
  259. be blocked from entering any critical sections associated with the same mutex,
  260. until the lock is released.
  261. */
  262. recursive_mutex_lock :: proc "contextless" (m: ^Recursive_Mutex) {
  263. _recursive_mutex_lock(m)
  264. }
  265. /*
  266. Release a lock on a recursive mutex.
  267. This procedure releases a lock on the specified recursive mutex. It also causes
  268. the critical sections associated with the same mutex, to become open for other
  269. threads for entering.
  270. */
  271. recursive_mutex_unlock :: proc "contextless" (m: ^Recursive_Mutex) {
  272. _recursive_mutex_unlock(m)
  273. }
  274. /*
  275. Try to acquire a lock on a recursive mutex.
  276. This procedure attempts to acquire a lock on the specified recursive mutex. If
  277. the recursive mutex is locked by other threads, this procedure returns `false`.
  278. Otherwise it locks the mutex and returns `true`.
  279. If the lock is acquired, all other threads attempting to obtain a lock will be
  280. blocked from entering any critical sections associated with the same mutex,
  281. until the lock is released.
  282. */
  283. recursive_mutex_try_lock :: proc "contextless" (m: ^Recursive_Mutex) -> bool {
  284. return _recursive_mutex_try_lock(m)
  285. }
  286. /*
  287. Guard the scope with a recursive mutex lock.
  288. This procedure acquires a lock on the specified recursive mutex and
  289. automatically releases it at the end of the callee's scope. If the recursive
  290. mutex was already held by a another thread, this procedure also blocks until the
  291. lock can be acquired.
  292. When the lock is acquired all other threads attempting to take a lock will be
  293. blocked from entering any critical sections associated with the same mutex,
  294. until the lock is released.
  295. This procedure always returns `true`, which makes it easy to define a critical
  296. section by calling this procedure inside an `if` statement.
  297. **Example**:
  298. if recursive_mutex_guard(&m) {
  299. ...
  300. }
  301. */
  302. @(deferred_in=recursive_mutex_unlock)
  303. recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool {
  304. recursive_mutex_lock(m)
  305. return true
  306. }
  307. /*
  308. A condition variable.
  309. `Cond` implements a condition variable, a rendezvous point for threads waiting
  310. for signalling the occurence of an event. Condition variables are used in
  311. conjuction with mutexes to provide a shared access to one or more shared
  312. variable.
  313. A typical usage of condition variable is as follows. A thread that intends to
  314. modify a shared variable shall:
  315. 1. Acquire a lock on a mutex.
  316. 2. Modify the shared memory.
  317. 3. Release the lock.
  318. 3. Call `cond_signal` or `cond_broadcast`.
  319. A thread that intends to wait on a shared variable shall:
  320. 1. Acquire a lock on a mutex.
  321. 2. Call `cond_wait` or `cond_wait_with_timeout` (will release the mutex).
  322. 3. Check the condition and keep waiting in a loop if not satisfied with result.
  323. **Note**: A condition variable must not be copied after first use (e.g., after
  324. waiting on it the first time). This is because, in order to coordinate with
  325. other threads, all threads must watch the same memory address to know when the
  326. lock has been released. Trying to use a copy of the lock at a different memory
  327. address will result in broken and unsafe behavior. For this reason, condition
  328. variables are marked as `#no_copy`.
  329. */
  330. Cond :: struct #no_copy {
  331. impl: _Cond,
  332. }
  333. /*
  334. Wait until the condition variable is signalled and release the associated mutex.
  335. This procedure blocks the current thread until the specified condition variable
  336. is signalled, or until a spurious wakeup occurs. In addition, if the condition
  337. has been signalled, this procedure releases the lock on the specified mutex.
  338. The mutex must be held by the calling thread, before calling the procedure.
  339. **Note**: This procedure can return on a spurious wake-up, even if the condition
  340. variable was not signalled by a thread.
  341. */
  342. cond_wait :: proc "contextless" (c: ^Cond, m: ^Mutex) {
  343. _cond_wait(c, m)
  344. }
  345. /*
  346. Wait until the condition variable is signalled or timeout is reached and release
  347. the associated mutex.
  348. This procedure blocks the current thread until the specified condition variable
  349. is signalled, a timeout is reached, or until a spurious wakeup occurs. In
  350. addition, if the condition has been signalled, this procedure releases the
  351. lock on the specified mutex.
  352. If the timeout was reached, this procedure returns `false`. Otherwise it returns
  353. `true`.
  354. Before this procedure is called the mutex must be held by the calling thread.
  355. */
  356. cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool {
  357. if duration <= 0 {
  358. return false
  359. }
  360. return _cond_wait_with_timeout(c, m, duration)
  361. }
  362. /*
  363. Wake up one thread that waits on a condition variable.
  364. This procedure causes exactly one thread waiting on the condition variable to
  365. wake up.
  366. */
  367. cond_signal :: proc "contextless" (c: ^Cond) {
  368. _cond_signal(c)
  369. }
  370. /*
  371. Wake up all threads that wait on a condition variable.
  372. This procedure causes all threads waiting on the condition variable to wake up.
  373. */
  374. cond_broadcast :: proc "contextless" (c: ^Cond) {
  375. _cond_broadcast(c)
  376. }
  377. /*
  378. Semaphore.
  379. When waited upon, semaphore blocks until the internal count is greater than
  380. zero, then decrements the internal counter by one. Posting to the semaphore
  381. increases the count by one, or the provided amount.
  382. This type of synchronization primitives can be useful for implementing queues.
  383. The internal counter of the semaphore can be thought of as the amount of items
  384. in the queue. After a data has been pushed to the queue, the thread shall call
  385. `sema_post()` procedure, increasing the counter. When a thread takes an item
  386. from the queue to do the job, it shall call `sema_wait()`, waiting on the
  387. semaphore counter to become non-zero and decreasing it, if necessary.
  388. **Note**: A semaphore must not be copied after first use (e.g., after posting
  389. to it). This is because, in order to coordinate with other threads, all threads
  390. must watch the same memory address to know when the lock has been released.
  391. Trying to use a copy of the lock at a different memory address will result in
  392. broken and unsafe behavior. For this reason, semaphores are marked as `#no_copy`.
  393. */
  394. Sema :: struct #no_copy {
  395. impl: _Sema,
  396. }
  397. /*
  398. Increment the internal counter on a semaphore by the specified amount.
  399. This procedure increments the internal counter of the semaphore. If any of the
  400. threads were waiting on the semaphore, up to `count` of threads will continue
  401. the execution and enter the critical section.
  402. */
  403. sema_post :: proc "contextless" (s: ^Sema, count := 1) {
  404. _sema_post(s, count)
  405. }
  406. /*
  407. Wait on a semaphore until the internal counter is non-zero.
  408. This procedure blocks the execution of the current thread, until the semaphore
  409. counter is non-zero, and atomically decrements it by one, once the wait has
  410. ended.
  411. */
  412. sema_wait :: proc "contextless" (s: ^Sema) {
  413. _sema_wait(s)
  414. }
  415. /*
  416. Wait on a semaphore until the internal counter is non-zero or a timeout is reached.
  417. This procedure blocks the execution of the current thread, until the semaphore
  418. counter is non-zero, and if so atomically decrements it by one, once the wait
  419. has ended. If the specified timeout is reached, the function returns `false`,
  420. otherwise it returns `true`.
  421. */
  422. sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool {
  423. return _sema_wait_with_timeout(s, duration)
  424. }
  425. /*
  426. Fast userspace mutual exclusion lock.
  427. Futex is a fast userspace mutual exclusion lock, that uses a pointer to a 32-bit
  428. value as an identifier of the queue of waiting threads. The value pointed to
  429. by that pointer can be used to store extra data.
  430. **IMPORTANT**: A futex must not be copied after first use (e.g., after waiting
  431. on it the first time, or signalling it). This is because, in order to coordinate
  432. with other threads, all threads must watch the same memory address. Trying to
  433. use a copy of the lock at a different memory address will result in broken and
  434. unsafe behavior.
  435. */
  436. Futex :: distinct u32
  437. /*
  438. Sleep if the futex contains the expected value until it's signalled.
  439. If the value of the futex is `expected`, this procedure blocks the execution of
  440. the current thread, until the futex is woken up, or until a spurious wakeup
  441. occurs.
  442. */
  443. futex_wait :: proc "contextless" (f: ^Futex, expected: u32) {
  444. if u32(atomic_load_explicit(f, .Acquire)) != expected {
  445. return
  446. }
  447. ok := _futex_wait(f, expected)
  448. assert_contextless(ok, "futex_wait failure")
  449. }
  450. /*
  451. Sleep if the futex contains the expected value until it's signalled or the
  452. timeout is reached.
  453. If the value of the futex is `expected`, this procedure blocks the execution of
  454. the current thread, until the futex is signalled, a timeout is reached, or
  455. until a spurious wakeup occurs.
  456. This procedure returns `false` if the timeout was reached, `true` otherwise.
  457. */
  458. futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
  459. if u32(atomic_load_explicit(f, .Acquire)) != expected {
  460. return true
  461. }
  462. if duration <= 0 {
  463. return false
  464. }
  465. return _futex_wait_with_timeout(f, expected, duration)
  466. }
  467. /*
  468. Wake up a single thread waiting on a futex.
  469. */
  470. futex_signal :: proc "contextless" (f: ^Futex) {
  471. _futex_signal(f)
  472. }
  473. /*
  474. Wake up multiple threads waiting on a futex.
  475. */
  476. futex_broadcast :: proc "contextless" (f: ^Futex) {
  477. _futex_broadcast(f)
  478. }