GCD dispatch_barrier (dispatch_barrier)

  • The source of the dispatch_barrier
  • How to use dispatch_barrier_async/dispatch_barrier_syncThe fencetask
  • Comparison of the dispatch_barrier_async and dispatch_barrier_sync functions
  • How to use Dispatch_barrier_Async to implement multiple read and write

The source of the dispatch_barrier

As we learned in GCD(1), a concurrent queue lets you append blocks to a queue and execute them concurrently without waiting for the first one to complete. But this raises the question, if concurrent queues allow all blocks to execute at the same time, then why are they called fifOS? Isn’t it more like a heap that can join blocks for concurrent execution?

GCD provides an API for the Dispatch_barrier system. Blocks enqueued with dispatch_barrier_sync() or dispatch_barrier_async() are not executed until all previous blocks have been executed. In addition, all blocks enqueued after the Barrier block wait until the barrier block itself has completed. It’s like when we rush to work in the morning, with a subway worker holding a sign saying that only those behind him can get in after those ahead of him (in unordered parallel). Because of this fence, concurrent queues behave like queues.

The test code is here

dispatch_barrier_async

/ *! * @functiongroup Dispatch Barrier API * The dispatch barrier API is a mechanism for submitting barrier blocks to a * dispatch queue, analogous to the dispatch_async()/dispatch_sync() API. * It enables the implementation of efficient reader/writer schemes. * Barrier blocks only behave specially when submitted to queues created with * the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block * will not run until all blocks submitted to the queue earlier have completed, * and any blocks submitted to the queue after a barrier block will not run * until the barrier block has completed. * When submitted to a a global queue or to a queue not created with the * DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to * blocks submitted with the dispatch_async()/dispatch_sync() API. */

/ *! * @function dispatch_barrier_async * * @abstract * Submits a barrier block for asynchronous execution on a dispatch queue. * * @discussion * Submits a block to a dispatch queue like dispatch_async(), but marks that * block as a barrier (relevant only on DISPATCH_QUEUE_CONCURRENT queues). * * See dispatch_async() for details. * * @param queue * The target dispatch queue to which the block is submitted. * The system will hold a reference on the target queue until the block * has finished. * The result of passing NULL in this parameter is undefined. * * @param block * The block to submit to the target dispatch queue. This function performs * Block_copy() and Block_release() on behalf of callers. * The result of passing NULL in this parameter is undefined. */
Copy the code

From its official notes:

  1. dispatch_barrierIs a kind ofdispatch_async()/dispatch_sync()API, it can bebarrier blockCommit to the queue,barrier blockOnly submissions to a custom concurrent queue are truly considered as oneThe fence, which serves as a link between the preceding and the following.barrier block) it does not execute until all blocks submitted to the custom concurrent queue have completed execution, and after that, blocks added to it continue execution.
  2. whendipatch_barrier blockIs not submitted to a custom serial queue, it is associated withdispatch_async()/dispatch_sync()The function of theta is the same.

Let’s verify this with some code:

#pragma mark - dispatch_barrier_async + custom concurrent queue
/* * Features: * 1. Tasks before a barrier are executed concurrently, and tasks after a barrier are executed concurrently * 2. A new thread is started to execute the task * 3. The current thread (main thread) */ is not blocked
- (IBAction)executeBarrierAsyncCustomConcurrentQueueTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread---%@"[NSThread currentThread]);  // Prints the current thread
    NSLog(@"---begin---");
    
    NSLog(@" Append task 1");
    dispatch_async(self.concurrentQueue, ^{
        // Append task 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // Simulate time-consuming operations
            NSLog(1 - % @ "@"[NSThread currentThread]);      // Prints the current thread}});NSLog("Append task 2");
    dispatch_async(self.concurrentQueue, ^{
        // Append task 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // Simulate time-consuming operations
            NSLog(2 - % @ "@"[NSThread currentThread]);      // Prints the current thread}});NSLog(@" Append barrier_async task");
    dispatch_barrier_async(self.concurrentQueue, ^{
        // Append the barrier task
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // Simulate time-consuming operations
            NSLog(@"barrier---%@"[NSThread currentThread]);      // Prints the current thread}});NSLog("Append task 3");
    dispatch_async(self.concurrentQueue, ^{
        // Add task 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // Simulate time-consuming operations
            NSLog(3 - % @ "@"[NSThread currentThread]);      // Prints the current thread}});NSLog(@" Appended Task 4");
    dispatch_async(self.concurrentQueue, ^{
        // Add task 4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // Simulate time-consuming operations
            NSLog(4 - % @ "@"[NSThread currentThread]);      // Prints the current thread}});NSLog(@"---end---");
    NSLog(@ "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *");
}
Copy the code

The result is as follows:

2019- 04- 23 16:14:35.900776+0800The GCD dispatch_barrier [(2)18819:3551551] CurrentThread---<NSThread: 0x600002b76980>{number = 1, name = main}
2019- 04- 23 16:14:35.900984+0800The GCD dispatch_barrier [(2)18819:3551551] ---begin---
2019- 04- 23 16:14:35.901171+0800The GCD dispatch_barrier [(2)18819:3551551] Additional tasks1
2019- 04- 23 16:14:35.901355+0800The GCD dispatch_barrier [(2)18819:3551551] Additional tasks2
2019- 04- 23 16:14:35.901596+0800The GCD dispatch_barrier [(2)18819:3551551] Appended barrier_Async task2019- 04- 23 16:14:35.901789+0800The GCD dispatch_barrier [(2)18819:3551551] Additional tasks3
2019- 04- 23 16:14:35.902093+0800The GCD dispatch_barrier [(2)18819:3551551] Additional tasks4
2019- 04- 23 16:14:35.902378+0800The GCD dispatch_barrier [(2)18819:3551551] ---end---
2019- 04- 23 16:14:35.902644+0800The GCD dispatch_barrier [(2)18819:3551551] * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *2019- 04- 23 16:14:37.904283+0800The GCD dispatch_barrier [(2)18819:3551647] 1---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019- 04- 23 16:14:37.904283+0800The GCD dispatch_barrier [(2)18819:3552871] 2---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:39.909809+0800The GCD dispatch_barrier [(2)18819:3551647] 1---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019- 04- 23 16:14:39.909810+0800The GCD dispatch_barrier [(2)18819:3552871] 2---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:41.914667+0800The GCD dispatch_barrier [(2)18819:3552871] barrier---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:43.917811+0800The GCD dispatch_barrier [(2)18819:3552871] barrier---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:45.921840+0800The GCD dispatch_barrier [(2)18819:3552871] 3---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:45.921847+0800The GCD dispatch_barrier [(2)18819:3551647] 4---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019- 04- 23 16:14:47.927349+0800The GCD dispatch_barrier [(2)18819:3552871] 3---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019- 04- 23 16:14:47.927373+0800The GCD dispatch_barrier [(2)18819:3551647] 4---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
Copy the code

From this we can see:

Barrier_async tasks queued before barrier_Async tasks are executed concurrently before the Barrier task, and two new threads are created to execute the barrier_Async tasks. The barrier_async tasks are executed after tasks 1 and 2 are completeddispatch_asyncIt does not block the current main thread

dispatch_barrier_sync

We change dispatch_barrier_async from the previous test code to dispatch_barrier_sync, and the resulting log is as follows:

CurrentThread [18819:3551551] <NSThread: 0x600002b76980>{number = 1, Name = main} dispatch_barrier[18819:3551551] --begin-- 2019-04-23 16:18:04.874758+0800 GCD(2) dispatch_barrier[18819:3551551] [18819:3551551] dispatch_barrier[18819:3551551] GCD(2) dispatch_barrier[18819:3552872] 1-- <NSThread: 0x600002b12100>{number = 5, Name = (null)} 2019-04-23 16:18:06.880102+0800 GCD dispatch_barrier[18819:3562466] 0x600002b288c0>{number = 6, Name = (null)} dispatch_barrier[18819:3552872] 1-- <NSThread: 0x600002b12100>{number = 5, Name = (null)} dispatch_barrier[18819:3562466] 2-- <NSThread: 0x600002b288c0>{number = 6, Name = (null)} 2019-04-23 16:18:10.886126+0800 GCD dispatch_barrier[18819:3551551] <NSThread: 0x600002b76980>{number = 1, Name = main} dispatch_barrier[18819:3551551] barrier-- <NSThread: 0x600002b76980>{number = 1, Name = main} 2019-04-23 16:18:12.887776+0800 GCD(2) dispatch_barrier[18819:3551551 16:18:12.887907+0800 GCD(2) dispatch_barrier[18819:3551551] GCD(2) dispatch_barrier[18819:3551551] -end [18819:3551551] * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2019-04-23 16:18:14. 888428 + 0800 the GCD (2) dispatch_barrier[18819:3552872] 4---<NSThread: 0x600002b12100>{number = 5, Name = (null)} 2019-04-23 16:18:14.888461+0800 GCD dispatch_barrier[18819:3562492] 0x600002b29e00>{number = 7, Name = (null)} 2019-04-23 16:18:16.893977+0800 GCD dispatch_barrier[18819:3562492] 3-- <NSThread: 0x600002b29e00>{number = 7, Name = (null)} 2019-04-23 16:18:16.893977+0800 GCD(2) dispatch_barrier[18819:3552872] 0x600002b12100>{number = 5, name = (null)}Copy the code

As you can see from log, barrier_sync, like Barrier_Async, can act as a fence in a concurrent queue. However, there are some differences between the two methods, which will be explained in detail below

Comparison of dispatch_barrier_async and dispatch_barrier_sync

Barrier_async differs from barrier_sync only in that barrier_sync blocks subsequent tasks and must wait until the barrier_sync task is completed before subsequent asynchronous tasks are added to the concurrent queue. Barrier_async doesn’t need to wait for its own block execution to complete before adding subsequent tasks to the queue.

Dispatch_barrier_sync with deadlocks

Since tasks added with dispatch_barrier can only act as a barrier to other queues when custom concurrent queues are used, Dispatch_barrier_sync works the same as dispatch_async/ dispatch_barrier_sync, so when dispatch_barrier_sync is used in a serial queue, Deadlocks are also possible, so we should use dispatch_barrier_sync cautiously in our normal development

Dispatch_barrier_async Implements multiple read and write operations

If we maintain a dictionary or in memory is a DB file, there is more than one reader or writer to operate this Shared data, we read written single model in order to achieve this, be considered for multithreaded access to the data problems, we must first solve the reader and the reader should be concurrent read, represents the read of meaning, secondly, Readers and readers should be mutually exclusive, for example, there are readers when reading data, some can’t thread to write data, so the reader and the writer to be mutually exclusive, secondly, the writer and the writer also wants the mutex, a writer threads in writing data, so can’t write another thread to write data, otherwise it will cause program anomaly or disorder, to meet the three points, We can actually use dispatch_barrier_async to implement multiple reads and writes

  • Concurrent reader with reader
  • Reader and writer are mutually exclusive
  • Writer and writer are mutually exclusive

Let’s look at the specific realization process of multiple read and write through a picture

If there are multiple read processes running at the same time or concurrently, then the write process needs to be mutually exclusive with the read process, after the write process is complete, then the read process can be processed again, and dispatch_barrier_async just implements a multi-read single-write model for us, that is, when our readers are in the process of reading, Other readers can also read, but cannot write. If there are other reads during the write operation, these reads can only be performed after the write operation is complete.

  1. Let’s first define a classZEDMultiReadSingleWriteHandler, and then define two attributes in the class
/** Concurrent queue */
@property (nonatomic.strong) dispatch_queue_t concurrentQueue;

/** Multiread single-write data containers, which may be accessed in different threads */
@property (nonatomic.strong) NSMutableDictionary *dict;
Copy the code

The first property is a custom concurrent queue for writes using dispatch_barrier_async.

The second property is a container for data storage that may be accessed in different threads.

  1. In the class initialization method, create the concurrent queue and global container dictionary
- (instancetype)init {
    self = [super init];
    if (self) {
        self.concurrentQueue = dispatch_queue_create("zed.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
        self.dict = [NSMutableDictionary dictionary];
    }
    return self;
}
Copy the code
  1. We then define two methods for external calls to read and write
- (id)dataForKey:(NSString *)key;
- (void)setData:(id)data forKey:(NSString *)key;
Copy the code
  1. For read operations, multiple read operations can be concurrent execution, concurrent queue feature is allowed to submit the task of concurrent execution, we submit task here is obtained through a key to the dictionary object, as a result of this retrieval is need synchronization results returned immediately, so we want to call through dispatch_sync this function, Immediately return the call as A result, the synchronization to the concurrent queue, can allows multiple threads to read at the same time, for example, dataForKey this method can be called in A thread, can also be invoked in the thread B, when A thread and thread B concurrent access to the same Key, due to the nature of the concurrent queue, This ensures that they can simultaneously read the value of a key and call simultaneously, so that no matter what thread, it can immediately return the result of the call.

    - (id)dataForKey:(NSString *)key {
        __block id data;
        // Read the specified data synchronously
        dispatch_sync(self.concurrentQueue, ^{
            data = [self.dict objectForKey:key];
        });
        return data;
    }
    Copy the code
  2. Write is done by sending dispatch_barrier_async to a concurrent queue, and then we write the corresponding value through the key

    - (void)setData:(id)data forKey:(NSString *)key {
        // Asynchronous fence calls set data
        dispatch_barrier_async(self.concurrentQueue, ^{
            [self.dict setObject:data forKey:key];
        });
    }
    Copy the code

If there are any mistakes in the article, or contrary to your ideas, please let me know in the comments section, I will continue to improve, if you think this article summary is good, please move your hands, to my article and Git code sample click ✨