跳转至

GCD(Grand Central Dispatch)

纯C语言,提供了非常多强大的函数

GCD是苹果公司为多核的并发运算提出的解决方案

GCD会自动利用更多的CPU内核(比如双核,四核)

GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)

程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

多线程控制最大并发数

NSThread一般和常驻线程使用(如AFN框架)。

NSOperation可设置最大并发数(maxConcurrentOperationCount)和控制状态。

GCD使用信号量手动去控制并发数。

barrier

把异步任务分开两半,前面的先执行,后面的后执行。起到控制流程效果。

最直接的作用: 控制任务执行顺序,起到同步加锁效果。

dispatch_barrier_async 前面的任务执行完毕才会来到这里

dispatch_barrier_sync 作用相同,但是这个会堵塞线程,影响后面的任务执行,后面的任务会等待栅栏函数。

判断队列queue中有没有栅栏函数,没有的话就是普通的执行流程,一旦有栅栏函数,就会发生等待,把队列中的任务都执行完毕,等待栅栏函数执行完,然后才会走后面的任务。

执行barrier任务,必须把队列中的前面任务清空,所以前面的任务全部执行完毕才会执行barrier任务。

注:

  1. 全局并发队列不能使用barrier。全局并发队列并不只是自己使用,系统后台可能也会使用,所以不能堵塞。所以只能使用自定义的并发队列。

  2. 栅栏函数只能控制同一队列

例:AFN封装了一层session。用barrier不能控制,不是一个队列。

用处

1、加载完两张图片,合成一张图片。

2、读写锁

需要实现功能

  1. 多读单写
  2. 写写互斥
  3. 读写互斥
  4. 不能阻塞任务执行(子线程,不能阻塞主线程)

使用栅栏函数实现

通过栅栏函数(自定义并发队列)实现写任务,如果队列前面读写操作没回来,后面的写不能执行。(2和3)

setter写操作:dispatch_barrier_async主线业务逻辑可以正常执行(4)

getter读操作:dispatch_sync同步。

读写操作是同一个并发队列。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self readWriteLock];
}

- (void)readWriteLock {
    // 使用自己创建的并发队列
    self.concurrentQueue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT);
    // 使用全局队列,必定野指针崩溃
//    self.concurrentQueue = dispatch_get_global_queue(0, 0);

    // 测试代码,模拟多线程情况下的读写
    for (int i = 0; i<10; i++) {
        // 创建10个线程进行写操作
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self updateText:[NSString stringWithFormat:@"噼里啪啦--%d",i]];
        });

    }

    for (int i = 0; i<50; i++) {
        // 50个线程进行读操作
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"读 %@ %@",[self getCurrentText],[NSThread currentThread]);
        });

    }

    for (int i = 10; i<20; i++) {
        // 10个进行写操作
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self updateText:[NSString stringWithFormat:@"噼里啪啦--%d",i]];
        });
    }
}

// 写操作,栅栏函数是不允许并发的,所以"写操作"是单线程进入的,根据log可以看出来
- (void)updateText:(NSString *)text {
    // block内不需要使用weakSelf, 不会产生循环引用
    dispatch_barrier_async(self.concurrentQueue, ^{
        self.text = text;
        NSLog(@"写操作 %@ %@",text,[NSThread currentThread]);
        // 模拟耗时操作,打印log可以放发现是1个1个执行,没有并发
        sleep(1);
    });
}
// 读操作,这个是可以并发的,log在很快时间打印完
- (NSString *)getCurrentText {
    __block NSString * t = nil;
    // block内不需要使用weakSelf, 不会产生循环引用
    dispatch_sync(self.concurrentQueue, ^{
        t = self.text;
        // 模拟耗时操作,瞬间执行完,说明是多个线程并发的进入的
        sleep(1);
    });
    return t;
}

@end

group调度组

最直接的作用: 控制任务执行顺序

  • dispatch_group_create 创建组

  • dispatch_group_notify 进组任务执行完毕通知

  • dispatch_group_wait 进组任务执行等待时间

  • dispatch_group_async 进组任务

里面封装了dispatch_group_enter和dispatch_group_leave(callout执行完毕)

  • dispatch_group_enter 进组 --
  • dispatch_group_leave 出组 ++

类似信号量的加减,搭配使用。

每次dispatch_group_leave会wakeup-->dispatch_group_notify判断是否等于0,等于0就会callout唤醒notify的block任务。

semaphore信号量

作用:

  • 同步->当锁。
  • 控制GCD最大并发数。
  • 控制流程。
dispatch_semaphore_create       创建信号量
dispatch_semaphore_wait         信号量等待 -- do while死循环 等待信号量为正
dispatch_semaphore_signal       信号量释放 ++

dispatch_semaphore_create(0); 它创建了一个调度信号量(dispatch semaphore)。信号量用于同步访问资源或者协调线程间的操作。

这行代码的作用是创建一个初始计数值为 0 的信号量。

信号量的计数值表示可以并发访问的资源数量。当计数值为 0 时,任何试图减少信号量的线程(通过 dispatch_semaphore_wait 函数)都会阻塞,直到信号量的计数值增加。

这是一个同步工具,可以用来控制线程间的同步,例如,当你需要一个线程等待另一个线程完成某项工作时。在这个例子中,dispatch_semaphore_create(0) 创建的信号量可以被用来阻塞一个线程,直到某个条件被满足,此时其他线程会增加信号量的计数值(通过 dispatch_semaphore_signal 函数),解除阻塞。

这里是一个简单的例子,展示了如何使用信号量来同步线程:

// 创建一个初始计数值为 0 的信号量
dispatch_semaphore_t sema = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 异步执行的任务
    NSLog(@"Doing some work in background...");

    // 模拟一个异步任务,比如网络请求
    sleep(2); // 模拟耗时操作

    // 发送信号量,增加信号量的计数值
    dispatch_semaphore_signal(sema);
    NSLog(@"Background work done.");
});

// 等待信号量(如果计数值为0则等待,否则继续执行并将计数值减1)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

// 一旦信号量的计数值被增加,这行代码就会执行
NSLog(@"Continue with the main thread tasks...");

在上面的代码中,主线程将会在 dispatch_semaphore_wait 处阻塞,直到后台线程完成工作并通过 dispatch_semaphore_signal 发送信号量。这确保了主线程会等待后台任务完成后才继续执行。

- (void)semaphoreDemo {
    ///执行结果:
    /// 睡2秒后
    /// 执行任务2
    /// 任务2完成
    /// 执行任务1
    /// 任务1完成
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue1 = dispatch_queue_create("cooci", NULL);

    //任务1
    dispatch_async(queue, ^{
        //会等待,因为信号量开始是0
        //第二个参数 是等待时间 DISPATCH_TIME_FOREVER一直等,do while循环
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待

        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
    });

    //任务2
    //会先执行任务2 任务2执行完之后会释放信号 然后执行任务1
    dispatch_async(queue, ^{
        sleep(2);

        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });

    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(2);

        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });

    //任务4
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(2);

        NSLog(@"执行任务4");
        NSLog(@"任务4完成");
        dispatch_semaphore_signal(sem);
    });
}

控制一次最多上传或下载多少个任务:

///2最先打印,1最后打印,3和4顺序不一定。
- (void)gcdConcurrentCount
{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("haha", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        sleep(5);
        NSLog(@"1");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        sleep(3);
        NSLog(@"2");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"3");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"4");
        dispatch_semaphore_signal(semaphore);
    });
}

Dispatch_Source

  • 其 CPU 负荷非常小,尽量不占用资源

  • 联结的优势

通过条件控制block执行。

在任一线程上调用它的一个函数 dispatch_source_merge_data 后,会执行 Dispatch Source 事先定义好的句柄(可以把句柄简单理解为一个 block ) 这个过程叫 Custom event 用户事件。是 dispatch source 支持处理的一种事件

句柄是一种指向指针的指针 它指向的就是一个类或者结构,它和系统有很密切的关系 HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON (图标句柄)等。这当中还有一个通用的句柄,就是HANDLE

  • dispatch_source_create 创建源
  • dispatch_source_set_event_handler 设置源事件回调
  • dispatch_source_merge_data 源事件设置数据
  • dispatch_source_get_data 获取源事件数据
  • dispatch_resume 继续
  • dispatch_suspend 挂起

底层封装的是pthread,不受runloop影响。