It used to be thought that the initial value of the semaphore was the maximum number of concurrent threads and could not be changed, but it is not.
Common development uses GCD DispatchSemaphore to address thread safety issues: When multiple threads access the same resource, it is easy to cause data confusion and data security issues.
A classic example of a multithreaded safety hazard – selling tickets
- Before using a semaphore:
var ticketTotal = 15
let group: DispatchGroup = DispatchGroup(a)// Sell tickets
func __saleTicket(_ saleCount: Int) {
DispatchQueue.global().async(group: group, qos: .default, flags: []) {
for _ in 0..<saleCount {
// Add a delay to make it possible for multiple threads to do this at the same time
sleep(1)
/ / sell one
self.ticketTotal -= 1}}}// Start selling tickets
func startSaleTicket(a) {
print("\(Date()) In the beginning there were\(ticketTotal)Zhang. "")
print("\(Date()) Five tickets for the first time.")
__saleTicket(5)
print("\(Date()) Five tickets for the second time.")
__saleTicket(5)
print("\(Date()) Five tickets for the third time.")
__saleTicket(5)
group.notify(queue: .main) {
print("\(Date()(Theoretically all sold out, practically all left\ [self.ticketTotal)Zhang. "")}}Copy the code
Print result:
Obviously the result is wrong, 15 sold 15 times but there are 4 left, this is the multi-threaded operation caused by data confusion.
- Use a semaphore to add to the operation of selling tickets 🔐 :
func __saleTicket(_ saleCount: Int) {
DispatchQueue.global().async(group: group, qos: .default, flags: []) {
for _ in 0..<saleCount {
// Add a delay to make it possible for multiple threads to do this at the same time
sleep(1)
/ / sell one
self.semaphore.wait() / / add 🔐
self.ticketTotal -= 1
self.semaphore.signal() // 解🔐}}}Copy the code
Print result:
The result is correct, multithreaded operations using semaphore can achieve thread synchronization to ensure data security.
Misunderstandings about semaphores
It was previously thought that the initial value of a semaphore was the maximum number of concurrent threads and was not changeable until we saw another article about a semaphore usage:
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
func semaphoreTest(a) {
DispatchQueue.global().async {
DispatchQueue.main.async {
// Get some information from the main queue.// Send a signal
self.semaphore.signal()
}
// Start to wait
self.semaphore.wait()
// The wait is over and the thread continues}}Copy the code
The maximum number of concurrent threads is 0. The maximum number of concurrent threads is 0. Isn’t there a thread that can work? It should always block the child thread, so what’s the point of this usage?
A new understanding of semaphore
Semaphore.wait () is known to subtract 1, but only if the semaphore is greater than zero:
- If it is greater than zero, the thread can continue to run down and then immediately after
semaphore.wait()
After this sentence, you will really reduce the semaphore by 1; - If it is equal to zero, it will put the thread to sleep and join aThe queue of threads waiting for this signalWhen the semaphore is greater than zero, it willWake up the thread at the top of the waiting queue, continue the thread code and subtract 1 from the semaphore. This ensures that the semaphore is greater than zero, so there is no case where the semaphore is less than zero (Unless you set it to negative when you initialize it, which would crash the program when you use it).
Semaphore.signal () adds one to the semaphore, and later tests show that semaphore.signal() allows you to add any semaphore you want, so the initial semaphore is not immutable, but mutable.
- Verified 🌰 :
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) / / 0
func semaphoreTest(a) {
semaphore.signal() // 0 + 1 = 1
semaphore.signal() // 1 + 1 = 2
semaphore.signal() // 2 + 1 = 3
semaphore.wait() // 3-1 = 2
print("\(Date()) \(Thread.current) hello_1")
semaphore.wait() // 2-1 = 1
print("\(Date()) \(Thread.current) hello_2")
semaphore.wait() // 1-1 = 0
print("\(Date()) \(Thread.current) hello_2")
// Delay another thread by 3 seconds to add the semaphore asynchronously
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
print("\(Date()) \(Thread.current)Semaphore + 1")
let result = self.semaphore.signal() // 0 + 1 = 1
print("\(Date()) \(Thread.current) result: \(result)");
/* * PS: signal() * This function returns non-zero if a thread is woken. Otherwise, zero is returned. If the thread is awakened, this function returns nonzero. Otherwise, return zero. Signal () returns 0, indicating that no thread has been awakened, but the semaphore does have +1. * /
}
semaphore.wait() // If the value is 0, the current thread is "stuck"
print("\(Date()) \(Thread.current) hello_4") // 1-1 = 0
}
Copy the code
Print result:
Another thread
Demonstrates that semaphores are self-maintainable, just “invisible” (no API access).
- Pseudocode explains the usage described earlier:
// Initialize the semaphore to 0, assuming semaphoseries is a number representing the semaphore
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) // semaphoreCount = 0
func semaphoreTest(a) {
DispatchQueue.global().async {
// [1] Start task 1
DispatchQueue.main.async {
//【4】 Start task 2.[5] When task 2 is over, the semaphore is increased by 1 and a signal is sent to wake up the thread waiting first
self.semaphore.signal() // semaphoreCount + 1 = 1
}
// [2] Task 1 needs to wait for task 2 to complete before continuing to determine whether there is a semaphore
self.semaphore.wait() [3] Semaphoseries == 0; semaphoseries == 0;
// [6] If you can get here, it means that the semaphore is at least 1, which wakes up the thread. At the same time, the semaphore is reduced by 1
// semaphoreCount - 1 = 0
// If one is equal to zero, other threads waiting for the semaphore will continue to wait, and this thread will continue to execute.
// [7] Continue task 1}}Copy the code
conclusion
- It is true that the initial value of the GCD semaphore is the maximum number of concurrent threads, but it is not unchangeablethrough
semaphore.signal()
Any addIt’s like there’s a hiddensemaphoreCount
To control how many threads can work simultaneously; - this
semaphoreCount
You have to have at least one to execute the code, as long as it’s zero,semaphore.wait()
It will let the thread sleep untilsemaphoreCount
Greater than 0 to wake up; - Because there’s no API to get this
semaphoreCount
, so be sure to pay attention to:How many timessemaphore.wait()
I remember how many times I have to use itsemaphore.signal()
, make sure to use pairing, otherwise the thread will sleep forever.
With this in mind, the GCD semaphore can be added to 🔐 as well as make the current thread wait for another thread, which means you can control the execution time of the thread
GCD has some other functions that need to maintain the number of pairs themselves
These functions also have no corresponding API fetching times and need to be maintained by themselves:
- Queue group:
group.enter()
和group.leave()
- Timer:
timer.resume()
和timer.suspend()
- PS: Don’t pause
suspend
Execute in statecancel()
, otherwise, it willcollapseSo remember beforecancel()
Before making sure the timer is runningresume
Condition.
- PS: Don’t pause