进程:正在进行中的程序被称为进程,负责程序运行的内存分配
每一个进程都有自己独立的虚拟内存空间
线程:线程是进程中一个独立的执行路径(控制单元)
一个进程中至少包含一条线程,即主线程
操作:任务/
同步操作(sync) :一个接着一个,前一个没有执行完,后面不能执行,不开线程。
会依次顺序执行,能够决定任务的执行顺序/
异步操作(async):开启多个新线程,任务同一时间可以一起执行。异步是多线程的代名词
会并发执行,无法确定任务的执行顺序
队列:装载线程任务的队形结构。
(系统以先进先出的方式调度队列中的任务执行)。
在GCD中有两种队列:串行队列和并发队列。/
并行队列:队列中的任务通常会并发执行
线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)/
串行队列:队列中的任务只会顺序执行
线程只能依次有序的执行/
主队列:主队列负责在主线程上调度任务,如果在主线程上已经有任务正在执行,主队列会等到主线程空闲后再调度任务。通常是返回主线程更新UI的时候使用。dispatch_get_main_queue()/
全局并发队列:全局并发队列是就是一个并发队列,是为了让我们更方便的使用多线程。dispatch_get_global_queue(0, 0)/
// 同步执行任务
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
// 任务放在这个block里
NSLog(@"我是同步执行的任务");
});
// 异步执行任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 任务放在这个block里
NSLog(@"我是异步执行的任务");
});
使用dispatch_queue_create来创建队列对象,传入两个参数,
第一个参数表示队列的唯一标识符,可为空。
第二个参数用来表示串行队列(DISPATCH_QUEUE_SERIAL)或
并发队列(DISPATCH_QUEUE_CONCURRENT)。
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.YDW.Queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.YDW.Queue", DISPATCH_QUEUE_CONCURRENT);
主队列:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗时操作放在这里
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程进行UI操作
});
});
全局并发队列:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 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 // 后台优先级
//iOS8开始使用服务质量,现在获取全局并发队列时,可以直接传0
dispatch_get_global_queue(0, 0);
串行同步
|123456789| 1个人 1干到9 干完
串行异步
|123 | 1个人 1干到3 干完叫
| 456 | 1个人4干到6 干完叫
| 789| 1个人7干到9 干完
并发同步
|1 | 1个人 1 干完叫
| 2 | 1个人 2 干完叫
。。。
| 9| 1个人 9 干完
并发异步 一起干
|1 | 1个人 1 干完
|2 | 1个人 2 干完
。。。
|9 | 1个人 9 干完
主队列同步
|123456789| 1个人 1干到9 干完
还没干完1就 叫干2 会造成等待死锁
主队列异步 一起干
|1 | 1个人 1 干完
|2 | 1个人 2 干完
。。。
|9 | 1个人 9 干完
GCD线程之间的通讯
开发中需要在主线程上进行UI的相关操作,通常会把一些耗时的操作放在其他线程,比如说图片文件下载等耗时操作。
当完成了耗时操作之后,需要回到主线程进行UI的处理,这里就用到了线程之间的通讯。
当需要等待一会再执行一段代码时,就可以用到这个方法了:dispatch_after。
和只执行了一次 dispatch_once
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 5秒后异步执行
NSLog(@"我已经等待了5秒!");
});
GCD实现代码只执行一次
使用dispatch_once能保证某段代码在程序运行过程中只被执行1次。可以用来设计单例。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"程序运行过程中我只执行了一次!");
});
GCD栅栏
当任务需要异步进行,但是这些任务需要分成两组来执行,第一组完成之后才能进行第二组的操作。这时候就用了到GCD的栅栏方法dispatch_barrier_async。
- (IBAction)barrierGCD:(id)sender {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 异步执行
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步1 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步2 %@",[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"------------barrier------------%@", [NSThread currentThread]);
NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步3 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步4 %@",[NSThread currentThread]);
}
});
}
GCD快速迭代
GCD有一个快速迭代的方法dispatch_apply,dispatch_apply可以同时遍历多个数字。
GCD队列组
队列组有下面几个特点:
所有的任务会并发的执行(不按序)。
所有的异步函数都添加到队列中,然后再纳入队列组的监听范围。
使用dispatch_group_notify函数,来监听上面的任务是否完成,如果完成, 就会调用这个方法。
- (void)testGroup {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"队列组:有一个耗时操作完成!");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"队列组:有一个耗时操作完成!");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
});
}
NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程。
NSOperation实现多线程的步骤如下:
- 创建任务:先将需要执行的操作封装到NSOperation对象中。
- 创建队列:创建NSOperationQueue。
- 将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中
使用:
使用子类NSInvocationOperation
使用子类NSBlockOperation
定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
NSInvocationOperation
- (void)testNSInvocationOperation {
// 创建NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
// 开始执行操作
[invocationOperation start];
}
- (void)invocationOperation {
NSLog(@"NSInvocationOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
}
NSBlockOperation
- (void)testNSBlockOperationExecution {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperation运用addExecutionBlock主任务========%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务1========%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务2========%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务3========%@", [NSThread currentThread]);
}];
[blockOperation start];
}
定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
/*******************"WHOperation.h"*************************/
#import @interface WHOperation : NSOperation
@end
/*******************"WHOperation.m"*************************/
#import "WHOperation.h"
@implementation WHOperation
- (void)main {
for (int i = 0; i < 3; i++) {
NSLog(@"NSOperation的子类WHOperation======%@",[NSThread currentThread]);
}
}
@end
/*****************回到主控制器使用WHOperation**********************/
- (void)testWHOperation {
WHOperation *operation = [[WHOperation alloc] init];
[operation start];
}
//主队列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
//非主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
非主队列(其他队列)可以实现串行或并行。
队列NSOperationQueue有一个参数叫做最大并发数:maxConcurrentOperationCount。
maxConcurrentOperationCount默认为-1,直接并发执行,所以加入到‘非队列’中的任务默认就是并发,开启多线程。
当maxConcurrentOperationCount为1时,则表示不开线程,也就是串行。
当maxConcurrentOperationCount大于1时,进行并发执行。
系统对最大并发数有一个限制,所以即使程序员把maxConcurrentOperationCount设置的很大,系统也会自动调整。所以把最大并发数设置的很大是没有意义的。
任务加到队列
- (void)testOperationQueue {
// 创建队列,默认并发
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作,NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];
// 创建操作,NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"addOperation把任务添加到队列======%@", [NSThread currentThread]);
}
}];
[queue addOperation:invocationOperation];
[queue addOperation:blockOperation];
}
- (void)invocationOperationAddOperation {
NSLog(@"invocationOperation===aaddOperation把任务添加到队列====%@", [NSThread currentThread]);
}
任务加到队列 简便写法
- (void)testAddOperationWithBlock {
// 创建队列,默认并发
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 最大并发数为1,串行
queue.maxConcurrentOperationCount = 1;
// 最大并发数为2,并发
// queue.maxConcurrentOperationCount = 2;
// 添加操作到队列
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"addOperationWithBlock把任务添加到队列======%@", [NSThread currentThread]);
}
}];
}
- (void)cancelAllOperations;
- (void)cancel ;
// 暂停队列
[queue setSuspended:YES];
判断队列是否暂停
- (BOOL)isSuspended;
某一个操作(operation2)依赖于另一个操作(operation1),只有当operation1执行完毕,才能执行operation2.
NSOperation的操作依赖
- (void)testAddDependency {
// 并发队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操作1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
NSLog(@"operation1======%@", [NSThread currentThread]);
}
}];
// 操作2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****");
for (int i = 0; i < 3; i++) {
NSLog(@"operation2======%@", [NSThread currentThread]);
}
}];
// 使操作2依赖于操作1
[operation2 addDependency:operation1];
// 把操作加入队列
[queue addOperation:operation1];
[queue addOperation:operation2];
}
https://blog.csdn.net/liqun3yue25/article/details/88258532
实际运用中还用到的
iOS GCD group 多请求异步 semaphore 多请求同步
1.多图上传的功能,而恰巧服务器可能只支持单张上传 重复请求多次接口
//创建一个组
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i <9; I++)
{//模仿多个网络请求
dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{
//异步网络请求
int x = arc4random() % 5;
//模拟网络请求快慢不确定的情况
sleep(x);
NSLog(@"group 请求成功OR请求失败 %d!",i);
});
}
NSLog(@"group开始 网络请求!");
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//网络请求完毕 回到主线程更新UI 或者做些其它的操作
NSLog(@"group所有请求完毕!!!");
});
2.用到的 接口请求
//创建一个组
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i <9; I++)
{//模仿多个网络请求
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//异步网络请求
int x = arc4random() % 5;
//模拟网络请求快慢不确定的情况
sleep(x);
NSLog(@"group 请求成功OR请求失败 %d!",i);
dispatch_group_leave(group);
});
}
NSLog(@"group开始 网络请求!");
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//网络请求完毕 回到主线程更新UI 或者做些其它的操作
NSLog(@"group所有请求完毕!!!");
});
3.比如要做一个相册功能,也是多图上传,但是要有规律按顺序上传
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/// 创建一个线程"001" 确保之后不要阻塞主线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
/// 创建一个信号量 数值为1 信号量可以让线程阻塞等待
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
for (int i = 0; i<9; i++) {
/// 模仿9个请求任务
/// 执行dispatch_semaphore_wait 信号量数值 -1.
/// 当i为0此时的信号量数值为0, 当此时的信号量大于等于0继续执行wait函数下面的语句.
/// 当i为1此时的信号量数值为-1, 阻塞当前线程 阻塞时长为DISPATCH_TIME_FOREVER, 不执行wait函数下面的语句.
/// 只有等到执行i为0 的 dispatch_semaphore_signal 方法执行, 信号量数值+1 为0, 唤醒 继续执行wait函数下面的语句.
/// 以此类推循环.
NSLog(@"当前线程:%@",[NSThread currentThread]);
/// ******这是一个网络请求开始******
dispatch_async(dispatch_get_global_queue(0, 0), ^{
/// 模拟异步网络请求
int x = arc4random() % 2;
/// 模拟网络请求快慢
sleep(x);
NSLog(@"执行任务代号:%d",i);
/// 任务结束 信号量数值+1 解放线程"001" 阻塞
dispatch_semaphore_signal(semaphore);
});
/// ******这是一个网络请求结束******
/// 信号量减1 变为负数 当前线程"001" 阻塞
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
});
}
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
dispatch_source 使用场景:由于 dispatch_source 不依赖于 Runloop,而是直接和底层内核交互,准确性更高,所以经常用于验证码倒计时。
- (void)use {
// 倒计时时间
__block int timeout = 3;
// 创建队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);// 创建timerdispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);// 设置1s触发一次,0s的误差/- source 分派源- start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。- interval 间隔时间- leeway 计时器触发的精准程度/
dispatch_source_set_timer(timer,dispatch_walltime(NULL,0),
1.0*NSEC_PER_SEC, 0);
// 触发的事件
dispatch_source_set_event_handler(timer, ^{// 倒计时结束,关闭
if (timeout <= 0) {// 取消dispatch源
dispatch_source_cancel(timer);
}else{
timeout--;
dispatch_async(dispatch_get_main_queue(), ^{// 更新主界面的操作
NSLog(@"倒计时 - %d", timeout);
});
}
});// 开始执行dispatch源
dispatch_resume(timer);
}
https://www.jianshu.com/p/8416d541ef5a
https://betheme.net/news/txtlist_i192062v.html?action=onClick
https://www.jianshu.com/p/c1f4d7ef6002
if (self.cantCommentCourse && self.objectType.intValue == 4) {
[DJAlertController showTagWithMessage:@"购买后才可发表想法"];
return;
}
self.imageUrlString = @"";
[GBMBProgressHUD showMessage:@""];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 执行 同步并且顺序执行
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger count = 0;
__block NSMutableArray *imageMArray = imageArrM.mutableCopy;
for (NSInteger i = 0; i < imageArrM.count; ++i) {
UIImage *image = [UIImage imageCompressDefaultScale:imageArrM[i]];
NSData *imageData = UIImageJPEGRepresentation(image, 0.5);
// 文件命名
NSDictionary *imageDictionary = @{@"file" : imageData};
dispatch_queue_t concurrentQ = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQ, ^{
@weakify_self
[DJDiscoverRecommendManager dj_GEEBOO_file_oss_imgByFile:imageDictionary bizType:@"2" successBlock:^(NSDictionary *data){
@strongify_self
if ([NSString isEmpty:self.imageUrlString]){
self.imageUrlString = data[@"fileName"];
}else{
self.imageUrlString = [NSString stringWithFormat:@"%@|%@",self.imageUrlString,data[@"fileName"]];
}
count ++;
if (count == imageMArray.count) {
dispatch_semaphore_signal(semaphore);
}
} failureBlock:^(NSError *error){
LOG_ME_DEBUG(@"%@",error.domain);
count ++;
if (count == imageMArray.count) {
dispatch_semaphore_signal(semaphore);
}else{
[GBMBProgressHUD showError:error.domain];
}
}];
});
}
if (imageMArray.count == 0) {// 没有图片时,终止「阻塞当前线程」
dispatch_semaphore_signal(semaphore);
}
// dispatch_semaphore_t 信号量会阻塞当前进程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
LOG_ME_DEBUG(@"waiting。。。");
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
[params setObject:self.objectId forKey:@"objectId"];
[params setObject:content forKey:@"content"];
if (self.imageUrlString.length>0) {
[params setObject:self.imageUrlString forKey:@"images"];
}
[params setObject:self.objectType forKey:@"objectType"];
@weakify_self
[DJCommentManager dj_commentCommentAddWithParams:params Finished:^(BOOL isHttpSuccess) {
[GBMBProgressHUD hideHUD];
@strongify_self
if (isHttpSuccess) {
[self.bottomCommentView initContent];
}
}];
});
[self.tableView setHeaderFooterisHidden:YES];
[GBMBProgressHUD showToView:self.view frame:FRAME(0, 0, kScreenWidth, self.view.height)];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
@weakify_self
[self getRequestCommendData:^(BOOL isHttpSuccess) {
@strongify_self
NSLog(@"评论详情,%@",@(self.tableView.height));
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
self.tableView.sinceId = @"0";
self.tableView.maxId = @"";
[self getRequestData:^(BOOL isHttpSuccess) {
@strongify_self
NSLog(@"用户回复,%@",@(self.tableView.height));
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[GBMBProgressHUD hideHUDForView:self.view];
if (self.commentModel||self.tableView.dataArray.count>0) {
[self createRefreshTool];
[self setTableHeaderView];
self.bottomCommentView.hidden = NO;
[self.bottomCommentView revplyParentReplyId:@"0" CommentId:self.commentId NickName:@""];
}else{
[self extractedConfigNoNetView];
}
});
});