iOS - about GCD semaphores

Posted by Cazrin on Thu, 02 Apr 2020 05:24:51 +0200

Talk about it casually

In fact, GCD has been contacted by all of us, and we are not explaining what GCD is. Why do we suddenly want to talk about the semaphore problem? In recent interviews, when I asked the interviewers how to deal with a series of operations after multiple requests are completed, some said to create a temporary variable to do the addition. In fact, this can also be regarded as the basic logic of semaphore, some said to use threads to do the delay operation, and how to Delay, how to operate is not clear, a few will mention GCD semaphore, but may not say how to operate, through the increase and decrease of semaphore, carry out the network concurrent request, and finally do the final processing after the completion of the network request; in fact, when we are doing it, we can basically find it through online search;

The application scenario of GCD semaphore is generally to control the maximum concurrent amount, control the synchronous access of resources, such as data access, network synchronous loading, etc.

Requirement 1: perform the next step after multiple network requests are completed (unordered)

Let's see how to execute without GCD thread group or semaphore

- (void)dispatchSyncSignal{
    NSString *urlString = @"http://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    for (int i=0; i<5; i++) {
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"Request callback %d---%d",i,i);
            
        }];
        
        [task resume];
    }
    NSLog(@"end");
}

Post run spooling output:

    

From the above two printing results, it can be seen that end is executed first, because of the asynchronous callback of network request, and then the callback order of each network request is out of order. The following operations are carried out according to the requirements;

Use GCD's thread group dispatch group

- (void)dispatchSyncSignal1{
    //Create thread group
    dispatch_group_t group = dispatch_group_create();
    NSString *urlString = @"http://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    for (int i=0; i<5; i++) {
        dispatch_group_enter(group);
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"Request callback %d---%d",i,i);
            dispatch_group_leave(group);
        }];
        
        [task resume];
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}

Post run spooling output:

   

From the results of two printouts, it can be seen that end is output after all network requests, which meets our needs. Then we will talk about the relevant methods used:

Create a dispatch group;

dispatch_group_enter(); call before each network request;

dispatch_group_leave(); after each network request, it is called.

Dispatch [group] enter(); and dispatch [group] leave(); must be used together, and there must be several leaves for several times of entry;

Then when all the blocks of dispatch group enter(); are dispatch group leave(); the block of dispatch group notify will be executed.

Semaphore? Using GCD

- (void)dispatchSyncSignal2{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    NSString *urlString = @"http://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
   __block NSInteger count = 0;
    
    for (int i=0; i<5; i++) {
        
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"Request callback %d---%d",i,i);
            count = count + 1;
            if (count == 5) {
                dispatch_semaphore_signal(semaphore);
                count = 0;
            }
        }];
        
        [task resume];
    }
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}

Post run spooling output:

  

From the results of two printouts, it can be seen that end is also output after all network requests, which also meets our needs. Then we will talk about the relevant methods used:

If the count of the dispatch [semaphore semaphore semaphore is 0, wait. Dispatch ﹐ semaphore ﹐ signal (semaphore) is the count + 1, dispatch ﹐ semaphore ﹐ wait (SEMA, dispatch ﹐ time ﹐ forever) is the set waiting time, here the set waiting time is always waiting. Generally speaking, for the above code, start as 0, wait, wait for 5 network requests to be completed, count + 1, then count-1 to return, the program continues to execute, count variable, record the number of network callbacks, call back 5 times, and then send the semaphore, so that the subsequent program continues to run.

Requirement 2: perform the next step (in order) after multiple network requests are completed

If multiple network requests are executed in order according to the method of requirement 1, and then the next operation is carried out, then how to execute? Of course, the semaphore can also be used to operate:

- (void)dispatchSignal3{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSString *urlString = @"http://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    for (int i=0; i<5; i++) {
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"Request callback %d---%d",i,i);
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}

Post run spooling output:

  

From the results of two printouts, we can see that all network requests are executed in order, and end is output after all requests are completed, which meets our needs.

For each traverse in the method, the following dispatch [semaphore] wait() is executed. At this time, the thread will wait and block the current thread until the dispatch [semaphore] signal (SEM) call is made, and then the next traverse will be continued.

Let's talk about it

Semaphores are used for multithread synchronization. Unlike locks, semaphores are not necessarily used to lock A resource, but are process concepts. For example, there are two threads A and B. thread B needs to wait for thread A to complete A task before performing the following steps. This task It is not necessarily to lock A certain resource, but to do some calculation or data processing;

Next, make an XKGradeRoom based on the producers and consumers of NSObject In the workshop, look at the usage of semaphore, we can set a maximum output for the production room (here two). After all, the production room cannot be unlimited. Through the relationship between producers and consumers, we can reasonably control the output of the production room. When the output reaches the maximum output, we will stop production and wait for consumers to consume.

XKGradeRoom.h

#import <Foundation/Foundation.h>

/**
 Production and consumption workshop
 */
@interface XKGradeRoom : NSObject

/**
 production
 */
- (void)xk_produce:(NSString *)sp;

/**
 consumption

 @return NSString
 */
- (NSString*)xk_comsumer;
@end

XKGradeRoom.m

#import "XKGradeRoom.h"
@interface XKGradeRoom()
/**
 Warehouse
 */
@property(strong,nonatomic) NSMutableArray* baseArray;

/**
 Mutex access semaphore for accessing warehouse (critical area)
 */
@property(strong,nonatomic) dispatch_semaphore_t criticalSemaphore;

/**
 Consumer - mark of whether to consume warehouse object
 */
@property(strong,nonatomic) dispatch_semaphore_t comsumerSemaphore;

/**
 Producer - mark whether the object is produced
 */
@property(strong,nonatomic) dispatch_semaphore_t productSemaphore;

/**
 Maximum load in warehouse
 */
@property(nonatomic,assign) int maxProductCount;

@end
@implementation XKGradeRoom
- (instancetype)init{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}
- (void)setup{
    _maxProductCount = 2;
    self.baseArray = [NSMutableArray array];
    self.productSemaphore = dispatch_semaphore_create(_maxProductCount);
    self.comsumerSemaphore = dispatch_semaphore_create(0);
    //Initializing mutex semaphores in critical area,Realizing mutual exclusion with semaphores,Special initial value is 1.
    //Control that only one thread object is accessing the warehouse at the same time
    self.criticalSemaphore = dispatch_semaphore_create(1);
}


/**
 production
 */
-(void)xk_produce:(NSString *)sp{
   //Get the semaphore to access the warehouse first
    long baseCount = dispatch_semaphore_wait(self.criticalSemaphore,  5 * NSEC_PER_SEC);
    if(baseCount != 0){
        NSLog(@"Warehouse is in use, producer is waiting");
    }else{
        //Then judge whether there is room for articles in the warehouse
        long maxSpaceCount = dispatch_semaphore_wait(self.productSemaphore, 5 * NSEC_PER_SEC);
        
        if(maxSpaceCount != 0){
            NSLog(@"Warehouse%d Space (s) used up, producer (s) waiting: warehouse capacity:%lu",_maxProductCount,[self.baseArray count]);
            //Release access lock of critical area after production
            dispatch_semaphore_signal(self.criticalSemaphore);
        }else{
            
            [self.baseArray addObject:sp];
             NSLog(@"New production one,The warehouse currently has:%lu",[self.baseArray count]);
            dispatch_semaphore_signal(self.criticalSemaphore);
            dispatch_semaphore_signal(self.comsumerSemaphore);
          
        }
    }
}

/**
 consumption
 
 @return NSString
 */
-(NSString*)xk_comsumer{
    NSString* e = nil;
    long baseCount = dispatch_semaphore_wait(self.criticalSemaphore, 5 * NSEC_PER_SEC);        //Get the semaphore to access the warehouse first
    if(baseCount != 0){
        NSLog(@"Someone is using the warehouse and the consumer is waiting");
    }else{
        //Then judge whether the warehouse is still available. If there are any items, take one out, otherwise t wait for
        long avableCount = dispatch_semaphore_wait(self.comsumerSemaphore, 5 * NSEC_PER_SEC);
        if(avableCount != 0){
            NSLog(@"Empty position, consumers waiting");
            //Release access lock of critical area after production
            dispatch_semaphore_signal(self.criticalSemaphore);
        }else{
            e = [self.baseArray objectAtIndex:[self.baseArray count] -1];
            [self.baseArray removeLastObject];
            NSLog(@"Consumption:%@ Warehouses also have%lu:",e,[self.baseArray count]);
            //Release access lock of critical area after production
            dispatch_semaphore_signal(self.criticalSemaphore);
            //The quantity that can be placed in the warehouse +1
            dispatch_semaphore_signal(self.productSemaphore);
        }
    }
    return e;
}
@end

Let's test this workshop

XKGradeRoom * gradeRoom = [XKGradeRoom new];
    
    //Create a myDispatchQueue,It is mainly used to prevent resource competition. One thread uses up the resource before the other can continue to use it
    dispatch_queue_t myDispatchQueue =  dispatch_queue_create("com.example.gcd,myDispatchQueue", NULL);
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue1"];
        NSLog(@"Queue1-completion of enforcement");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        NSLog(@"Queue2-completion of enforcement");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        NSLog(@"Queue3-completion of enforcement");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue4"];
        NSLog(@"Queue4-completion of enforcement");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue5"];
        NSLog(@"Queue5-completion of enforcement");
    });
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_produce:@"Queue6"];
        NSLog(@"Queue6-completion of enforcement");
    });
    
    dispatch_async(myDispatchQueue, ^{
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        [gradeRoom xk_comsumer];
        NSLog(@"Queue7-completion of enforcement");
    });

Print results:

Topics: iOS network Session