sync_unix.odin 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // +build linux, darwin, freebsd
  2. package sync
  3. import "core:sys/unix"
  4. import "core:time"
  5. // A recursive lock that can only be held by one thread at once
  6. Mutex :: struct {
  7. handle: unix.pthread_mutex_t,
  8. }
  9. mutex_init :: proc(m: ^Mutex) {
  10. // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex.
  11. attrs: unix.pthread_mutexattr_t
  12. assert(unix.pthread_mutexattr_init(&attrs) == 0)
  13. defer unix.pthread_mutexattr_destroy(&attrs) // ignores destruction error
  14. unix.pthread_mutexattr_settype(&attrs, unix.PTHREAD_MUTEX_RECURSIVE)
  15. assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0)
  16. }
  17. mutex_destroy :: proc(m: ^Mutex) {
  18. assert(unix.pthread_mutex_destroy(&m.handle) == 0)
  19. m.handle = {}
  20. }
  21. mutex_lock :: proc(m: ^Mutex) {
  22. assert(unix.pthread_mutex_lock(&m.handle) == 0)
  23. }
  24. // Returns false if someone else holds the lock.
  25. mutex_try_lock :: proc(m: ^Mutex) -> bool {
  26. return unix.pthread_mutex_trylock(&m.handle) == 0
  27. }
  28. mutex_unlock :: proc(m: ^Mutex) {
  29. assert(unix.pthread_mutex_unlock(&m.handle) == 0)
  30. }
  31. Blocking_Mutex :: struct {
  32. handle: unix.pthread_mutex_t,
  33. }
  34. blocking_mutex_init :: proc(m: ^Blocking_Mutex) {
  35. // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex.
  36. attrs: unix.pthread_mutexattr_t
  37. assert(unix.pthread_mutexattr_init(&attrs) == 0)
  38. defer unix.pthread_mutexattr_destroy(&attrs) // ignores destruction error
  39. assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0)
  40. }
  41. blocking_mutex_destroy :: proc(m: ^Blocking_Mutex) {
  42. assert(unix.pthread_mutex_destroy(&m.handle) == 0)
  43. m.handle = {}
  44. }
  45. blocking_mutex_lock :: proc(m: ^Blocking_Mutex) {
  46. assert(unix.pthread_mutex_lock(&m.handle) == 0)
  47. }
  48. // Returns false if someone else holds the lock.
  49. blocking_mutex_try_lock :: proc(m: ^Blocking_Mutex) -> bool {
  50. return unix.pthread_mutex_trylock(&m.handle) == 0
  51. }
  52. blocking_mutex_unlock :: proc(m: ^Blocking_Mutex) {
  53. assert(unix.pthread_mutex_unlock(&m.handle) == 0)
  54. }
  55. // Blocks until signalled, and then lets past exactly
  56. // one thread.
  57. Condition :: struct {
  58. handle: unix.pthread_cond_t,
  59. mutex: Condition_Mutex_Ptr,
  60. // NOTE(tetra, 2019-11-11): Used to mimic the more sane behavior of Windows' AutoResetEvent.
  61. // This means that you may signal the condition before anyone is waiting to cause the
  62. // next thread that tries to wait to just pass by uninterrupted, without sleeping.
  63. // Without this, signalling a condition will only wake up a thread which is already waiting,
  64. // but not one that is about to wait, which can cause your program to become out of sync in
  65. // ways that are hard to debug or fix.
  66. flag: bool, // atomically mutated
  67. }
  68. condition_init :: proc(c: ^Condition, mutex: Condition_Mutex_Ptr) -> bool {
  69. // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the condition.
  70. attrs: unix.pthread_condattr_t
  71. if unix.pthread_condattr_init(&attrs) != 0 {
  72. return false
  73. }
  74. defer unix.pthread_condattr_destroy(&attrs) // ignores destruction error
  75. c.flag = false
  76. c.mutex = mutex
  77. return unix.pthread_cond_init(&c.handle, &attrs) == 0
  78. }
  79. condition_destroy :: proc(c: ^Condition) {
  80. assert(unix.pthread_cond_destroy(&c.handle) == 0)
  81. c.handle = {}
  82. }
  83. // Awaken exactly one thread who is waiting on the condition
  84. condition_signal :: proc(c: ^Condition) -> bool {
  85. switch m in c.mutex {
  86. case ^Mutex:
  87. mutex_lock(m)
  88. defer mutex_unlock(m)
  89. atomic_swap(&c.flag, true, .Sequentially_Consistent)
  90. return unix.pthread_cond_signal(&c.handle) == 0
  91. case ^Blocking_Mutex:
  92. blocking_mutex_lock(m)
  93. defer blocking_mutex_unlock(m)
  94. atomic_swap(&c.flag, true, .Sequentially_Consistent)
  95. return unix.pthread_cond_signal(&c.handle) == 0
  96. }
  97. return false
  98. }
  99. // Awaken all threads who are waiting on the condition
  100. condition_broadcast :: proc(c: ^Condition) -> bool {
  101. return unix.pthread_cond_broadcast(&c.handle) == 0
  102. }
  103. // Wait for the condition to be signalled.
  104. // Does not block if the condition has been signalled and no one
  105. // has waited on it yet.
  106. condition_wait_for :: proc(c: ^Condition) -> bool {
  107. switch m in c.mutex {
  108. case ^Mutex:
  109. mutex_lock(m)
  110. defer mutex_unlock(m)
  111. // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
  112. // the thread that gets signalled and wakes up, discovers that the flag was taken and goes
  113. // back to sleep.
  114. // Though this overall behavior is the most sane, there may be a better way to do this that means that
  115. // the first thread to wait, gets the flag first.
  116. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  117. return true
  118. }
  119. for {
  120. if unix.pthread_cond_wait(&c.handle, &m.handle) != 0 {
  121. return false
  122. }
  123. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  124. return true
  125. }
  126. }
  127. return false
  128. case ^Blocking_Mutex:
  129. blocking_mutex_lock(m)
  130. defer blocking_mutex_unlock(m)
  131. // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
  132. // the thread that gets signalled and wakes up, discovers that the flag was taken and goes
  133. // back to sleep.
  134. // Though this overall behavior is the most sane, there may be a better way to do this that means that
  135. // the first thread to wait, gets the flag first.
  136. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  137. return true
  138. }
  139. for {
  140. if unix.pthread_cond_wait(&c.handle, &m.handle) != 0 {
  141. return false
  142. }
  143. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  144. return true
  145. }
  146. }
  147. return false
  148. }
  149. return false
  150. }
  151. // Wait for the condition to be signalled.
  152. // Does not block if the condition has been signalled and no one
  153. // has waited on it yet.
  154. condition_wait_for_timeout :: proc(c: ^Condition, duration: time.Duration) -> bool {
  155. switch m in c.mutex {
  156. case ^Mutex:
  157. mutex_lock(m)
  158. defer mutex_unlock(m)
  159. // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
  160. // the thread that gets signalled and wakes up, discovers that the flag was taken and goes
  161. // back to sleep.
  162. // Though this overall behavior is the most sane, there may be a better way to do this that means that
  163. // the first thread to wait, gets the flag first.
  164. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  165. return true
  166. }
  167. ns := time.duration_nanoseconds(duration)
  168. timeout: time.TimeSpec
  169. timeout.tv_sec = ns / 1e9
  170. timeout.tv_nsec = ns % 1e9
  171. for {
  172. if unix.pthread_cond_timedwait(&c.handle, &m.handle, &timeout) != 0 {
  173. return false
  174. }
  175. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  176. return true
  177. }
  178. }
  179. return false
  180. case ^Blocking_Mutex:
  181. blocking_mutex_lock(m)
  182. defer blocking_mutex_unlock(m)
  183. // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
  184. // the thread that gets signalled and wakes up, discovers that the flag was taken and goes
  185. // back to sleep.
  186. // Though this overall behavior is the most sane, there may be a better way to do this that means that
  187. // the first thread to wait, gets the flag first.
  188. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  189. return true
  190. }
  191. ns := time.duration_nanoseconds(duration)
  192. timeout: time.TimeSpec
  193. timeout.tv_sec = ns / 1e9
  194. timeout.tv_nsec = ns % 1e9
  195. for {
  196. if unix.pthread_cond_timedwait(&c.handle, &m.handle, &timeout) != 0 {
  197. return false
  198. }
  199. if atomic_swap(&c.flag, false, .Sequentially_Consistent) {
  200. return true
  201. }
  202. }
  203. return false
  204. }
  205. return false
  206. }
  207. thread_yield :: proc() {
  208. unix.sched_yield()
  209. }