[读书笔记]iOS与OS X多线程和内存管理 [GCD部分]

时间:2022-11-22 23:38:54
3.2 GCD的API

苹果对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
“Dispatch Queue”是执行处理的等待队列。通过dispatch_async函数等API,在Block语法中记述想执行的处理并追加到Dispatch Queue中,Dispatch Queue按照追加的顺序,执行处理。
Dispatch Queue分为两种:

种类
说明
Serial Dispatch Queue
等待现在执行中处理结束(顺序执行)
Concurrent Dispatch Queue
不等待现在执行中处理结束(并发执行)

生成Dispatch Queue的方式有两种
          第一通过GCD的API生成Dispatch Queue;
使用方法dispatch_queue_create函数可以生成Dispatch Queue。
dispatch_queue_t myDQ=dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>);
第一个参数是 Dispatch Queue名称,推荐使用应用程序ID这种逆序全程域名。第二个参数是指定生成Dispatch Queue的类型,NULL代表Serial Dispatch Queue, DISPATCH_QUEUE_CONCURRENT代表生成的是Concurrent Dispatch Queue。使用示例:

dispatch_queue_t myQu=dispatch_queue_create("log", DISPATCH_QUEUE_CONCURRENT);

多个Serial Dispatch Queue之间是并行执行的,只在为了避免多个线程更新相同资源导致数据竞争时使用 Serial Dispatch Queue。
如果是iOS6或之前的系统,生成的Dispatch Queue在使用结束后要手动释放:dispatch_release(myQu);
同样也存在dispatch_retain函数,即Dispatch Queue也想OC的引用计数式内存管理一样,可以释放和持有。
在iOS7或之后的系统中,ARC环境下系统会自动释放。

    dispatch_queue_t myQU=dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
   
dispatch_async(myQU, ^{
       
NSLog(@"test");
    });
    dispatch_release(myQU);
像这样追加Block到Dispatch Queue后直接释放Dispatch Queue是没问题的。因为在dispatch_async函数中把Block追加到Dispatch Queue后,该Dispatch Queue就会被Block所持有。在之后介绍的GCD的API中,当使用名称中含有”create”的API生成对象时,在不需要其生成的对象释放(iOS7和之后系统ARC环境下不需要)。

          第二种方法是获取系统标准提供的Dispatch Queue。

名称
种类
说明
Main Dispatch Queue
Serial Dispatch Queue
主线程执行
Global Dispatch Queue
Concurrent Dispatch Queue
有四个等级:High、Default、Low、Background。分别对应高、默认、低、后台

获取方法
名称
获取方法
Main Dispatch Queue
dispatch_queue_t mainQU=dispatch_get_main_queue();
Global Dispatch Queue
dispatch_queue_t globalQU=dispatch_get_global_queue(<#dispatch_queue_priority_t priority#>, 0);
第一个参数代表等级,分别有:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-
2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
第二个参数是保留参数,直接写0吧。
通过系统获得的这两个Dispatch Queue可以不用考虑内存管理问题,比较轻松。
使用实例:

dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       
/**
         * 
并行执行的处理
         */

       
//需要在主线程展处理的,例如界面更新
       
dispatch_async(dispatch_get_main_queue(), ^{
           
/**
             * 
主线程处理
             */

        });
    });

3.2.4 dispatch_set_target_queue

我们自己生成的Dispatch Queue都是使用与Global Dispatch Queue默认优先级相同优先级的线程,变更Dispatch Queue的优先级要使用dispatch_set_target_queue函数。

    dispatch_queue_t mySerialQueue=dispatch_queue_create("test_serial", NULL);
   
dispatch_queue_t globalBackground=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_set_target_queue(mySerialQueue, globalBackground);
第一个参数是需要更改优先级的Dispatch Queue,将第二个的优先级赋值给第一个,也就是说第二个参数就是目标Dispatch Queue,我们需要使用它的优先级。
dispatch_set_target_queue 不仅可以改变 Dispatch Queue的优先级,还可以作为Dispatch Queue的执行阶层,如果将多个Serial Dispatch Queue使用函数指定目标为同一个Serial Dispatch Queue。那么原本应该并行执行的Serial Dispatch Queue只能同时执行一个处理。

3.2.5 dispatch_after

dispatch_after在指定的时间之后将Block追加到Dispatch Queue。

函数:
void  dispatch_after(dispatch_time_t when, dispatch_queue_t queue,dispatch_block_t block);
使用:
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@“after");
    });

dispatch_time_t  dispatch_time(dispatch_time_t when, int64_t delta);
dispatch_time_t  dispatch_walltime(const struct timespec *when, int64_t delta);
timespec结构体可以通过NSDate类型的数据生成。
dispatch_time通常用于计算相对时间,dispatch_walltime函数用来计算绝对时间。

3.2.6 Dispatch Group

如果想等待追加到Dispatch Queue中的多个处理结束后再执行某些结束处理,可以选择使用Serial Dispatch Queue,也可以使用Dispatch Group 配合 Concurrent Dispatch Queue的方式。
使用实例:

    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group=dispatch_group_create();
    dispatch_group_async(group, globalQueue, ^{NSLog(@"0");});
   
dispatch_group_async(group, globalQueue, ^{NSLog(@"1");});
   
dispatch_group_async(group, globalQueue, ^{NSLog(@"2");});
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
在追加到Dispatch Queue中的全部处理结束后,dispatch_group_notify函数会将执行的Block追加到Dispatch Queue中。另外,Dispatch Group中也可以使用 dispatch_group_wait函数等待全部处理执行结束。dispatch_group_wait函数的第二个参数是等待的时间(超时),下面源码中选择DISPATCH_TIME_FOREVER表示一直等待,直到group中的操作全部结束。假设你给dispatch_group_wait函数设定等待时间为1秒,一秒后返回0时表示成功,返回其他数值表示group的中的某个处理还在进行中。

    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group=dispatch_group_create();
    dispatch_group_async(group, globalQueue, ^{NSLog(@“0");});
    dispatch_group_async(group, globalQueue, ^{NSLog(@"1");});
    dispatch_group_async(group, globalQueue, ^{NSLog(@"2");});
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

3.2.7 dispatch_barrier_async

本节参考苹果官网有改动。
       dispatch_barrier_async函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用。一个比较合适的场景是对数据库的操作,不应在同一时刻对数据库进行多个写操作,但是多个读操作是允许的,此时的写操作的实现是比较复杂的。 dispatch_barrier_async函数的作用就是会等待追加到 Concurrent Dispatch Queue处理全部结束后再将指定的处理追加到该 Concurrent Dispatch Queue。并不等待Block被调用函数就返回,等 dispatch_barrier_async函数追加到 Concurrent Dispatch Queue处理执行结束后, dispatch_barrier_async 追加到 Concurrent Dispatch Queue的处理又开始执行。比较适合数据库和文件访问。

    dispatch_queue_t concurrentQU=dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQU, ^{ NSLog(@"write data0");});
    dispatch_async(concurrentQU, ^{ NSLog(@"write data1");});
    dispatch_async(concurrentQU, ^{ NSLog(@"write data2"); });        
    dispatch_async(concurrentQU, ^{ NSLog(@"write data3");});
    dispatch_barrier_async(concurrentQU, ^{
        NSLog(@"read...");
    });
    dispatch_async(concurrentQU, ^{ NSLog(@"write data4); });
    dispatch_async(concurrentQU, ^{ NSLog(@"write data5); });

执行结果是:
write data1
write data0
write data2
write data3
read...
write data4
write data5
官网说明:

Submits a barrier block for asynchronous execution and returns immediately.

Declaration

Objective-C

void dispatch_barrier_async ( dispatch_queue_t queue, dispatch_block_t block );

Parameters
         queue
         
The dispatch queue on which to execute the barrier block. The queue is retained by the     system until the block has run to completion. This parameter cannot be NULL.
block

The barrier block to submit to the target dispatch queue. This block is copied and retained until it finishes executing, at which point it is released. This parameter cannot be NULL.

Discussion

Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.


与此相对,存在函数 dispatch_barrier_sync,此函数与dispatch_barrier_async函数的区别是它会等待它所提交的Block执行完毕才返回,也就是说会阻塞线程,等Block执行完毕后函数后面的任务才会被提交到进程。个人理解是,dispatch_barrier_async提交任务后函数之后的任务也被提交,但并未执行,等函数提交的任务执行后,在其之后提交的任务才执行,而dispatch_barrier_sync函数提交任务后知道任务执行结束才接着往下执行。dispatch_barrier_async使用率较高。
官网说明如下:

Submits a barrier block object for execution and waits until that block completes.

Declaration

Objective-C

void dispatch_barrier_sync ( dispatch_queue_t queue, dispatch_block_t block );

Parameters

queue

The dispatch queue on which to execute the barrier block. This parameter cannot be NULL.

block

The barrier block to be executed. This parameter cannot be NULL.

Discussion

Submits a barrier block to a dispatch queue for synchronous execution. Unlike dispatch_barrier_async, this function does not return until the barrier block has finished. Calling this function and targeting the current queue results in deadlock.

When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the queue executes the barrier block by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.

Unlike with dispatch_barrier_async, no retain is performed on the target queue. Because calls to this function are synchronous, it "borrows" the reference of the caller. Moreover, no Block_copy is performed on the block.

As an optimization, this function invokes the barrier block on the current thread when possible.




3.2.8 dispatch_sync

dispatch_async意味着“非同步”(asynchronous),与之相对应的就是同步(synchronous),即dispatch_sync函数,将指定的Block“同步“追加到指定的Dispatch Queue中,在追加Block结束之前,函数会一直等待。可以说 dispatch_sync是简易版的 dispatch_group_wait函数。dispatch_sync容易引起死锁,下面的代码在主线程中执行就会死锁。

    dispatch_queue_t queue=dispatch_get_main_queue();
    dispatch_sync(queue, ^{  NSLog(@"hello"); });
该源码在主线程中执行指定的Block,并等待其执行结束,然而主线程正在执行源码,已经被阻塞,无法继续执行任何源码。

3.2.9 dispach_apply

dispatch_apply函数和Dispatch Group关联的API。该函数按指定的次数将指定的Block追加到某个Dispatch Queue中,并等待处理全部结束。

    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
dispatch_apply(5, queue, ^(size_t index) {
       
NSLog(@"%zu",index);
    });
    NSLog(@"done");
运行结果:1 0 3 2 4 done
第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数是追加的处理。第三个参数是有参数的Block,为了区分Block使用,例如要对NSArray类对象的所有元素执行处理时,不需要for循环。

3.2.10 dispatch_suspend/dispatch_resume

dispatch_suspand挂起指定的Dispatch Queue,dispatch_resume恢复指定的Dispatch Queue。

3.2.11 Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中计数类型信号。

    dispatch_semaphore_t semaphore=dispatch_semaphore_create(1);//参数表示计数的初始值
   
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//此函数等待计数值等于或大于1,对该技术进行减法并返回,第二个参数是等待时间,若在等待时间内semaphore的计数满足要求,则返回0,反之返回非0
    dispatch_semaphore_signal(semaphore);// 该函数使 semaphore 1
示例:

    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
//生成计数信号(信号量),初始值为1,保证同时访问数组的线程只有一个。
   
dispatch_semaphore_t sema=dispatch_semaphore_create(1);
   
NSMutableArray*array=[[NSMutableArray alloc]init];
   
for (int i=0; i<10000; i++) {
       
dispatch_async(queue, ^{
          
//等待计数信号直到大于等于1
           
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
           
/**
             * 
由于第一个访问的线程访问时,信号量等于1,满足条件,wait函数返回,信号量为0,其余访问的线程只能等待,能访问array的线程始终只有一个
             */

            [array
addObject:[NSNumber numberWithInt:i]];
           
//排他控制结束,信号量加1
           
dispatch_semaphore_signal(sema);
        });
    }

3.2.12 dispatch_once
dispatch_once是保证在应用程序执行中只执行一次的API。

    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
       
/**
         * 
初始化代码
         */
    });
dispatch_once可保证此源码在多线程环境下的安全。

3.2.13 Dispatch I/O

Dispatch I/O和Dispatch Data 能实现读取文件时将文件分成合适大小使用Global Dispatch Queue将一个文件按某个大小读取。如:

    dispatch_async(queue, ^{/*读取0~8191字节*/});
   
dispatch_async(queue, ^{/*读取8192~16383字节*/});
    dispatch_async(queue, ^{/* 读取 16383~24575 字节 */ });

苹果官方使用示例:
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
});

*out_fd = fdpair[1];

dispatch_io_set_low_water(pipe_channel, SIZE_MAX);

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
        if (err == 0)
        {
                size_t len = dispatch_data_get_size(pipedata);
                if (len > 0)
                {
                        const char *bytes = NULL;
                        char *encoded;
                        dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                        encoded = asl_core_encode_buffer(bytes, len);
                        asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
                        free(encoded);
                        _asl_send_message(NULL, merged_msg, -1, NULL);
                        asl_msg_release(merged_msg);
                        dispatch_release(md);
                }
}

if (done)
{
        dispatch_semaphore_signal(sem);
        dispatch_release(pipe_channel);
        dispatch_release(pipe_q);
}
});
dispatch_io_create函数创建了一个dispatch I/O。它指定了一个会在发生错误的时候被执行的block,以及执行block的队列。dispatch_io_set_low_water函数指定了每次读取的大小(数据会按这个分块)。dispatch_io_read函数使用Global Dispatch Queue开始并列读取。每当各个分割的文件块读取结束时,将含有文件块数据的Dispatch Data传递给dispatch_io_read函数指定的读取结束时回调的Block,这个Block处理传递过来的Dispatch Data。

3.3 GCD实现 略