Is low level synchronized block still cool in coro...
# coroutines
u
Is low level synchronized block still cool in coroutines? The usual copy load+save use case... Or maybe updating StateFlow instance via reducing of the current value + change
m
Depends on what you are trying to achieve.
l
synchronized
over suspension points is not cool (expect issues), and should be replaced by
Mutex
, but otherwise, it's fine in the JVM.
e
@Synchronized
just sets a flag on the method in the classfile, so it probably doesn't cause anything to break, but it won't work either; as far as the JVM is concerned, the function is being called and is returning at every suspension point
synchronized(lock)
translates to monitorenter and monitorexit bytecode instructions. that will be problematic for sure as the object will stay locked on the original thread, but the coroutine may not be resumed there
u
Yes I should have been clearer. Its only blocking code within the synchronized block
Copy code
synchronized {
   stateFlow.value = stateFlow.value.copy(foo = bar)
}
(stateflow syntax might be incorrect, im new to it)
z
You don't need a lock for that,
MutableStateFlow
has a
compareAndSet
method you can use for lock-free atomic updates: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/compare-and-set.html
👍 1
e
also, if you're trying to handle updates coming from multiple sources, maybe you could try to process them in a single coroutine, effectively serializing them
u
@Zach Klippenstein (he/him) [MOD] ehm I might be confused but how would I use that? Id expect something like this
Copy code
val mutableStateFlow = MutableStateFlow(State())
mutableStateFlow.set {
    copy(counter + 1)
}

data class State(counter: Int = 0)
and have the set function be thread safe
z
Not hard to write that function yourself:
Copy code
lateinit var old: YourState
lateinit var new: YourState
do {
  old = mutableStateFlow.value
  new = old.copy(foo = bar)
} while (!mutableStateFlow.compareAndSet(old, new))
u
yea I had this so far
Copy code
fun <T> MutableStateFlow<T>.set(reduce: T.() -> T) {
    synchronized(this) {
        value = value.reduce()
    }
}
so the difference is the classic lock vs lockfree concurrency thing? nothing coroutine about this right?
z
But using a mutex is probably simpler
u
thread mutex or coroutine Mutex?
z
Coroutine mutex
u
hmm why, does it matter? is it to make it multiplatform friendly in future?
l
@ursus Threads take up space in memory (RAM for their heap and stack), so blocking them just to wait is wasting them.
u
im aware thanks, this is however just a simple assigment just need to make sure everyone sees the latest value before updating it, bit of a overkill to use some highlevel coroutine object, no?
l
@Zach Klippenstein (he/him) [MOD] Other reasons to pick coroutines mutex over java.util.concurrent mutex?
e
do not use java.util.concurrent locks in a coroutine. it'll block instead of suspending
and if you're talking about coroutines versus traditional concurrency, I think the overall benefits of structured concurrency are clear
u
Im almost sure all sqlite libraries will still block transactions blocks on thread level regardless, I dont this its useless
e
assuming you're talking about java.util.concurrent.locks.ReentrantLock (there's no standard type specifically called Mutex), you also can't use it because it must be unlocked on the same thread that it was locked on
the coroutine dispatcher will not necessarily resume your coroutine on the same (java) thread. same problem as monitorenter/monitorexit
u
No im talking about synchronized block. Underlying dispatcher thread will get blocked and wait as usual. Theoretically less performant but will work correctly, no?
e
no, even with a synchronized block, as soon as you hit a suspension point, the actual JVM function returns
(even though it doesn't look like it looking at the kotlin suspend fun)
actually playing around with it now, it looks like the compiler actually prevents you from doing that altogether
Copy code
suspend fun foo(lock: Any, block: suspend () -> Unit) {
    synchronized(lock) {
        block()
    }
}
Copy code
foo.kt:3:9: error: the 'invoke' suspension point is inside a critical section
        block()
        ^
u
I mean if sychnronization block doesnt work with coroutines, then how can all the java concurent data structures work? im sure its not just lockless threadsafety in all of them
e
atomic references work
and it shouldn't be a problem in pure java code that doesn't have suspension points in it
u
even the basic example in the official coroutine samples uses AtomicInteger to count emits of parallel coroutines and says to use this if possible
e
atomic doesn't use synchronization blocks
u
sure but ConcurrentHashMap does
e
ConcurrentHashMap can't possibly have any suspend points while in the middle of a synchronized block