Learning Kotlin

Mutex For Kotlin Coroutines

How to protect your shared resources

John Hoegy
Mobile App Development Publication

--

In this photograph taken on December 22, 2017, Indian traffic policeman Ranjeet Singh directs traffic at an intersection in Indore. Photo: AFP

Use the Kotlin mutex to protect resources shared by coroutines

Coroutines are Kotlin’s answer to the need for concurrency: they allow our code to do multiple things at the same time. Developers embrace coroutines because they are a superior alternative to threads; coroutines are fast, inexpensive, and easy to use. However, we must be careful when it comes to resources that are shared by coroutines. This is where the mutex comes into play.

In an application that uses coroutines to implement concurrency, each individual coroutine will, for the most part, run its own code, with its own variables, objects, control structures, and so on. However, there may be times where two or more coroutines need access to the same thing at the same time. If we don’t have a mechanism for controlling that access, things could get messy.

The mutex is Kotlin’s mechanism to help us control access to shared resources via a suspending function. The mutex provides us with mutual exclusion: it allows a single coroutine to access a shared resource while any other coroutines attempting to access that same shared resource will suspend execution. The mutex helps to keep things working smoothly and deliver expected outcomes in shared resource situations.

A real-world example to help understand the mutex

Here’s an example from everyday life to help understand the value of the mutex. Let’s use the analogy of an intersection and a traffic cop.

Scenario 1 (bad):

Imagine that in intersection 1, there is no traffic cop. Two drivers arrive at the intersection simultaneously: one traveling north and the other traveling west. With no traffic cop to control access to the intersection, there will be a crash (sounds like some apps we’ve all encountered, no doubt).

No cop results in a car crash!

Scenario 2 (good):

Now let’s change the scenario; our intersection is now dutifully patrolled by a traffic cop. Again, two cars arrive at the intersection simultaneously: one northbound and one westbound. But this time, the traffic cop stops the westbound car to let the northbound car go through. Next, the cop waves the westbound car through. No crash, and the drivers safely reach their destinations.

With the cop means no crash!

In these examples, think of the cars as the coroutines, the intersection as the shared resource, and the traffic cop as the mutex. The traffic cop (mutex) allows the cars (coroutines) to access the intersection (resource) in a controlled and safe manner.

Now let’s code!

Hopefully, the traffic cop analogy helped to convey the value of the mutex. Now let’s build a simple program that uses a mutex to demonstrate this. In our coding scenario, we will have four coroutines running simultaneously. Two of the coroutines will access one shared resource without using a mutex, and the other two coroutines will access a different shared resource while using a mutex. Each of the two resources will do some work to arrive at a value, which will cumulate into a total for each pair of coroutines.

For purposes of keeping things easy to follow, the “work” that our shared resource will do will be to increment a counter ten times inside of a for loop.

Each of the four coroutines will access the shared resource 500 times. This means that each of the two resources will be accessed 1,000 times total. And because each shared resource will increment the counter 10 times, we can expect that ideally, each shared resource tally will end up being 10,000.

Please note that the mutex and non-mutex scenarios are the same, and ideally will produce the same outcome. The only difference is whether the mutex is used.

  • 2 coroutines * access shared resource 500 times * 10 increments per access
  • tally of 10,000 increments
No mutex versus with mutex

Below is the code for the tests. The mutex is used in the function incrementCounterByTenWithMutex() where it surrounds the shared resource code with a mutex.withLock {} block (line 43, below).

The mutex is used in “incrementCounterByTenWithMutex()” on line 43. Contrast with no mutex usage for function “incrementCounterByTenNoMutex()”, line 50.

[Please note — each coroutine has been created with its own context to simulate what would likely happen in a real-world program.]

Below are the results from running the test ten times. These results show that not using a mutex can be risky! Half of the non-mutex coroutines worked perfectly and the other half did not. In some other test runs that were performed, the non-mutex worked perfectly up to six times in a row! Notice how inconsistent this behavior is.

So, what happened with half of the non-mutex cases? In certain non-mutex cases, multiple coroutines are accessing the shared resource simultaneously, perhaps changing the for loop index value, thereby causing the total to not tally properly.

One thing to be mindful of with this example is that there are many touchpoints (10,000 total). You may not have as many touchpoints in a real-world scenario. What does this mean to you? Perhaps you would not see errors as frequently as we have seen above. Instead, you may have intermittent errors that are difficult to replicate and identify, which could lead to a poor customer experience.

The take-away

Please be mindful of whether your coroutines can simultaneously access a shared resource. If they can, please consider using a mutex to control access to that shared resource. Doing so will help to keep things running smoothly by helping to ensure expected results. Your customers will appreciate it!

--

--