cgen, sync: implement separate read/write locks for `rwshared` types (#5687)

pull/5693/head
Uwe Krüger 2020-07-05 22:53:28 +02:00 committed by GitHub
parent 3cd9e2cab7
commit c3614c0e38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 229 additions and 19 deletions

View File

@ -389,12 +389,24 @@ fn C.ReleaseMutex(voidptr) bool
fn C.CreateEvent(int, bool, bool, byteptr) voidptr fn C.CreateEvent(int, bool, bool, byteptr) voidptr
fn C.SetEvent(voidptr) int fn C.SetEvent(voidptr) int
fn C.InitializeSRWLock(voidptr)
fn C.AcquireSRWLockShared(voidptr)
fn C.AcquireSRWLockExclusive(voidptr)
fn C.ReleaseSRWLockShared(voidptr)
fn C.ReleaseSRWLockExclusive(voidptr)
// pthread.h // pthread.h
fn C.pthread_mutex_init(voidptr, voidptr) int fn C.pthread_mutex_init(voidptr, voidptr) int
fn C.pthread_mutex_lock(voidptr) int fn C.pthread_mutex_lock(voidptr) int
fn C.pthread_mutex_unlock(voidptr) int fn C.pthread_mutex_unlock(voidptr) int
fn C.pthread_rwlockattr_init(voidptr) int
fn C.pthread_rwlockattr_setkind_np(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_unlock(voidptr) int
fn C.read(fd int, buf voidptr, count size_t) int fn C.read(fd int, buf voidptr, count size_t) int
fn C.write(fd int, buf voidptr, count size_t) int fn C.write(fd int, buf voidptr, count size_t) int
fn C.close(fd int) int fn C.close(fd int) int

View File

@ -11,12 +11,32 @@ pub struct Mutex {
mutex C.pthread_mutex_t mutex C.pthread_mutex_t
} }
[ref_only]
pub struct RwMutex {
mutex C.pthread_rwlock_t
}
[ref_only]
struct RwMutexAttr {
attr C.pthread_rwlockattr_t
}
pub fn new_mutex() &Mutex { pub fn new_mutex() &Mutex {
m := &Mutex{} m := &Mutex{}
C.pthread_mutex_init(&m.mutex, C.NULL) C.pthread_mutex_init(&m.mutex, C.NULL)
return m return m
} }
pub fn new_rwmutex() &RwMutex {
m := &RwMutex{}
a := &RwMutexAttr{}
C.pthread_rwlockattr_init(&a.attr)
// Give writer priority over readers
C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
C.pthread_rwlock_init(&m.mutex, &a.attr)
return m
}
// m_lock(), for *manual* mutex handling, since `lock` is a keyword // m_lock(), for *manual* mutex handling, since `lock` is a keyword
pub fn (mut m Mutex) m_lock() { pub fn (mut m Mutex) m_lock() {
C.pthread_mutex_lock(&m.mutex) C.pthread_mutex_lock(&m.mutex)
@ -25,3 +45,22 @@ pub fn (mut m Mutex) m_lock() {
pub fn (mut m Mutex) unlock() { pub fn (mut m Mutex) unlock() {
C.pthread_mutex_unlock(&m.mutex) C.pthread_mutex_unlock(&m.mutex)
} }
// RwMutex has separate read- and write locks
pub fn (mut m RwMutex) r_lock() {
C.pthread_rwlock_rdlock(&m.mutex)
}
pub fn (mut m RwMutex) w_lock() {
C.pthread_rwlock_wrlock(&m.mutex)
}
// Windows SRWLocks have different function to unlock
// So provide two functions here, too, to have a common interface
pub fn (mut m RwMutex) r_unlock() {
C.pthread_rwlock_unlock(&m.mutex)
}
pub fn (mut m RwMutex) w_unlock() {
C.pthread_rwlock_unlock(&m.mutex)
}

View File

@ -22,6 +22,12 @@ mut:
writer_sem u32 // writer semaphones writer_sem u32 // writer semaphones
} }
[ref_only]
pub struct RwMutex {
mut:
mx C.SRWLOCK // mutex handle
}
enum MutexState { enum MutexState {
broken broken
waiting waiting
@ -43,6 +49,12 @@ pub fn new_mutex() &Mutex {
return sm return sm
} }
pub fn new_rwmutex() &RwMutex {
m := &RwMutex{}
C.InitializeSRWLock(&m.mx)
return m
}
pub fn (mut m Mutex) m_lock() { pub fn (mut m Mutex) m_lock() {
// if mutex handle not initalized // if mutex handle not initalized
if isnil(m.mx) { if isnil(m.mx) {
@ -80,6 +92,25 @@ pub fn (mut m Mutex) unlock() {
m.state = .released m.state = .released
} }
// RwMutex has separate read- and write locks
pub fn (mut m RwMutex) r_lock() {
C.AcquireSRWLockShared(&m.mx)
}
pub fn (mut m RwMutex) w_lock() {
C.AcquireSRWLockExclusive(&m.mx)
}
// Windows SRWLocks have different function to unlock
// So provide two functions here, too, to have a common interface
pub fn (mut m RwMutex) r_unlock() {
C.ReleaseSRWLockShared(&m.mx)
}
pub fn (mut m RwMutex) w_unlock() {
C.ReleaseSRWLockExclusive(&m.mx)
}
pub fn (mut m Mutex) destroy() { pub fn (mut m Mutex) destroy() {
if m.state == .waiting { if m.state == .waiting {
m.unlock() // unlock mutex before destroying m.unlock() // unlock mutex before destroying

View File

@ -402,8 +402,7 @@ fn (mut g Gen) find_or_register_shared(t table.Type, base string) string {
if (is_rw && t_idx in g.rwshareds) || (!is_rw && t_idx in g.shareds) { if (is_rw && t_idx in g.rwshareds) || (!is_rw && t_idx in g.shareds) {
return sh_typ return sh_typ
} }
// TODO: These two should become different... mtx_typ := if is_rw { 'sync__RwMutex' } else { 'sync__Mutex' }
mtx_typ := if is_rw { 'sync__Mutex' } else { 'sync__Mutex' }
g.hotcode_definitions.writeln('struct $sh_typ { $base val; $mtx_typ* mtx; };') g.hotcode_definitions.writeln('struct $sh_typ { $base val; $mtx_typ* mtx; };')
g.typedefs2.writeln('typedef struct $sh_typ $sh_typ;') g.typedefs2.writeln('typedef struct $sh_typ $sh_typ;')
// println('registered shared type $sh_typ') // println('registered shared type $sh_typ')
@ -2058,32 +2057,31 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
} }
fn (mut g Gen) lock_expr(node ast.LockExpr) { fn (mut g Gen) lock_expr(node ast.LockExpr) {
mut lock_prefixes := []byte{len: 0, cap: node.lockeds.len}
for id in node.lockeds { for id in node.lockeds {
name := id.name name := id.name
deref := if id.is_mut { '->' } else { '.' } // fields of `id` itself have no valid information, so look up type(flags) in scope
// TODO: use 3 different locking functions obj := g.file.scope.innermost(id.pos.pos).find(name) or {
if node.is_rlock { verror('cgen: unable to get type for lock variable $name')
g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);') ast.ScopeObject{}
} else if id.var_info().typ.has_flag(.atomic_or_rw) {
g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);')
} else {
g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);')
} }
typ := if obj is ast.Var { (*(obj as ast.Var)).typ } else { table.Type(0) }
deref := if id.is_mut { '->' } else { '.' }
lock_prefix := if node.is_rlock { `r` } else { if typ.has_flag(.atomic_or_rw) { `w` } else { `m` } }
lock_prefixes << lock_prefix // keep for unlock
mut_prefix := if lock_prefix == `m` { '' } else { 'Rw' }
g.writeln('sync__${mut_prefix}Mutex_${lock_prefix:c}_lock(${name}${deref}mtx);')
} }
g.stmts(node.stmts) g.stmts(node.stmts)
// unlock in reverse order // unlock in reverse order
for i := node.lockeds.len-1; i >= 0; i-- { for i := node.lockeds.len-1; i >= 0; i-- {
id := node.lockeds[i] id := node.lockeds[i]
lock_prefix := lock_prefixes[i]
name := id.name name := id.name
deref := if id.is_mut { '->' } else { '.' } deref := if id.is_mut { '->' } else { '.' }
// TODO: use 3 different unlocking functions mut_prefix := if lock_prefix == `m` { '' } else { 'Rw' }
if node.is_rlock { unlock_type := if lock_prefix == `m` { '' } else { '${lock_prefix:c}_' }
g.writeln('sync__Mutex_unlock(${name}${deref}mtx);') g.writeln('sync__${mut_prefix}Mutex_${unlock_type}unlock(${name}${deref}mtx);')
} else if id.var_info().typ.has_flag(.atomic_or_rw) {
g.writeln('sync__Mutex_unlock(${name}${deref}mtx);')
} else {
g.writeln('sync__Mutex_unlock(${name}${deref}mtx);')
}
} }
} }
@ -2752,6 +2750,7 @@ const (
fn (mut g Gen) struct_init(struct_init ast.StructInit) { fn (mut g Gen) struct_init(struct_init ast.StructInit) {
styp := g.typ(struct_init.typ) styp := g.typ(struct_init.typ)
mut shared_styp := '' // only needed for shared &St{... mut shared_styp := '' // only needed for shared &St{...
mut share_prefix := '' // 'rw' for `rwshared`
if styp in skip_struct_init { if styp in skip_struct_init {
g.go_back_out(3) g.go_back_out(3)
return return
@ -2765,6 +2764,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
mut shared_typ := struct_init.typ.set_flag(.shared_f) mut shared_typ := struct_init.typ.set_flag(.shared_f)
if g.is_rwshared { if g.is_rwshared {
shared_typ = shared_typ.set_flag(.atomic_or_rw) shared_typ = shared_typ.set_flag(.atomic_or_rw)
share_prefix = 'rw'
} }
shared_styp = g.typ(shared_typ) shared_styp = g.typ(shared_typ)
g.writeln('($shared_styp*)memdup(&($shared_styp){.val = ($styp){') g.writeln('($shared_styp*)memdup(&($shared_styp){.val = ($styp){')
@ -2870,7 +2870,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
} }
g.write('}') g.write('}')
if g.is_shared { if g.is_shared {
g.write(', .mtx = sync__new_mutex()}') g.write(', .mtx = sync__new_${share_prefix}mutex()}')
if is_amp { if is_amp {
g.write(', sizeof($shared_styp))') g.write(', sizeof($shared_styp))')
} }

View File

@ -195,10 +195,22 @@ $c_common_macros
#pragma comment(lib, "Dbghelp.lib") #pragma comment(lib, "Dbghelp.lib")
extern wchar_t **_wenviron; extern wchar_t **_wenviron;
#elif !defined(SRWLOCK_INIT)
// these seem to be missing on Windows tcc
typedef struct SRWLOCK { void* SRWLOCK; } SRWLOCK;
void InitializeSRWLock(void*);
void AcquireSRWLockShared(void*);
void AcquireSRWLockExclusive(void*);
void ReleaseSRWLockShared(void*);
void ReleaseSRWLockExclusive(void*);
#endif #endif
#else #else
#include <pthread.h> #include <pthread.h>
#ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
// musl does not have that
#define pthread_rwlockattr_setkind_np(a, b)
#endif
#endif #endif
// g_live_info is used by live.info() // g_live_info is used by live.info()

View File

@ -0,0 +1,58 @@
import sync
import time
struct St {
mut:
a int
}
fn f(rwshared x St, shared z St) {
for _ in 0..reads_per_thread {
rlock x { // other instances may read at the same time
time.sleep_ms(1)
assert x.a == 7 || x.a == 5
}
}
lock z {
z.a--
}
}
const (
reads_per_thread = 30
read_threads = 10
writes = 5
)
fn test_shared_lock() {
// object with separate read/write lock
rwshared x := &St{
a: 5
}
shared z := &St{
a: read_threads
}
for _ in 0..read_threads {
go f(rwshared x, shared z)
}
for i in 0..writes {
lock x { // wait for ongoing reads to finish, don't start new ones
x.a = 17 // this should never be read
time.sleep_ms(50)
x.a = if (i&1) == 0 { 7 } else { 5 }
} // now new reads are possible again
time.sleep_ms(20)
}
// wait until all read threads are finished
for finished := false; ; {
mut rr := 0
lock z {
rr = z.a
finished = z.a == 0
}
if finished {
break
}
time.sleep_ms(100)
}
}

View File

@ -0,0 +1,58 @@
import sync
import time
struct St {
mut:
a int
}
fn (rwshared x St) f(shared z St) {
for _ in 0..reads_per_thread {
rlock x { // other instances may read at the same time
time.sleep_ms(1)
assert x.a == 7 || x.a == 5
}
}
lock z {
z.a--
}
}
const (
reads_per_thread = 30
read_threads = 10
writes = 5
)
fn test_shared_lock() {
// object with separate read/write lock
rwshared x := &St{
a: 5
}
shared z := &St{
a: read_threads
}
for _ in 0..read_threads {
go x.f(shared z)
}
for i in 0..writes {
lock x { // wait for ongoing reads to finish, don't start new ones
x.a = 17 // this value should never be read
time.sleep_ms(50)
x.a = if (i&1) == 0 { 7 } else { 5 }
} // now new reads are possible again
time.sleep_ms(20)
}
// wait until all read threads are finished
for finished := false; ; {
mut rr := 0
lock z {
rr = z.a
finished = z.a == 0
}
if finished {
break
}
time.sleep_ms(100)
}
}