In programming, we sometimes face situations where we need to prevent a request from accessing a variable, file, or data structure that is currently being held by another request. Allowing concurrent requests to access and modify resources freely can cause various issues and even errors that we do not desire.
In a load-balanced environment - where you try to create multiple instances of the same API server to increase fault tolerance and handle multiple requests concurrently - it becomes even more challenging to prevent resource contentions because each instance is independent and it is difficult to establish communication between them. Not to mention the performance and complexity issues during deployment.
All of these factors require us to find a solution. That's when you need to know about a term called Mutex - what it is and when it should be applied.
Race condition occurs when multiple requests can access shared data and try to modify it simultaneously. As the operating system's thread scheduler can switch between threads at any time, you do not know the order in which the threads will attempt to access the shared data. Therefore, the result of the data modification depends on the thread scheduling algorithm, meaning both threads are "racing" to access/modify the data.
For this reason, a race condition can cause unexpected errors in programming, which is why we need to find a way to resolve this contention. At the very least, we need to determine which request has permission to operate on the data. That's where Mutex comes in.
Mutex - Mutual Exclusion, known as "mutual exclusion," is designed to prevent race conditions. Its goal is to ensure that one thread never accesses a resource that is currently held by another executing thread.
The shared resource can be a data object that two or more threads are concurrently attempting to modify. The Mutex algorithm ensures that if one process is preparing to modify a data object, no other process/thread is allowed to access or modify it until it completes and releases the object for other processes to continue.
In programming, Mutex is implemented differently depending on the programming language or tools.
Node.js does not have a clear concept of Mutex. You may have heard somewhere that Node.js only has one thread, so there should be no resource contention. It is true that Node.js only has one thread, which is used to handle JS code execution. However, most I/O tasks are performed by parallel threads or Worker Pools in libuv, so the possibility of contention still exists here.
There are some libraries that support mutex implementation in Node.js, such as async-mutex. Basically, they apply solutions to mark a thread accessing a resource for mutual exclusion, using Promises to wait until it is released (resolved)... Everything works, but the concern at this point may be performance. However, let's pause for a moment and think about the case where there is only one instance. So what about in a distributed environment with multiple instances?
A distributed environment is when you "replicate" multiple identical instances, and these instances do not necessarily have to be on the same server. You can even utilize multi-core CPUs to run each instance on them. In that case, the libraries that only support internal communication as mentioned above may not be able to resolve mutex in the usual way because they do not create an environment for communication between instances.
If your application only needs one instance, you don't need to worry about distributed scenarios. But who knows, one day your application may grow, and scaling them up to balance the load becomes inevitable. Sooner or later, it will be a problem that you need to know how to solve.
There are many methods to handle mutex in distributed scenarios, and each approach indicates its pros and cons for us to apply to each specific problem. The simplest approach is to run a centralized instance to handle resource-related business logic. This model can be represented by using a message queue or a stream to queue up requests and process them sequentially. Obviously, there will be no contention in this case anymore.
However, it is not always possible to create separate instances like that, so you need to find a more suitable solution. One of them is leveraging the speed of Redis to act as a communication channel between instances. Basically, this approach works by creating a "key," and the processing thread that grabs the key first will have the right to access the shared resource. After it finishes, it releases the key for the next processing threads.
You can implement your own Mutex algorithm or use existing libraries from npm. Examples are warlock and live-mutex. While warlock uses Redis to create a connection between instances, live-mutex implements its own linking system and provides a client for usage. Generally, these libraries can meet certain usage requirements. In an information system, reliability, fault tolerance, and recovery after incidents are always the top concerns.
Redis also has a feature called Distributed Locks where they implement an algorithm called Redlock following the idea of using multiple Redis servers that adhere to the 2 principles: "Safety" and "Liveness" for high reliability and fault tolerance in a distributed environment.
Mutex can also be manifested in resolving data conflicts in services such as the Database. We can create locks to have exclusive access to a table or a data row, and subsequent queries have to wait until the lock is released.
We can utilize locks in the database to resolve race conditions, but continuously locking resources for a long time is not efficient. In that case, you need to balance or find another reasonable solution.
In programming, especially when working with multiple data processing threads, we often need to solve resource contentions. Mutex is one of the techniques invented to address errors caused by race conditions. Understanding mutex will give you an additional skill to resolve conflicts.
Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.
Comments (0)