- Binary Semaphore – This is also known as mutex lock. It can have only two values – 0 and 1. Its value is initialized to 1. It is used to implement the solution of critical section problems with multiple processes.
- Counting Semaphore – Its value can range over an unrestricted domain. It is used to control access to a resource that has multiple instances.
Semaphores give us the ability to control access to a shared resource by multiple threads. For an easy start, let’s consider the following real-life scenario:
A father sits with his three kids at home, then he pulls out an iPad…
Kid 2: I want to play with the iPad!!! Kid 1: NO!, I want to play first… Kid 3: Ipad! Ipad! Ipad! *sound of claps* Father: Ok, Kid 2, since you asked first and no one is currently using the iPad, take it, but let me know once you are done. Rest of kids, please wait patiently. Kid 2: (5 min later) I’m done father. Father: Kid 1, the iPad is available, let me know once you are done. Kid 1: (5 min later) I’m done father. Father: Kid 3, the iPad is available, let me know once you are done. Kid 3: (5 min later) I’m done father.
In the scenario above, the father is the semaphore, the iPad is the shared resource, and the kids are the threads. Note how the father makes sure that only one kid uses the iPad at a time. If we compare this to programming, only one thread has access to a shared resource at a time. In addition, note the order of use, the first who asked is the first who get (FIFO).
Without semaphore in dispatch queue-
//
// ViewController.swift
// Semaphore
//
// Created by JOYNAL ABEDIN on 3/8/22.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let queue = DispatchQueue.global()
queue.async {
let url = self.imageDownload(url: \"Nature\")
print(\"Downloaded images successfully: \(url)\")
}
queue.async {
self.saveImageDirectory()
}
}
private func imageDownload(url: String) -> String {
sleep(4)
return url
}
private func saveImageDirectory(){
sleep(2)
print(\"✅image save successfully!\")
}
}
Output:
✅image save successfully!
Downloaded images successfully: Nature
Above this output , we was see that image saved before it downloaded. if this sceneario happend any time then app will be crash.
But after using semaphore this scenerio will be -
//
// ViewController.swift
// Semaphore
//
// Created by JOYNAL ABEDIN on 3/8/22.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let semaphore = DispatchSemaphore(value: 1)
let queue = DispatchQueue.global()
queue.async {
print(\"✅Before execution wait first block\")
semaphore.wait() // value 0
print(\"✅After execution wait first block\")
let url = self.imageDownload(url: \"Nature\")
print(\"Downloaded images successfully: \(url)\")
semaphore.signal() // value 1
}
queue.async {
print(\"✅Before execution wait second block\")
semaphore.wait() // value 0
print(\"✅After execution wait second block\")
self.saveImageDirectory()
semaphore.signal() // value 1
}
}
private func imageDownload(url: String) -> String {
sleep(4)
return url
}
private func saveImageDirectory(){
sleep(2)
print(\"✅image save successfully!\")
}
}
Output:
✅Before execution wait first block ✅After execution wait first block ✅Before execution wait second block Downloaded images successfully: Nature ✅After execution wait second block ✅image save successfully!Tip: a shared resource can represent a variable, or a job such as downloading an image from a URL, reading from a database, etc.
What if the father just gave the iPad to the kids? A fight would build up to the point of a probably broken iPad 😖. If we compare this to programming, multiple threads try to access the same resource at the same time and nothing is preventing it. Such behavior could lead to race conditions, crashes, and obviously, our code won’t be thread-safe.
You increment a semaphore count by calling thesignal() method, and decrement a semaphore count by calling wait() or one of its variants that specifies a timeout.
So, when should we call wait() and signal() functions?
- Call wait() each time before using the shared resource. We are basically asking the semaphore if the shared resource is available or not. If not, we will wait.
- Call signal() each time after using the shared resource. We are basically signaling the semaphore that we are done interacting with the shared resource.
Calling wait() will do the following:
- Decrement semaphore counter by 1.
- If the resulting value is less than zero, the thread is frozen.
- If the resulting value is equal to or bigger than zero, the code will get executed without waiting.
Calling signal() will do the following:
- Increment semaphore counter by 1.
- If the previous value was less than zero, this function wakes the oldest thread currently waiting in the thread queue.
- If the previous value is equal to or bigger than zero, it means the thread queue is empty, aka, no one is waiting.
Advantages of Semaphores
Some of the advantages of semaphores are as follows −- Semaphores allow only one process into the critical section. They follow the mutual exclusion principle strictly and are much more efficient than some other methods of synchronization.
- There is no resource wastage because of busy waiting in semaphores as processor time is not wasted unnecessarily to check if a condition is fulfilled to allow a process to access the critical section.
- Semaphores are implemented in the machine independent code of the microkernel. So they are machine independent.
Disadvantages of Semaphores
Some of the disadvantages of semaphores are as follows −- Semaphores are complicated so the wait and signal operations must be implemented in the correct order to prevent deadlocks.
- Semaphores are impractical for last scale use as their use leads to loss of modularity. This happens because the wait and signal operations prevent the creation of a structured layout for the system.
- Semaphores may lead to a priority inversion where low priority processes may access the critical section first and high priority processes later.
Tips
- ❌NEVER run semaphore wait() function on the main thread as it will freeze your app.
- Wait() function allows us to specify a timeout. Once timeout is reached, the wait will finish regardless of semaphore count value.
0 Comments
Leave a Comment