“This is the 10th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

The GCD semaphore

What is Dispatch Semaphore

A GCD Semaphore is a Dispatch Semaphore, a signal that holds counts. It’s like crossing a toll rail on a highway. Open the railing when it is possible to pass, and close the railing when it is not possible to pass. In Dispatch Semaphore, count is used to accomplish this function, and count less than 0 waits and does not pass. If the count is 0 or greater than 0, the count is subtracted by 1 and does not wait.


Dispatch Semaphore method

  • Create semaphore

Create a semaphore of type dispatch_semaphore_ and specify the size of the semaphore. If the value passed is less than 0, the semaphore will fail initialization and return NULL

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
Copy the code
  • Transmitting semaphore

Sending a signal increments the semaphore by one

dispatch_semaphore_signal(semaphore);
Copy the code
  • Waiting semaphore

This function decays the semaphore by one. If the semaphore is less than zero after decrement by one (that is, it was zero before decrement by one), then the function waits, that is, does not return (equivalent to blocking the current thread), until the semaphore it is waiting for is greater than or equal to one, then decrement the semaphore by one and returns.

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Copy the code

Usually the waiting semaphore and the sending semaphore functions come in pairs. When a task is executed concurrently, the dispatch_semaphore_WAIT function is used to wait (block) before the current task is executed. After the last task is executed, the semaphore is sent via dispatch_semaphore_signal function (increment the semaphore value by 1). The dispatch_semaphore_WAIT function determines that the semaphore value is greater than or equal to 1 and then reduces the semaphore value by 1. Then the current task can be executed. The dispatch_semaphore_signal function sends the semaphore (incremented by 1) to notify the next task…… In this way, through semaphores, tasks in concurrent queues are executed synchronously


Use the semaphore mechanism to enable asynchronous threads to complete synchronous operations

In a concurrent queue, the order in which tasks are executed by asynchronous threads is uncertain, and two tasks are executed by two threads, making it difficult to control which task finishes first and which finishes later. But sometimes there is a need for two tasks that are asynchronous but still need to be executed synchronously. This is where the GCD semaphore comes into play.

  • Asynchronous function + concurrent queue for synchronous operation

    We know that asynchronous functions + serial queues make it easier to execute tasks synchronously. However, the disadvantages of asynchronous functions and serial queues are also obvious: new (child) threads are started because of asynchronous functions, and only one child thread is started because of serial queues. This results in all tasks being executed synchronously one by one in the child thread. The possibility of concurrent execution is lost. It gets things done, but it doesn’t take full advantage of the CPU’s multi-core (multi-threading) capabilities

    Example:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_queue_t queue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
    
        dispatch_async(queue, ^{
            NSLog(@Task 1: % @ "",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@Task 2: % @ "",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@Task 3: "% @",[NSThread currentThread]);
        });
    }    
    Copy the code

    Log :(three tasks are always executed in the order of task 1, task 2, and task 3, and are always executed on the same subthread)

  • GCD semaphore to achieve asynchronous thread synchronization operations

    When asynchronous thread synchronization is implemented using semaphores, tasks are synchronized one after another, but not all tasks are executed on the same thread because they are in a concurrent queue. Task 3 in the green box in the log is executed in thread 8, while task 1 and task 2 are executed in thread 3. This is different from the asynchronous function + serial queue approach where all tasks are executed sequentially on the same new thread.

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
        NSLog(@"1. Add task 1 to the concurrent queue");
        dispatch_async(dispatch_get_global_queue(0.0), ^{
        
            NSLog(@Task 1: % @ "",[NSThread currentThread]);
            
            NSLog(@"3. Add 1 to the semaphore for task 1");
            dispatch_semaphore_signal(sem);
        });
    
        NSLog(@"2. Task 1 decrement the semaphore by 1");
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        
        NSLog(@4. Add task 2 to the concurrent queue);
        dispatch_async(dispatch_get_global_queue(0.0), ^{
           
            NSLog(@Task 2: % @ "",[NSThread currentThread]);
            
            NSLog(@"6. Task 2 add semaphore 1");
            dispatch_semaphore_signal(sem);
        });
        
        NSLog(@"5. Task 2 decrement semaphore by 1");
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        
        NSLog(@"7. Add task 3 to the concurrent queue");
        dispatch_async(dispatch_get_global_queue(0.0), ^{
            NSLog(@Task 3: "% @",[NSThread currentThread]);
            
        });
    }
    Copy the code

    Log :(click the log of the event 3 times to remove the smooth log)

    Log :(log of single click events, including execution order)


Use of Dispatch Semaphore

The premise of using semaphores is to figure out which thread needs to be blocked and which thread needs to continue executing, and then use the semaphores

  • Keep thread synchronization and convert asynchronous execution tasks to synchronous execution tasks

    Example:

    NSLog(@"currentThread---%@",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // Create a Semaphore and initialize the total number of signals
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    
    // Execute asynchronously
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<3; i++) {
            NSLog(@"Execute asynchronously ----%@",[NSThread currentThread]);
        }
        number = 100;
    
        // Send a signal to increase the total number of signals by 1
        dispatch_semaphore_signal(semaphore);
    });
    
    NSLog(@"Asynchronous execution added concurrent queue");
    
    // Total semaphore decreased by 1
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"Execute,number = %d",number);
    Copy the code

    The log:

    Log is used to sort out the execution order: Semaphore dispatch_semaphore_wait (dispatch_semaphore_wait (dispatch_semaphore_wait)); * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  • Keep the thread safe and lock the thread

    Example:

    - (void)viewDidLoad{
        semaphore = dispatch_semaphore_create(1);
    
        self.ticketSurplusCount = 5;
        
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_CONCURRENT);
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(queue, ^{
            [weakSelf saleTicketSafe];
        });    
        dispatch_async(queue, ^{
            [weakSelf saleTicketSafe];
        });
    }
    
    - (void)saleTicket{
        while (1) {
            // lock
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            if (self.ticketSurplusCount > 0) {
                self.ticketSurplusCount--;
                NSLog(@"% @", [NSString stringWithFormat:@"Remaining tickets: %ld Conductor: %@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
                [NSThread sleepForTimeInterval:0.2];
            } else {           
                NSLog(@"Sold out.");
                // Unlock
                dispatch_semaphore_signal(semaphore);
                break;
            }
            // Unlockdispatch_semaphore_signal(semaphore); }}Copy the code

    log: