Should `counter` be `volatile` in the following e...
# coroutines
n
Should
counter
be
volatile
in the following example?
Copy code
private var counter: Int = 1

    private val contexts = (1..5).map { newSingleThreadContext("thread#$it") }

    @Test
    fun testCoroutine() {
        runBlocking {
            repeat(100) {
                withContext(contexts[Random.nextInt(5)]) {
                    println(Thread.currentThread().name + "\t" + counter)
                    counter++
                }
            }
        }
    }
Thanks.
👀 2
s
I am no coroutine expert, but after experimenting with it on my own I think that
@Volatile
is not necessary here since you're not running any coroutines in parallel here.
e
no need for
@Volatile
, in essence every coroutine context switch is a sync point
n
@ephemient This was exactly what I wanted to know, thanks. Is this explicitly documented somewhere?
s
@Norbi could you please explain to me what was your motivation to add a
yield
call at the end of the
withContext
's block?
🥴 1
e
also fyi. if kotlin coroutines didn't sync, then not even
@Volatile
would help you. you'd have to use atomics
n
@Stanislav Kral "add a
yield
call"
- ehhh, I left it there accidentally from a previous version - thanks for pointing that out, I have removed it for clarity.
@ephemient "not even
@Volatile
would help you. you'd have to use atomics"
- I think it's not true, because only one thread is accessing
counter
at any given time, so there could be no race condition, so
@Volatile
is sufficient, there is no need for a higher level concurrency construct. My question was: is
@Volatile
necessary, or the
withContext()
call does some magic in the background to synchronize the threads' memory like e.g.
synchronized
and
ReentrantLock
do in normal multi-threaded (non-coroutine) code. The article you linked answered it, so
@Volatile
is really not necessary: > Even though a coroutine in Kotlin can execute on multiple threads it is just like a thread from a standpoint of mutable state. No two actions in the same coroutine can be concurrent. And just like with threads you should avoid sharing your mutable state between coroutines or you’ll have to worry about synchronization yourself. Thanks a lot 🙂🤓
d
I think in this case, since you're not actually doing any concurrency, it wouldn't be necessary. However, as soon as you have any two threads potentially acting on counter at the same time, you should use either atomics or a mutex.
j
The lack of concurrent execution does not imply that non-volatile changes will be visible across threads
💯 2
d
That’s true in general, yes, but as stated above, coroutines will hand it in this case.
e
that was basically my point. either volatile isn't enough, or volatile isn't needed. the latter is the case for kotlinx.coroutines, but in neither scenario does it help.
n
Thanks for the clarifications! It is important to know the execution and memory model - it's better later than never 😉
Just one more question related to > coroutines will hand it in this case Maybe do you know which part of the kotlinx-coroutines library implements this functionality? I'm very curious how such low-level functionality is implemented... :)
👀 1
d
It's probably a side-effect of how they safely pass the continuation between threads. If they didn't have proper happens-before semantics in that code, it would cause all kinds of problems.
j
It's also not kotlinx-coroutines which is a library built on top of coroutines which are a language feature (i.e., built-in to the Kotlin compiler)
d
If you want to be pedantic, coroutines are built on top of the "suspend/continuation" language feature, and a portion of them are in the standard library, not kotlinx-coroutines. In either case, the dispatch of them is handled by the kotlinx-coroutine library via CoroutineDispatchers. The implementation of the newSingleThreadContext uses
Executors.newScheduledThreadPool
to create the dispatcher worker, which uses various mechanisms to ensure correct concurrent behavior.
n
Thanks for all of you for your time to make me understand it more 🙂 After reading several articles and discussions on the internet, this seems to be a much-much more complex problem if someone wants to understand it thoroughly. So, if someone is interested, just some of the more interesting related things I found: • a good article describing volatile semantics: https://tarunjain07.medium.com/volatile-reentrant-lock-vs-synchronize-condition-variable-66e870a8434d • a question on StackOverflow very similar to mine: https://stackoverflow.com/a/58519259/306047 • a related (old) article: https://www.reddit.com/r/androiddev/comments/do687v/kotlin_coroutines_in_complex_flows/ • a serious bug in Kotlin/Native 1.9.20 (and a very good example why one cannot/shouldn't reason about coroutines using the Java Memory Model): https://www.reddit.com/r/Kotlin/comments/18cxi41/serious_bug_in_kotlin_native_19/ • serious semantic problems related to coroutines: https://youtrack.jetbrains.com/issue/KT-15514 and https://github.com/Kotlin/kotlinx.coroutines/issues/3480 • ARM vs X86 memory models: https://www.nickwilcox.com/blog/arm_vs_x86_memory_model/ My conclusion is: concurrency is very difficult 🙂 And as one of the JB team members wrote somewhere: "Multiplatform is hard." 😄 Thanks for your time, again!