iOS GCD 小笔记

//
//  ViewController.m
//  GCDTest
//
//  Created by Fly on 2022/1/19.
//

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, assign) int count;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
//    Grand Central Dispatch(GCD)是 Apple 开发的一个多核编程的较新的解决方法。
//    它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。
//    它是一个在线程池模式的基础上执行的并发任务。
    
//    GCD 有哪些好处?
//    GCD 可用于多核的并行运算;
//    GCD 会自动利用更多的 CPU 内核(比如双核、四核);
//    GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
//    程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
    
    /// 队列的创建方法
    /// 可以使用dispatch_queue_create来创建,需要两个参数,第一个参数表示队列的唯一标识符号,第二参数用来区分是串行队列还是并发队列。
    /// DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列
    {
        // 串行队列的创建方法
        dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
    }
    {
        // 并发队列的创建方法
        dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
    }
    
    /// 队列的获取方法
    {
        // 一种特殊的串行队列:主队列,主队列的获取方法
        dispatch_queue_t queue = dispatch_get_main_queue();
    }
    {
        // 全局并发队列的额获取方法
        // 第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
        // 第二个参数暂时没用,用0即可
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    
    /// 任务的创建方法
    {
        // 同步执行任务创建方法
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 同步执行任务代码
        });
        
        // 异步执行任务创建方法
        dispatch_async(dispatch_get_main_queue(), ^{
            // 异步执行任务代码
        });
    }
    
    /// 基本使用
    /// 两种任务执行方式(同步执行和异步执行)
    /// 两种队列(串行队列和并发队列)
    /// 四种组合方式:
    /// 同步执行 + 并发队列
//    [self syncConcurrent];
    /// 异步执行 + 并发队列
//    [self asyncConcurrent];
    /// 同步执行 + 串行队列
//    [self syncSerial];
    /// 异步执行 + 串行队列
//    [self asyncSerial];
    /// 特殊的主队列
    /// 同步执行 + 主队列
//    [self syncMain];
//    [self syncMainOtherThread];
    /// 异步执行 + 主队列
//    [self asyncMain];

    /// GCD 线程间的通信
//    [self communication];
    
    /// GCD 栅栏方法
    /// 在栅栏之前的任务都执行完成后,才执行栅栏操作,最好在执行栅栏之后的任务。
//    [self barrier];
    
    /// GCD 延时执行方法
//    [self after];
    
    /// GCD  只执行一次方法
    /// 常用于创建单例,即使在多线程下,也能保证线程安全
//    [self once];
    
    /// GCD 快速迭代方法
    /// 按照指定的次数将指定的任务加到指定的队列中,并等待全部任务执行结束。
//    [self apply];
    
    /// GCD 队列组 dispatch_group_notify
    /// 监听group中任务的完成状态,当所有任务都执行完毕后,再执行dispatch_group_notify block 中的任务
//    [self groupNotify];
    
    /// GCD 队列组 dispatch_group_wait
    /// 阻塞当前线程,等待任务全部完成,才继续往下走
//    [self groupWait];
    
    /// GCD 队列组
    /// dispatch_group_enter 标志着一个任务加入 group,相当于 group 中未执行完毕数+1
    /// dispatch_group_leave 标志着一个任务离开 group,相当于 group 中未执行完毕数-1
//    [self groupEnterAndLeave];
    
    /// GCD 信号量 semaphore
//    [self semaphoreSync];
    /// 非线程安全,不使用semaphore
//    [self semaphoreSyncStatusNotSave];
    /// 线程安全,使用semaphore
//    [self semaphoreSyncStatusSave];
}

/// 同步执行 + 并发队列
/// 不会开启新线程,执行完一个任务,再执行下一个任务
- (void)syncConcurrent {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"syncConcurrent--begin");
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"syncConcurrent--end");
    
    /* 输出结果:
    2022-01-19 11:05:42.556554+0800 GCDTest[2214:56045] currentThread--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:42.556674+0800 GCDTest[2214:56045] syncConcurrent--begin
    2022-01-19 11:05:44.556943+0800 GCDTest[2214:56045] 执行任务1--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:46.558354+0800 GCDTest[2214:56045] 执行任务1--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:48.559643+0800 GCDTest[2214:56045] 执行任务2--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:50.560998+0800 GCDTest[2214:56045] 执行任务2--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:52.562511+0800 GCDTest[2214:56045] 执行任务3--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:54.564138+0800 GCDTest[2214:56045] 执行任务3--<_NSMainThread: 0x600001e70880>{number = 1, name = main}
    2022-01-19 11:05:54.564566+0800 GCDTest[2214:56045] syncConcurrent--end
     */
    /// 输出结果分析:
    /// 没有开启新线程,同步执行不具备开启新线程
    /// 所有任务都在begin、end之前执行,同步任务需要等待队列的任务执行结束
    /// 任务按顺序执行,同步执行需要等待队列的任务执行结束
}

/// 异步执行 + 并发队列
/// 会开启新线程,任务交替(同时)执行
- (void)asyncConcurrent {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"asyncConcurrent--begin");
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncConcurrent--end");
    
    /* 输出结果:
     2022-01-19 11:12:04.775755+0800 GCDTest[2340:60543] currentThread--<_NSMainThread: 0x600001754500>{number = 1, name = main}
     2022-01-19 11:12:04.775901+0800 GCDTest[2340:60543] asyncConcurrent--begin
     2022-01-19 11:12:04.776023+0800 GCDTest[2340:60543] asyncConcurrent--end
     2022-01-19 11:12:06.776669+0800 GCDTest[2340:60626] 执行任务1--<NSThread: 0x600001700400>{number = 7, name = (null)}
     2022-01-19 11:12:06.776665+0800 GCDTest[2340:60621] 执行任务3--<NSThread: 0x60000176bb80>{number = 8, name = (null)}
     2022-01-19 11:12:06.776664+0800 GCDTest[2340:60627] 执行任务2--<NSThread: 0x600001718d00>{number = 5, name = (null)}
     2022-01-19 11:12:08.776981+0800 GCDTest[2340:60626] 执行任务1--<NSThread: 0x600001700400>{number = 7, name = (null)}
     2022-01-19 11:12:08.776981+0800 GCDTest[2340:60621] 执行任务3--<NSThread: 0x60000176bb80>{number = 8, name = (null)}
     2022-01-19 11:12:08.776981+0800 GCDTest[2340:60627] 执行任务2--<NSThread: 0x600001718d00>{number = 5, name = (null)}
     */
    /// 输出结果分析:
    /// 在asyncConcurrent--end之后执行,当前线程没有等待,直接开启了新线程。
    /// 开启了三个新线程:<NSThread: 0x600001700400><NSThread: 0x60000176bb80><NSThread: 0x600001718d00>
    /// 任务交替同时执行
}

/// 同步执行 + 串行队列
/// 不会开启新线程,执行完一个任务,再执行下一个任务
- (void)syncSerial {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"syncSerial--begin");
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"syncSerial--end");
    
    /* 输出结果:
     2022-01-19 11:30:12.367324+0800 GCDTest[2639:70606] currentThread--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:12.367469+0800 GCDTest[2639:70606] syncSerial--begin
     2022-01-19 11:30:14.367708+0800 GCDTest[2639:70606] 执行任务1--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:16.368723+0800 GCDTest[2639:70606] 执行任务1--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:18.369024+0800 GCDTest[2639:70606] 执行任务2--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:20.369906+0800 GCDTest[2639:70606] 执行任务2--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:22.371050+0800 GCDTest[2639:70606] 执行任务3--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:24.371358+0800 GCDTest[2639:70606] 执行任务3--<_NSMainThread: 0x600001e38440>{number = 1, name = main}
     2022-01-19 11:30:24.371632+0800 GCDTest[2639:70606] syncSerial--end
     */
    /// 输出结果分析:
    /// 没有开启新线程,同步执行不具备开启新线程
    /// 所有任务都在begin、end之前执行,同步任务需要等待队列的任务执行结束
    /// 任务按顺序执行,串行队列每次只能执行一个任务
}

/// 异步执行 + 串行队列
/// 会开启新线程,但是任务是串行的,任务按顺序执行。
- (void)asyncSerial {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"asyncSerial--begin");
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncSerial--end");
    
    /* 输出结果:
     2022-01-19 14:27:19.255747+0800 GCDTest[4943:139050] currentThread--<_NSMainThread: 0x600002a9c140>{number = 1, name = main}
     2022-01-19 14:27:19.255887+0800 GCDTest[4943:139050] asyncSerial--begin
     2022-01-19 14:27:19.256000+0800 GCDTest[4943:139050] asyncSerial--end
     2022-01-19 14:27:21.260378+0800 GCDTest[4943:139309] 执行任务1--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     2022-01-19 14:27:23.262536+0800 GCDTest[4943:139309] 执行任务1--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     2022-01-19 14:27:25.265340+0800 GCDTest[4943:139309] 执行任务2--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     2022-01-19 14:27:27.270469+0800 GCDTest[4943:139309] 执行任务2--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     2022-01-19 14:27:29.274091+0800 GCDTest[4943:139309] 执行任务3--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     2022-01-19 14:27:31.274501+0800 GCDTest[4943:139309] 执行任务3--<NSThread: 0x600002ad3000>{number = 6, name = (null)}
     */
    /// 输出结果分析:
    /// 在asyncConcurrent--end之后执行,当前线程没有等待。
    /// 异步执行具备开启新线程的能力,串行队列只能开启一个新线程,开启了一个新线程:<NSThread: 0x600002ad3000>
    /// 任务按顺序执行,串行队列每次只能执行一个任务
}

/// 同步执行 + 主队列
/// 在主线程中调用会出现死锁
/// 在其他线程中调用不会
- (void)syncMain {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"syncMain--begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"syncMain--end");
    
    /* 输出结果:
     2022-01-19 14:48:18.204885+0800 GCDTest[5271:149697] currentThread--<_NSMainThread: 0x600000600440>{number = 1, name = main}
     2022-01-19 14:48:18.205012+0800 GCDTest[5271:149697] syncMain--begin
     (lldb)
     */
    /// 输出结果分析:
    /// 在主线程中使用同步执行 + 主队列,任务都没有执行,在第一个dispatch_sync(queue...)时奔溃了。
    /// 因为把syncMain放到了主队列,而同步执行会等待当前队列中的任务执行完毕,才会接着执行。
    /// 当我们把任务1放到主队列中,任务1就会等待主队列处理完syncMain再执行。然而syncMain任务需要等待任务1执行完毕。
    /// 所以syncMain和任务1都在等对方执行完成,结果就卡住了。
}

/// 同步执行 + 主队列
/// 在其他线程调用
- (void)syncMainOtherThread {
    // 创建线程
    [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
    
    /* 输出结果
    2022-01-19 15:02:21.524275+0800 GCDTest[5528:158892] currentThread--<NSThread: 0x60000186ccc0>{number = 6, name = (null)}
    2022-01-19 15:02:21.524425+0800 GCDTest[5528:158892] syncMain--begin
    2022-01-19 15:02:23.543045+0800 GCDTest[5528:158658] 执行任务1--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:25.543365+0800 GCDTest[5528:158658] 执行任务1--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:27.545706+0800 GCDTest[5528:158658] 执行任务2--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:29.545917+0800 GCDTest[5528:158658] 执行任务2--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:31.547423+0800 GCDTest[5528:158658] 执行任务3--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:33.547666+0800 GCDTest[5528:158658] 执行任务3--<_NSMainThread: 0x600001824440>{number = 1, name = main}
    2022-01-19 15:02:33.547822+0800 GCDTest[5528:158892] syncMain--end
     */
    /// 输出结果分析:
    /// 不会死锁,因为syncMain任务放到了其他线程<NSThread: 0x60000186ccc0>,而任务1、2、3是追加到了主队列中,都在主线程<_NSMainThread: 0x600001824440>中执行。所以当执行任务123时,主队列中是没有syncMain任务的,不会出现互等。
    /// 所有任务都是在主线程中执行,不会开启新线程,所有任务都在begin、end之前执行,同步任务需要等待队列的任务执行结束
    /// 任务按顺序执行,主队列是串行队列,每次只能执行一个任务
}

/// 异步执行 + 主队列
/// 只在主线程中执行,执行完一个任务,再执行下一个任务
- (void)asyncMain {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"asyncMain--begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncMain--end");
    
    /* 输出结果:
     2022-01-19 15:30:37.011501+0800 GCDTest[5951:172877] currentThread--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:37.011640+0800 GCDTest[5951:172877] asyncMain--begin
     2022-01-19 15:30:37.011743+0800 GCDTest[5951:172877] asyncMain--end
     2022-01-19 15:30:39.046038+0800 GCDTest[5951:172877] 执行任务1--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:41.046487+0800 GCDTest[5951:172877] 执行任务1--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:43.046868+0800 GCDTest[5951:172877] 执行任务2--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:45.047671+0800 GCDTest[5951:172877] 执行任务2--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:47.047940+0800 GCDTest[5951:172877] 执行任务3--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     2022-01-19 15:30:49.048535+0800 GCDTest[5951:172877] 执行任务3--<_NSMainThread: 0x600001b38000>{number = 1, name = main}
     */
    /// 输出结果分析:
    /// 任务都在主线程中执行,没有开启新线程。
    /// 在end之后才开始执行,异步执行无需等待。
    /// 按顺序执行,主队列是串行队列,每次只能执行一个任务
}

/// 线程间通信
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
        
        dispatch_async(mainQueue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
                NSLog(@"执行任务2--%@", [NSThread currentThread]);
            }
        });
    });
    /* 输出结果:
     2022-01-19 16:02:23.168062+0800 GCDTest[6470:190632] 执行任务1--<NSThread: 0x600003c75f80>{number = 7, name = (null)}
     2022-01-19 16:02:25.173437+0800 GCDTest[6470:190632] 执行任务1--<NSThread: 0x600003c75f80>{number = 7, name = (null)}
     2022-01-19 16:02:27.174348+0800 GCDTest[6470:190560] 执行任务2--<_NSMainThread: 0x600003c24140>{number = 1, name = main}
     2022-01-19 16:02:29.175014+0800 GCDTest[6470:190560] 执行任务2--<_NSMainThread: 0x600003c24140>{number = 1, name = main}
     */
    /// 输出结果分析:
    /// 在其他线程执行完任务1,回到主线程执行任务2
}

/// GCD栅栏方法
/// 在栅栏之前的任务都执行完成后,才执行栅栏操作,最好在执行栅栏之后的任务。
- (void)barrier {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"barrier--begin");
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"barrier--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务4--%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"barrier--end");
    
    /* 输出结果:
     2022-01-19 16:19:00.293322+0800 GCDTest[6742:199598] currentThread--<_NSMainThread: 0x6000019b8200>{number = 1, name = main}
     2022-01-19 16:19:00.293479+0800 GCDTest[6742:199598] barrier--begin
     2022-01-19 16:19:00.293608+0800 GCDTest[6742:199598] barrier--end
     2022-01-19 16:19:02.297275+0800 GCDTest[6742:199664] 执行任务2--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:02.297290+0800 GCDTest[6742:199660] 执行任务1--<NSThread: 0x600001982940>{number = 6, name = (null)}
     2022-01-19 16:19:04.297676+0800 GCDTest[6742:199664] 执行任务2--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:04.297676+0800 GCDTest[6742:199660] 执行任务1--<NSThread: 0x600001982940>{number = 6, name = (null)}
     2022-01-19 16:19:06.302685+0800 GCDTest[6742:199664] barrier--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:08.303138+0800 GCDTest[6742:199664] barrier--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:10.306357+0800 GCDTest[6742:199665] 执行任务4--<NSThread: 0x6000019f9f00>{number = 8, name = (null)}
     2022-01-19 16:19:10.306357+0800 GCDTest[6742:199664] 执行任务3--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:12.307780+0800 GCDTest[6742:199664] 执行任务3--<NSThread: 0x6000019f5680>{number = 3, name = (null)}
     2022-01-19 16:19:12.307782+0800 GCDTest[6742:199665] 执行任务4--<NSThread: 0x6000019f9f00>{number = 8, name = (null)}
     */
}

/// GCD 延时执行方法
/// 再指定时间之后将任务加到队列中,严格来讲,这个时间并不是精准的
- (void)after {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"after--begin");
    
    // 2秒后将任务异步加到主队列中
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after--%@", [NSThread currentThread]);
    });
    
    NSLog(@"after--end");
    
    /* 输出结果:
     2022-01-19 17:08:15.836887+0800 GCDTest[7548:228428] currentThread--<_NSMainThread: 0x600003eec140>{number = 1, name = main}
     2022-01-19 17:08:15.837011+0800 GCDTest[7548:228428] after--begin
     2022-01-19 17:08:15.837120+0800 GCDTest[7548:228428] after--end
     2022-01-19 17:08:17.837261+0800 GCDTest[7548:228428] after--<_NSMainThread: 0x600003eec140>{number = 1, name = main}
     */
}

/// GCD  只执行一次方法
/// 常用于创建单例,即使在多线程下,也能保证线程安全
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"执行一次的代码");
    });
}

/// GCD 快速迭代方法
/// 按照指定的次数将指定的任务加到指定的队列中,并等待全部任务执行结束。
- (void)apply {
    NSLog(@"apply--begin");
    // 获取全局并发队列。如果在串行队列中使用dispatch_apply,那么就和for循环一样,按顺序同步执行。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 无论是串行队列,还是并发队列,dispatch_apply都会等待全部任务执行完毕。
    dispatch_apply(10, queue, ^(size_t iteration) {
        NSLog(@"%zu--%@", iteration, [NSThread currentThread]);
    });
    
    NSLog(@"apply--end");
    
    /* 输出结果:
     2022-01-19 17:32:43.274729+0800 GCDTest[7970:244521] apply--begin
     2022-01-19 17:32:43.274908+0800 GCDTest[7970:244521] 0--<_NSMainThread: 0x600001bac000>{number = 1, name = main}
     2022-01-19 17:32:43.274917+0800 GCDTest[7970:244607] 2--<NSThread: 0x600001be16c0>{number = 7, name = (null)}
     2022-01-19 17:32:43.274920+0800 GCDTest[7970:244613] 1--<NSThread: 0x600001bede40>{number = 6, name = (null)}
     2022-01-19 17:32:43.274957+0800 GCDTest[7970:244611] 3--<NSThread: 0x600001be6ec0>{number = 3, name = (null)}
     2022-01-19 17:32:43.275039+0800 GCDTest[7970:244521] 4--<_NSMainThread: 0x600001bac000>{number = 1, name = main}
     2022-01-19 17:32:43.275045+0800 GCDTest[7970:244607] 5--<NSThread: 0x600001be16c0>{number = 7, name = (null)}
     2022-01-19 17:32:43.275066+0800 GCDTest[7970:244613] 6--<NSThread: 0x600001bede40>{number = 6, name = (null)}
     2022-01-19 17:32:43.275078+0800 GCDTest[7970:244611] 7--<NSThread: 0x600001be6ec0>{number = 3, name = (null)}
     2022-01-19 17:32:43.275135+0800 GCDTest[7970:244521] 8--<_NSMainThread: 0x600001bac000>{number = 1, name = main}
     2022-01-19 17:32:43.275156+0800 GCDTest[7970:244607] 9--<NSThread: 0x600001be16c0>{number = 7, name = (null)}
     2022-01-19 17:32:43.275773+0800 GCDTest[7970:244521] apply--end
     */
    /// 输出结果分析:
    /// 并发队列 + 异步执行,开启了多个线程
    /// 执行时间长短不定,顺序也不定
    /// apply--end在最后执行,dispatch_apply会等待全部任务执行完毕
}

/// GCD 队列组 dispatch_group_notify
/// 监听group中任务的完成状态,当所有任务都执行完毕后,再执行dispatch_group_notify block 中的任务
- (void)groupNotify {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"groupNotify--begin");
    
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
        NSLog(@"groupNotify--end");
    });
    
    /* 输出结果:
     2022-01-19 17:50:09.172152+0800 GCDTest[8273:255171] currentThread--<_NSMainThread: 0x600002420040>{number = 1, name = main}
     2022-01-19 17:50:09.172298+0800 GCDTest[8273:255171] groupNotify--begin
     2022-01-19 17:50:11.172600+0800 GCDTest[8273:255378] 执行任务1--<NSThread: 0x600002464b80>{number = 5, name = (null)}
     2022-01-19 17:50:11.172610+0800 GCDTest[8273:255375] 执行任务2--<NSThread: 0x6000024612c0>{number = 7, name = (null)}
     2022-01-19 17:50:13.172823+0800 GCDTest[8273:255375] 执行任务2--<NSThread: 0x6000024612c0>{number = 7, name = (null)}
     2022-01-19 17:50:13.172823+0800 GCDTest[8273:255378] 执行任务1--<NSThread: 0x600002464b80>{number = 5, name = (null)}
     2022-01-19 17:50:15.176670+0800 GCDTest[8273:255378] 执行任务3--<NSThread: 0x600002464b80>{number = 5, name = (null)}
     2022-01-19 17:50:17.177844+0800 GCDTest[8273:255378] 执行任务3--<NSThread: 0x600002464b80>{number = 5, name = (null)}
     2022-01-19 17:50:17.178005+0800 GCDTest[8273:255378] groupNotify--end
     */
    /// 输出结果分析:
    /// 当所有任务全部执行完毕后,才执行dispatch_group_notify block 中的任务。
}

/// GCD 队列组 dispatch_group_wait
/// 阻塞当前线程,等待任务全部完成,才继续往下走
- (void)groupWait {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"groupWait--begin");
    
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
    });
    
    // 等待上面任务全部完成,才继续往下走
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"groupWait--end");
    
    /* 输出结果:
     2022-01-19 18:00:41.022138+0800 GCDTest[8454:261866] currentThread--<_NSMainThread: 0x600000ab40c0>{number = 1, name = main}
     2022-01-19 18:00:41.022299+0800 GCDTest[8454:261866] groupWait--begin
     2022-01-19 18:00:43.024546+0800 GCDTest[8454:261961] 执行任务1--<NSThread: 0x600000afca80>{number = 4, name = (null)}
     2022-01-19 18:00:43.024654+0800 GCDTest[8454:261964] 执行任务2--<NSThread: 0x600000af7640>{number = 6, name = (null)}
     2022-01-19 18:00:45.029005+0800 GCDTest[8454:261961] 执行任务1--<NSThread: 0x600000afca80>{number = 4, name = (null)}
     2022-01-19 18:00:45.029006+0800 GCDTest[8454:261964] 执行任务2--<NSThread: 0x600000af7640>{number = 6, name = (null)}
     2022-01-19 18:00:45.029260+0800 GCDTest[8454:261866] groupWait--end
     */
}

/// GCD 队列组
/// dispatch_group_enter 标志着一个任务加入 group,相当于 group 中未执行完毕数+1
/// dispatch_group_leave 标志着一个任务离开 group,相当于 group 中未执行完毕数-1
- (void)groupEnterAndLeave {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"groupEnterAndLeave--begin");
    
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务1--%@", [NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务2--%@", [NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
            NSLog(@"执行任务3--%@", [NSThread currentThread]);
        }
        NSLog(@"groupEnterAndLeave--end");
    });
    
    /* 输出结果:
     2022-01-19 18:16:12.020326+0800 GCDTest[8754:272504] currentThread--<_NSMainThread: 0x6000004bc840>{number = 1, name = main}
     2022-01-19 18:16:12.020520+0800 GCDTest[8754:272504] groupEnterAndLeave--begin
     2022-01-19 18:16:14.022818+0800 GCDTest[8754:272736] 执行任务2--<NSThread: 0x6000004e8440>{number = 7, name = (null)}
     2022-01-19 18:16:14.022818+0800 GCDTest[8754:272738] 执行任务1--<NSThread: 0x6000004f5240>{number = 4, name = (null)}
     2022-01-19 18:16:16.024763+0800 GCDTest[8754:272736] 执行任务2--<NSThread: 0x6000004e8440>{number = 7, name = (null)}
     2022-01-19 18:16:16.024763+0800 GCDTest[8754:272738] 执行任务1--<NSThread: 0x6000004f5240>{number = 4, name = (null)}
     2022-01-19 18:16:18.027618+0800 GCDTest[8754:272736] 执行任务3--<NSThread: 0x6000004e8440>{number = 7, name = (null)}
     2022-01-19 18:16:20.030572+0800 GCDTest[8754:272736] 执行任务3--<NSThread: 0x6000004e8440>{number = 7, name = (null)}
     2022-01-19 18:16:20.030757+0800 GCDTest[8754:272736] groupEnterAndLeave--end
     */
    /// 输出结果分析:
    /// 所有任务执行完毕后,才执行 dispatch_group_notify block 中的任务
    /// dispatch_group_enter dispatch_group_leave 组合等同于 dispatch_group_async
}

/// GCD 信号量 dispatch_semaphore
/// 信号量使“异步”线程完成“同步”操作
/// 保证线程安全
/// signal 与 wait 一一对应使用
- (void)semaphoreSync {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"semaphoreSync--begin");
    
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 创建一个semaphore,并初始化信号量>=0,<0的值会返回NULL
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 1;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];  // 模拟耗时操作
        NSLog(@"执行任务1--%@", [NSThread currentThread]);

        number = 100;

        // 发信号,信号量加1
        dispatch_semaphore_signal(semaphore);
    });
    
    // 可以使信号量减1,当信号量为0时,就一直等待
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"semaphoreSync--end number=%d", number);
    
    /* 输出结果:
     2022-01-20 10:50:33.829768+0800 GCDTest[2176:41610] [Unknown process name] copy_read_only: vm_copy failed: status 1.
     2022-01-20 10:50:33.991973+0800 GCDTest[2176:41504] currentThread--<_NSMainThread: 0x600002328840>{number = 1, name = main}
     2022-01-20 10:50:33.992087+0800 GCDTest[2176:41504] semaphoreSync--begin
     2022-01-20 10:50:33.992188+0800 GCDTest[2176:41504] semaphoreSync--wait number=1
     2022-01-20 10:50:35.993857+0800 GCDTest[2176:41611] 执行任务1--<NSThread: 0x600002365000>{number = 4, name = (null)}
     2022-01-20 10:50:35.994333+0800 GCDTest[2176:41504] semaphoreSync--end number=100
     */
}


/// 非线程安全,不使用semaphore
- (void)semaphoreSyncStatusNotSave {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"semaphoreSyncStatusNotSave--begin");
    
    self.count = 10;
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakself = self;
    dispatch_async(queue1, ^{
        [weakself countSubtractNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakself countSubtractNotSafe];
    });
    
    /* 输出结果:
     2022-01-20 11:38:27.808121+0800 GCDTest[3016:70932] currentThread--<_NSMainThread: 0x6000038d40c0>{number = 1, name = main}
     2022-01-20 11:38:27.808245+0800 GCDTest[3016:70932] semaphoreSyncStatusNotSave--begin
     2022-01-20 11:38:27.808444+0800 GCDTest[3016:71026] count=8 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:27.808444+0800 GCDTest[3016:71029] count=9 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:28.808637+0800 GCDTest[3016:71026] count=7 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:28.808693+0800 GCDTest[3016:71029] count=6 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:29.809625+0800 GCDTest[3016:71026] count=5 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:29.809625+0800 GCDTest[3016:71029] count=5 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:30.814457+0800 GCDTest[3016:71029] count=4 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:30.814457+0800 GCDTest[3016:71026] count=3 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:31.814761+0800 GCDTest[3016:71029] count=2 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:31.814761+0800 GCDTest[3016:71026] count=2 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:32.817754+0800 GCDTest[3016:71029] count=0 <NSThread: 0x6000038efdc0>{number = 6, name = (null)}
     2022-01-20 11:38:32.817753+0800 GCDTest[3016:71026] count=1 <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     2022-01-20 11:38:33.817991+0800 GCDTest[3016:71026] end <NSThread: 0x6000038efdc0>{number = 7, name = (null)}
     2022-01-20 11:38:33.817991+0800 GCDTest[3016:71029] end <NSThread: 0x6000038dc700>{number = 7, name = (null)}
     */
    /// 输出结果分析:
    /// 在不使用semaphore情况下,count得到的数是错乱的。
}

- (void)countSubtractNotSafe {
    while (1) {
        if (self.count > 0) {
            self.count--;
            NSLog(@"count=%d %@", self.count, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:1];
        } else {
            NSLog(@"end %@", [NSThread currentThread]);
            break;
        }
    }
}

/// 线程安全,使用semaphore
- (void)semaphoreSyncStatusSave {
    NSLog(@"currentThread--%@", [NSThread currentThread]);
    NSLog(@"semaphoreSyncStatusSave--begin");
    
    // 创建一个semaphore,并初始化信号量1
    self.semaphore = dispatch_semaphore_create(1);
    
    self.count = 10;
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakself = self;
    dispatch_async(queue1, ^{
        [weakself countSubtractSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakself countSubtractSafe];
    });
    
    /*
     2022-01-20 11:46:37.203806+0800 GCDTest[3156:76143] currentThread--<_NSMainThread: 0x600000c64600>{number = 1, name = main}
     2022-01-20 11:46:37.204017+0800 GCDTest[3156:76143] semaphoreSyncStatusSave--begin
     2022-01-20 11:46:37.204328+0800 GCDTest[3156:76363] count=9 <NSThread: 0x600000c2c300>{number = 3, name = (null)}
     2022-01-20 11:46:38.207165+0800 GCDTest[3156:76365] count=8 <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     2022-01-20 11:46:39.211938+0800 GCDTest[3156:76363] count=7 <NSThread: 0x600000c2c300>{number = 3, name = (null)}
     2022-01-20 11:46:40.216589+0800 GCDTest[3156:76365] count=6 <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     2022-01-20 11:46:41.221414+0800 GCDTest[3156:76363] count=5 <NSThread: 0x600000c2c300>{number = 3, name = (null)}
     2022-01-20 11:46:42.222815+0800 GCDTest[3156:76365] count=4 <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     2022-01-20 11:46:43.223155+0800 GCDTest[3156:76363] count=3 <NSThread: 0x600000c2c300>{number = 3, name = (null)}
     2022-01-20 11:46:44.224244+0800 GCDTest[3156:76365] count=2 <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     2022-01-20 11:46:45.226359+0800 GCDTest[3156:76363] count=1 <NSThread: 0x600000c2c300>{number = 3, name = (null)}
     2022-01-20 11:46:46.227220+0800 GCDTest[3156:76365] count=0 <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     2022-01-20 11:46:47.227548+0800 GCDTest[3156:76363] end <NSThread: 0x600000c2c300>{number = 8, name = (null)}
     2022-01-20 11:46:47.227709+0800 GCDTest[3156:76365] end <NSThread: 0x600000c2c780>{number = 8, name = (null)}
     */
    /// 输出结果分析:
    /// 在使用semaphore情况下,count得到的数没有错乱的。
}

- (void)countSubtractSafe {
    while (1) {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        
        if (self.count > 0) {
            self.count--;
            NSLog(@"count=%d %@", self.count, [NSThread currentThread]);
            [NSThread sleepForTimeInterval:1];
        } else {
            NSLog(@"end %@", [NSThread currentThread]);
            dispatch_semaphore_signal(self.semaphore);
            break;
        }
        dispatch_semaphore_signal(self.semaphore);
    }
}

@end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值