Coroutines and Mutexes: The Ultimate Guide to Kotlin Mutexes

introduction

Today we will delve into the principle of Mutex (mutex lock) in Kotlin and its usage techniques in actual development. Mutex is a key tool in multi-threaded programming, which can effectively solve the problem of race conditions that may occur when multi-threads access shared resources.

Basic principles of Mutex

Mutex is the abbreviation of mutex, which is a synchronization tool used to protect shared resources and ensure that only one thread can access the resource at any time. In Kotlin, Mutex is kotlinx.coroutines.syncimplemented through packages.

Implementation principle of Mutex

The implementation of Mutex is based on the concepts of suspending functions and coroutines. When a coroutine requests to enter a critical section protected by a Mutex, if the Mutex is already occupied, the requested coroutine will be suspended until the Mutex is available. This prevents multiple coroutines from accessing shared resources at the same time and ensures thread safety.

State variables

MutexThe state variables of the class include the following two:

  • owner: Indicates the owner of the lock.
  • availablePermits: Indicates the number of available licenses.

initialization

MutexThe initialization of the class will ownerinitialize the variable to NO_OWNER, indicating that the lock has not been acquired by any thread.

Get lock

MutexThe class's lock()method will attempt to acquire the lock. If the lock has not been acquired by other threads, this method will successfully acquire the lock. If the lock has been acquired by another thread, this method will put the thread into the waiting queue and block the thread.

lock()The method is implemented as follows:

suspend fun lock(owner: Any?) {
    
    
    if (tryLock(owner)) return
    lockSuspend(owner)
}

private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable<Unit> {
    
     cont ->
    val contWithOwner = CancellableContinuationWithOwner(cont, owner)
    acquire(contWithOwner)
}

lock()The method first calls tryLock()the method to try to acquire the lock. If tryLock()the method succeeds, it means that the lock has not been acquired by other threads, and lock()the method will return directly.

If tryLock()the method fails, it means that the lock has been acquired by another thread. In this case, lock()the method calls lockSuspend()the method to acquire the lock.

lockSuspend()method creates an CancellableContinuationWithOwnerobject and passes it to acquire()the method. acquire()The method will try to acquire the lock. If successful, CancellableContinuationWithOwnerthe object's ownervariables are set as ownerparameters. If it fails, the object will CancellableContinuationWithOwnerbe placed in the waiting queue.

release lock

MutexThe class unlock()method releases the lock. If the owner of the lock is the current thread, this method will successfully release the lock. If the owner of the lock is not the current thread, this method throws an exception.

unlock()The method is implemented as follows:

override fun unlock(owner: Any?) {
    
    
    while (true) {
    
    
        // Is this mutex locked?
        check(isLocked) {
    
     "This mutex is not locked" }
        // Read the owner, waiting until it is set in a spin-loop if required.
        val curOwner = this.owner.value
        if (curOwner === NO_OWNER) continue // <-- ATTENTION, BLOCKING PART HERE
        // Check the owner.
        check(curOwner === owner || owner == null) {
    
     "This mutex is locked by $curOwner, but $owner is expected" }
        // Try to clean the owner first. We need to use CAS here to synchronize with concurrent `unlock(..)`-s.
        if (!this.owner.compareAndSet(curOwner, NO_OWNER)) continue
        // Release the semaphore permit at the end.
        release()
        return
    }
}

unlock()The method first checks whether the lock has been acquired. If the lock is not acquired, an exception will be thrown.

If the lock has already been acquired, the owner of the lock is acquired. Then, it checks whether the owner of the lock is the current thread. If so, the lock's owner is set to NO_OWNER, and a license is released.

Other details

MutexThe class also provides some additional details:

  • holdsLock()Method used to check whether the current thread holds the lock.
  • tryLock()Method is used to try to acquire the lock. If successful, it returns immediately. If it fails, it returns immediately.
  • onLockProperties are used to specify the actions to be performed by the coroutine when acquiring the lock.

MutexThe implementation principle of the class is based on semaphore. MutexThe class maintains a availablePermitsvariable representing the number of available licenses. If availablePermitsthe value of the variable is 0, it means that the lock has been acquired by other threads

Tips for using Mutex

Below we will introduce some techniques for using Mutex in actual development, as well as precautions and optimization techniques.

How to use Mutex to deal with specific problems

Consider a simple Android project scenario where multiple coroutines make network requests and update the UI simultaneously. In this scenario, we want to ensure that network requests and UI updates are in the correct order to avoid race conditions and UI inconsistencies. The following is a sample code using Mutex:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex

class MainActivity : AppCompatActivity() {
    
    

    private val mutex = Mutex()

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 启动多个协程进行网络请求和 UI 更新
        repeat(5) {
    
    
            launch {
    
    
                performNetworkRequestAndUIUpdate(it)
            }
        }
    }

    private suspend fun performNetworkRequestAndUIUpdate(index: Int) {
    
    
        // 模拟网络请求
        delay(1000)

        // 使用 Mutex 保护对 UI 更新的临界区域
        mutex.withLock {
    
    
            updateUI("Task $index completed")
        }
    }

    private fun updateUI(message: String) {
    
    
        // 在主线程更新 UI
        runOnUiThread {
    
    
            textView.append("$message\n")
        }
    }
}

In this example, performNetworkRequestAndUIUpdatethe function simulates a network request and then uses a Mutex to protect critical areas for UI updates. This way, we ensure the ordering of network requests and UI updates, avoiding possible race conditions.

The functions and effects of Mutex

Protect critical areas for UI updates

By performNetworkRequestAndUIUpdateusing a Mutex in the function, we ensure that access to UI updates is thread-safe. At any time, only one coroutine can perform update operations, avoiding problems caused by multiple coroutines modifying the UI at the same time.

Avoid race conditions and data inconsistencies

In Android, as it involves UI operations, it is crucial to ensure that the UI is updated in the correct order on the main thread. The role of Mutex is to coordinate the access of multiple coroutines to the UI to avoid race conditions and data inconsistency.

Simplify synchronization control of asynchronous operations

Mutex provides a simple and efficient way to synchronize multiple coroutines, especially when it comes to asynchronous operations (such as network requests) and UI updates. By using Mutex in critical areas, we can ensure that these operations are performed in the correct order, improving the maintainability and stability of the code.

Precautions

  1. Mutex between coroutines : Mutex is mainly used for mutual exclusion between coroutines to ensure that only one coroutine can access shared resources at the same time to avoid race conditions.
  2. Avoid deadlock : When using Mutex, pay attention to avoid deadlock, that is, the coroutine is suspended before it is released after acquiring the Mutex, causing other coroutines to be unable to continue executing.
  3. Coroutine cancellation : When using Mutex, pay attention to the cancellation of the coroutine to ensure that the Mutex can be released correctly when the coroutine is canceled to avoid resource leaks.
  4. Performance overhead : Excessive use of Mutex may cause performance overhead. The code needs to be designed carefully to avoid frequent mutual exclusion operations.

Optimization tips

  1. Refined locking : Only use Mutex in critical sections that need to be protected, and avoid excessive use of global Mutex.
  2. Use tryLock : In some cases, you can use it tryLockto try to obtain the Mutex to avoid the coroutine being suspended and improve execution efficiency.

Conclusion

Through the introduction of this article, I believe that everyone has a deeper understanding of the principles and use of Mutex in Kotlin. In actual development, flexible use of Mutex, combined with the advantages of coroutines, can better handle multi-threaded scenarios and improve the robustness of the program.

at last

If you want to become an architect or want to break through the 20-30K salary range, then don't be limited to coding and business, you must be able to select and expand, and improve your programming thinking. In addition, good career planning is also very important, and learning habits are important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.

If you have no direction, here is a set of "Advanced Notes on the Eight Modules of Android" written by a senior architect at Alibaba to help you systematically organize messy, scattered, and fragmented knowledge, so that you can systematically and efficiently Master various knowledge points of Android development.
img
Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are strictly arranged according to the knowledge system.

Welcome everyone to support with one click and three links. If you need the information in the article, just scan the CSDN official certification WeChat card at the end of the article to get it for free↓↓↓ (There is also a small bonus of ChatGPT robot at the end of the article, don’t miss it)

PS: There is also a ChatGPT robot in the group, which can answer everyone’s work or technical questions.

picture

Guess you like

Origin blog.csdn.net/Androiddddd/article/details/135026189