From d7fc66f054e7b5112823c4c2df86497a0be1b4fd Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Fri, 5 Jan 2024 21:57:04 +0800 Subject: [PATCH] sync: add .try_lock() to mutex/rwmutex, add tests (#20381) --- vlib/sync/mutex_test.v | 42 ++++++++++++++++++++++ vlib/sync/rwmutex_test.v | 74 ++++++++++++++++++++++++++++++++++++++ vlib/sync/sync_darwin.c.v | 24 +++++++++++++ vlib/sync/sync_default.c.v | 24 +++++++++++++ vlib/sync/sync_windows.c.v | 39 ++++++++++++++++++++ 5 files changed, 203 insertions(+) create mode 100644 vlib/sync/mutex_test.v create mode 100644 vlib/sync/rwmutex_test.v diff --git a/vlib/sync/mutex_test.v b/vlib/sync/mutex_test.v new file mode 100644 index 0000000000..42bbffa948 --- /dev/null +++ b/vlib/sync/mutex_test.v @@ -0,0 +1,42 @@ +import sync + +struct Counter { +pub mut: + i int +} + +fn write_10000(mut co Counter, mut mx sync.Mutex) { + mx.@lock() + co.i = 10000 + mx.unlock() +} + +fn test_mutex() { + mut co := &Counter{10086} + mut mx := sync.new_mutex() + mx.@lock() + co.i = 888 + th := spawn write_10000(mut co, mut mx) + mx.unlock() // after mx unlock, thread write_10000 can continue + th.wait() + mx.destroy() + assert co.i == 10000 +} + +fn test_try_lock_mutex() { + // In Windows, try_lock only avalible after Windows 7 + $if windows { + $if !windows_7 ? { + return + } + } + mut mx := sync.new_mutex() + mx.@lock() + try_fail := mx.try_lock() + assert try_fail == false + mx.unlock() + try_sucess := mx.try_lock() + assert try_sucess == true + mx.unlock() // you must unlock it, after try_lock sucess + mx.destroy() +} diff --git a/vlib/sync/rwmutex_test.v b/vlib/sync/rwmutex_test.v new file mode 100644 index 0000000000..b860b98dbb --- /dev/null +++ b/vlib/sync/rwmutex_test.v @@ -0,0 +1,74 @@ +import sync +import time + +struct Counter { +pub mut: + i int +} + +fn write_10000(mut co Counter, mut mx sync.RwMutex) { + mx.@lock() + co.i = 10000 + mx.unlock() +} + +fn test_rwmutex() { + mut co := &Counter{10086} + mut mx := sync.new_rwmutex() + mx.@lock() + co.i = 888 + th1 := spawn write_10000(mut co, mut mx) + mx.unlock() // after mx unlock, thread write_10000 can continue + th1.wait() + assert co.i == 10000 + + mx.@rlock() + th2 := spawn write_10000(mut co, mut mx) // write_10000 will be blocked + co.i = 999 // for demo purpose, don't modify data in rlock! + time.sleep(1 * time.millisecond) + assert co.i == 999 + mx.runlock() // after rlock released, write_10000 can continue + th2.wait() + assert co.i == 10000 + mx.destroy() +} + +fn test_try_lock_rwmutex() { + // In Windows, try_lock only avalible after Windows 7 + $if windows { + $if !windows_7 ? { + return + } + } + mut mx := sync.new_rwmutex() + + // try_rlock will always fail when mx locked + mx.@lock() + try_fail_reading1 := mx.try_rlock() + try_fail_writing1 := mx.try_wlock() + assert try_fail_reading1 == false + assert try_fail_writing1 == false + + mx.unlock() + + // try_rlock will always sucess when mx unlocked, + // multiple try_rlock can apply to the same mx + try_sucess_reading2 := mx.try_rlock() + try_sucess_reading3 := mx.try_rlock() + assert try_sucess_reading2 == true + assert try_sucess_reading3 == true + + // if mx is rlocked, then the try_wlock will fail + try_fail_writing2 := mx.try_wlock() + assert try_fail_writing2 == false + + mx.runlock() + mx.runlock() // you must release rlock mutiple times, as it was rlocked multiple times + + // after mx release all rlock, try_wlock will sucess + try_sucess_writing3 := mx.try_wlock() + assert try_sucess_writing3 == true + + mx.unlock() // you must unlock it, after try_wlock sucess + mx.destroy() +} diff --git a/vlib/sync/sync_darwin.c.v b/vlib/sync/sync_darwin.c.v index 7c2e5cd8a8..84e97317dc 100644 --- a/vlib/sync/sync_darwin.c.v +++ b/vlib/sync/sync_darwin.c.v @@ -12,6 +12,7 @@ import time @[trusted] fn C.pthread_mutex_init(voidptr, voidptr) int fn C.pthread_mutex_lock(voidptr) int +fn C.pthread_mutex_trylock(voidptr) int fn C.pthread_mutex_unlock(voidptr) int fn C.pthread_mutex_destroy(voidptr) int fn C.pthread_rwlockattr_init(voidptr) int @@ -20,6 +21,8 @@ fn C.pthread_rwlockattr_setpshared(voidptr, int) int fn C.pthread_rwlock_init(voidptr, voidptr) int fn C.pthread_rwlock_rdlock(voidptr) int fn C.pthread_rwlock_wrlock(voidptr) int +fn C.pthread_rwlock_tryrdlock(voidptr) int +fn C.pthread_rwlock_trywrlock(voidptr) int fn C.pthread_rwlock_unlock(voidptr) int fn C.pthread_rwlock_destroy(voidptr) int fn C.pthread_condattr_init(voidptr) int @@ -119,6 +122,13 @@ pub fn (mut m Mutex) @lock() { C.pthread_mutex_lock(&m.mutex) } +// try_lock try to lock the mutex instance and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m Mutex) try_lock() bool { + return C.pthread_mutex_trylock(&m.mutex) == 0 +} + // unlock unlocks the mutex instance. The mutex is released, and one of // the other threads, that were blocked, because they called @lock can continue. @[inline] @@ -158,6 +168,20 @@ pub fn (mut m RwMutex) @lock() { C.pthread_rwlock_wrlock(&m.mutex) } +// try_rlock try to lock the given RwMutex instance for reading and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m RwMutex) try_rlock() bool { + return C.pthread_rwlock_tryrdlock(&m.mutex) == 0 +} + +// try_wlock try to lock the given RwMutex instance for writing and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m RwMutex) try_wlock() bool { + return C.pthread_rwlock_trywrlock(&m.mutex) == 0 +} + // destroy frees the resources associated with the rwmutex instance. // Note: the mutex itself is not freed. @[inline] diff --git a/vlib/sync/sync_default.c.v b/vlib/sync/sync_default.c.v index 6a4d8aa878..a8c921e292 100644 --- a/vlib/sync/sync_default.c.v +++ b/vlib/sync/sync_default.c.v @@ -16,6 +16,7 @@ $if !android { @[trusted] fn C.pthread_mutex_init(voidptr, voidptr) int fn C.pthread_mutex_lock(voidptr) int +fn C.pthread_mutex_trylock(voidptr) int fn C.pthread_mutex_unlock(voidptr) int fn C.pthread_mutex_destroy(voidptr) int fn C.pthread_rwlockattr_init(voidptr) int @@ -25,6 +26,8 @@ fn C.pthread_rwlockattr_destroy(voidptr) int fn C.pthread_rwlock_init(voidptr, voidptr) int fn C.pthread_rwlock_rdlock(voidptr) int fn C.pthread_rwlock_wrlock(voidptr) int +fn C.pthread_rwlock_tryrdlock(voidptr) int +fn C.pthread_rwlock_trywrlock(voidptr) int fn C.pthread_rwlock_unlock(voidptr) int fn C.pthread_rwlock_destroy(voidptr) int fn C.sem_init(voidptr, int, u32) int @@ -106,6 +109,13 @@ pub fn (mut m Mutex) @lock() { C.pthread_mutex_lock(&m.mutex) } +// try_lock try to lock the mutex instance and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m Mutex) try_lock() bool { + return C.pthread_mutex_trylock(&m.mutex) == 0 +} + // unlock unlocks the mutex instance. The mutex is released, and one of // the other threads, that were blocked, because they called @lock can continue. @[inline] @@ -144,6 +154,20 @@ pub fn (mut m RwMutex) @lock() { C.pthread_rwlock_wrlock(&m.mutex) } +// try_rlock try to lock the given RwMutex instance for reading and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m RwMutex) try_rlock() bool { + return C.pthread_rwlock_tryrdlock(&m.mutex) == 0 +} + +// try_wlock try to lock the given RwMutex instance for writing and return immediately. +// If the mutex was already locked, it will return false. +@[inline] +pub fn (mut m RwMutex) try_wlock() bool { + return C.pthread_rwlock_trywrlock(&m.mutex) == 0 +} + // destroy frees the resources associated with the rwmutex instance. // Note: the mutex itself is not freed. pub fn (mut m RwMutex) destroy() { diff --git a/vlib/sync/sync_windows.c.v b/vlib/sync/sync_windows.c.v index 0cdd272db8..2d19677e89 100644 --- a/vlib/sync/sync_windows.c.v +++ b/vlib/sync/sync_windows.c.v @@ -76,6 +76,19 @@ pub fn (mut m Mutex) @lock() { C.AcquireSRWLockExclusive(&m.mx) } +// try_lock try to lock the mutex instance and return immediately. +// If the mutex was already locked, it will return false. +// NOTE: try_lock require Windows 7 or later. Before Windows 7, it will always return false. +// NOTE: To enable try_lock , you should compile your project with `-d windows_7`, like `v . -d windows_7` +// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-tryacquiresrwlockexclusive +pub fn (mut m Mutex) try_lock() bool { + $if windows_7 ? { + return C.TryAcquireSRWLockExclusive(&m.mx) != 0 + } $else { + return false + } +} + pub fn (mut m Mutex) unlock() { C.ReleaseSRWLockExclusive(&m.mx) } @@ -89,6 +102,32 @@ pub fn (mut m RwMutex) @lock() { C.AcquireSRWLockExclusive(&m.mx) } +// try_rlock try to lock the given RwMutex instance for reading and return immediately. +// If the mutex was already locked, it will return false. +// NOTE: try_rlock require Windows 7 or later. Before Windows 7, it will always return false. +// NOTE: To enable try_rlock , you should compile your project with `-d windows_7`, like `v . -d windows_7` +// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-tryacquiresrwlockshared +pub fn (mut m RwMutex) try_rlock() bool { + $if windows_7 ? { + return C.TryAcquireSRWLockShared(&m.mx) != 0 + } $else { + return false + } +} + +// try_wlock try to lock the given RwMutex instance for writing and return immediately. +// If the mutex was already locked, it will return false. +// NOTE: try_wlock require Windows 7 or later. Before Windows 7, it will always return false. +// NOTE: To enable try_wlock , you should compile your project with `-d windows_7`, like `v . -d windows_7` +// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-tryacquiresrwlockexclusive +pub fn (mut m RwMutex) try_wlock() bool { + $if windows_7 ? { + return C.TryAcquireSRWLockExclusive(&m.mx) != 0 + } $else { + return false + } +} + // Windows SRWLocks have different function to unlock // So provide two functions here, too, to have a common interface pub fn (mut m RwMutex) runlock() {