iOS uses GCD semaphore to control concurrent network requests

iOS uses GCD semaphore to control concurrent network requests

lead

Anyone who knows the computer will know the function of the semaphore. When multiple threads want to access the same resource, a semaphore is often set. When the semaphore is greater than 0, a new thread can operate the resource. The semaphore is -1, and the semaphore is +1 after the operation. When the semaphore is equal to 0, it must wait, so by controlling the semaphore, we can control the number of concurrency that can be performed at the same time.

In the development of network requests, there are often two situations. One is that I need to request a variety of data at the same time in one interface, such as list data, advertising data, etc., and then refresh the interface together after all requests are made. The other is that my requests must meet a certain order, for example, personal information must be requested first, and then relevant content must be requested based on personal information. These requirements can achieve concurrency control and dependent operations for ordinary operations, but for network requests that require time, the effect is often different from what is expected. In this case, a semaphore is needed for a control.

GCD semaphore

A semaphore is an integer that has an initial value when it is created. This initial value often represents the number of concurrent operations that I want to control. In operation, there are two operations on the semaphore: signal notification and wait. When the signal is notified, the semaphore will be +1, while waiting, if the semaphore is greater than 0, the semaphore will be -1, otherwise, it will wait until the semaphore is greater than 0. When will it be greater than zero? Often after a previous operation ends, we send a signal to let the semaphore +1.

After talking about the concept, let's take a look at the three semaphore operations in GCD:

  • dispatch_semaphore_create: create a semaphore (semaphore)
  • dispatch_semaphore_signal: signal notification, that is, let the semaphore +1
  • dispatch_semaphore_wait: Wait until the semaphore is greater than 0, then operate, and set the semaphore to -1

When using it, a semaphore is often created, and then multiple operations are performed. Each operation waits for the semaphore to be greater than 0 before operating, and the signal is set to -1. After the operation, the semaphore is +1, similar to the following process:

dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次循环操作) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 操作
        dispatch_semaphore_signal(sema);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

The above code indicates that I want to operate 100 times, but the control allows concurrent concurrent operations only up to 5 times. When the concurrency amount reaches 5, the semaphore will be reduced to 0. At this time, the wait operation will work, and DISPATCH_TIME_FOREVER will wait forever. , wait until the semaphore is greater than 0, that is, an operation is completed, and the semaphore is +1. At this time, the waiting can be ended, the operation can be performed, and the semaphore will be -1, so that the new task has to wait again.

Unified operation after multiple requests

Suppose we need to make multiple requests for a page at the same time, but they do not require a sequential relationship, but require that they all be requested before performing interface refresh or other operations.

We can generally use GCD's group and notify to achieve this requirement:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成,刷新界面");
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

The function of notify is to operate its own content after all other operations in the group are completed, so we will see that the content of the interface refresh will be printed only after the above three contents are printed out.

But when the above three operations are changed to real network operations, this simple approach will become invalid, why? Because the network request takes time, and the execution of the thread does not wait for the completion of the request before it is truly completed, but is only responsible for sending the request, the thread considers that its task is completed, and when all three requests are sent, The content in notify will be executed, but the time when the request result is returned is not certain, which will cause the interface to be refreshed and the request to be returned, which is invalid.

To solve this problem, we need to use the semaphore mentioned above to operate.

Before each request starts, we create a semaphore, which is initially 0. After the request operation, we set a dispatch_semaphore_wait. After the request is received, the semaphore is +1, which is dispatch_semaphore_signal. The purpose of this is to ensure that the thread waits there until the request result is not returned. In this way, the task of a thread has been waiting, it will not be counted as completion, and the content of notify will not be executed until each request. The results are returned, the thread task can end, and notify can also be executed at this time. The pseudo code is as follows:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Multiple requests are executed sequentially

Sometimes we need to perform multiple requests in sequence, such as requesting user information first, and then requesting related data according to the content of the user information. This can be written directly in order in the usual code, but here Because it involves the relationship between multiple threads, it is called thread dependency.

It is more troublesome to use GCD for thread dependencies. It is recommended to use NSOperationQueue to set dependencies between tasks more easily.

    // 1.任务一:获取用户信息
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];

    // 2.任务二:请求相关数据
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];

    // 3.设置依赖
    [operation2 addDependency:operation1];// 任务二依赖任务一

    // 4.创建队列并加入任务
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation2, operation1] waitUntilFinished:NO];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

It is possible to do this for general multi-threaded operations, and thread 2 will wait for thread 1 to complete before executing. But for network requests, the problem comes again. Similarly, network requests take time. After the thread sends a request, it considers the task to be completed, and does not wait for the operation after the return, which is meaningless.

To solve this problem, still use semaphore to control, in fact, it is a truth, the code is the same, in a task operation:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Or wait for the request to return before letting the task end. The dependencies are implemented through NSOperationQueue.

Knot

In fact, in the final analysis, the central idea is to use semaphore to control when the thread task is counted as finished. If the semaphore is not used, the task is considered to be completed after the request is sent, and the network request takes a different time, so the order will be disrupted. Therefore, using a semaphore to control the operation of a single thread, you must wait for the request to return. After the operation you want to perform is completed, the semaphore is +1. At this time, the code that has been waiting can also be executed and passed, and the task is considered completed. .

Through this method, some unexpected multi-threading problems caused by the time-consuming nature of network requests can be solved.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324961611&siteId=291194637