前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS 多线程总结

iOS 多线程总结

作者头像
赵哥窟
发布2019-09-27 11:25:14
7260
发布2019-09-27 11:25:14
举报
文章被收录于专栏:日常技术分享日常技术分享

了解多线程,首先我们需要了解以下知识

进程

●进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app。 ●每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源。

线程

●程序执行流的最小单元,线程是进程中的一个实体. ●一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程

任务

任务就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)

同步

同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行,即会阻塞线程。只能在当前线程中执行任务,不具备开启新线程的能力。

代码语言:javascript
复制
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);  
    NSLog(@"1");  
    dispatch_sync(concurrentQueue, ^(){  
        NSLog(@"2");  
        [NSThread sleepForTimeInterval:2];  
        NSLog(@"3");  
    });  
 NSLog(@"4"); 
异步

线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程。可以在新的线程中执行任务,具备开启新线程的能力。如果不是添加到主队列上,异步会在子线程中执行任务

代码语言:javascript
复制
dispatch_queue_t queue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);  
    dispatch_async(queue, ^{  
        // dispatch_async是异步方法。长时间处理,例如数据库访问  
        dispatch_async(dispatch_get_main_queue(), ^{  
            // 到主线程队列中执行  
            // 例如界面更新  
        });  
 });  
队列

队列(Dispatch Queue):队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue):

同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的

串行队列

代码语言:javascript
复制
// 串行队列DISPATCH_QUEUE_SERIAL 
// 并发队列DISPATCH_QUEUE_CONCURRENT
 dispatch_queue_t serialQueue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"1");
    
    dispatch_async(serialQueue, ^{
        
         NSLog(@"2");
    });
    
    NSLog(@"3");
    
    dispatch_sync(serialQueue, ^{
        
        NSLog(@"4");
    });
    
 NSLog(@"5");

打印结果13245

首先先打印1 接下来将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3然后是将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5 所以最终顺序就是13245。

并发队列(Concurrent Dispatch Queue):

同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。并发队列的并发功能只有在异步(dispatch_async)函数下才有效。

代码语言:javascript
复制
// 串行队列DISPATCH_QUEUE_SERIAL 
// 并发队列DISPATCH_QUEUE_CONCURRENT
 dispatch_queue_t  queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        sleep(1);
        [[NSThread currentThread] setName:@"任务A"];
        NSLog(@"任务A  thread:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        sleep(1);
        [[NSThread currentThread] setName:@"任务B"];
        NSLog(@"任务B  thread:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        sleep(1);
        [[NSThread currentThread] setName:@"任务C"];
        NSLog(@"任务C  thread:%@",[NSThread currentThread]);
    });
    NSLog(@"结束");

打印

代码语言:javascript
复制
2019-08-30 10:34:02.718139+0800 TestDemo[56896:6617059] 结束
2019-08-30 10:34:03.722077+0800 TestDemo[56896:6617114] 任务B  thread:<NSThread: 0x6000030407c0>{number = 4, name = 任务B}
2019-08-30 10:34:03.722092+0800 TestDemo[56896:6617111] 任务A  thread:<NSThread: 0x60000305c3c0>{number = 5, name = 任务A}
2019-08-30 10:34:03.722091+0800 TestDemo[56896:6617112] 任务C  thread:<NSThread: 0x60000305a6c0>{number = 3, name = 任务C}
iOS中的多线程

主要有三种:NSThread、NSoperationQueue、GCD

1. NSThread

NSThread轻量级别的多线程技术 需要自己手动开辟的子线程,如果使用的是初始化方式就需要我们自己启动,如果使用的是构造器方式它就会自动启动。只要是我们手动开辟的线程,都需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收。

手动开启线程

代码语言:javascript
复制
 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是参数"];
// 当使用初始化方法出来的主线程需要start启动
[thread start];
// 可以为开辟的子线程起名字
thread.name = @"NSThread线程";
// 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
thread.threadPriority = 1;
// 取消当前已经启动的线程
[thread cancel];

通过遍历构造器开辟子线程

代码语言:javascript
复制
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];
2.NSoperationQueue
代码语言:javascript
复制
 NSOperationQueue  *queue = [[NSOperationQueue alloc]init];
    
 NSBlockOperation *opA = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        [[NSThread currentThread]setName:@"任务A"];
        NSLog(@"任务A  thread:%@",[NSThread currentThread]);
  }];
    
 NSBlockOperation *opB = [NSBlockOperation blockOperationWithBlock:^{
        sleep(10);
        [[NSThread currentThread]setName:@"任务B"];
        NSLog(@"任务B  thread:%@",[NSThread currentThread]);
 }];
    
 NSBlockOperation *opC = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        [[NSThread currentThread]setName:@"任务C"];
        NSLog(@"任务C  thread:%@",[NSThread currentThread]);
 }];
    
 //添加依赖关系,保证执行顺序
 [opC addDependency:opB];
 [opB addDependency:opA];
    
 [queue addOperation:opA];
 [queue addOperation:opB];
 [queue addOperation:opC];

打印

代码语言:javascript
复制
2019-08-30 10:44:35.753808+0800 TestDemo[57063:6627379] 任务A  thread:<NSThread: 0x6000022360c0>{number = 3, name = 任务A}
2019-08-30 10:44:45.759742+0800 TestDemo[57063:6627377] 任务B  thread:<NSThread: 0x6000022360c0>{number = 4, name = 任务B}
2019-08-30 10:44:48.765530+0800 TestDemo[57063:6627377] 任务C  thread:<NSThread: 0x6000022360c0>{number = 4, name = 任务C}
GCD
dispatch_semaphore

dispatch_semaphore是GCD用来同步的一种方式,与他相关的共有三个函数,分别是 dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。

Dispatch Semaphore 在实际开发中主要用于:

保持线程同步,将异步执行任务转换为同步执行任务 保证线程安全,为线程加锁

dispatch_semaphore_signal:

这个函数会使传入的信号量semaphore的值加1;

dispatch_semaphore_wait

这个函数会使传入的信号量semaphore的值减1;这个函数的作用是这样的,如果semaphore信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果semaphore的值为0,那么这个函数就阻塞当前线程等待timeout

卖火车票经典案例
代码语言:javascript
复制
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.ticketNumber = 100;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    for (NSInteger i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicketsWithSemaphore) object:nil];
            [thread setName:[NSString stringWithFormat:@"售票员-%zd",i]];
            [thread start];
            dispatch_semaphore_signal(semaphore);
            
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }
}

- (void)sellTicketsWithSemaphore
{
    while (true) {
        //信号==0就阻塞当前线程等待timeout,>0就继续执行下面的语句信号量的值减1
        if (self.ticketNumber > 0) {
            
            self.ticketNumber --;
            NSLog(@"%@卖了一张票,还剩%ld张票",[[NSThread currentThread] name],self.ticketNumber);
            
        }else{
            // 退出当前线程
            [NSThread exit];
        }
    }
}
使用GCD如何实现这个需求:A、B、C 三个任务并发,完成后执行任务 D?

需要解决这个首先就需要了解dispatch_group_enter 和 dispatch_group_leave。

dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1 dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。

代码语言:javascript
复制
 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    [self requestA:^{
        NSLog(@"---执行A任务结束---");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [self requestB:^{
        NSLog(@"---执行B任务结束---");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [self requestC:^{
        NSLog(@"---执行C任务结束---");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_notify(group, globalQueue, ^{
        [self requestD:^{
            NSLog(@"---执行D任务结束---");
        }];
    });
代码语言:javascript
复制
- (void)requestA:(void(^)(void))block{
      NSLog(@"---执行A任务开始---");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}
- (void)requestB:(void(^)(void))block{
      NSLog(@"---执行B任务开始---");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}
- (void)requestC:(void(^)(void))block{
      NSLog(@"---执行C任务开始---");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
    
}
- (void)requestD:(void(^)(void))block{
    NSLog(@"---执行D任务开始---");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}
线程间通信
什么叫做线程间通信?

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信

线程间通信的体现

●1个线程传递数据给另1个线程 ●在1个线程中执行完特定任务后,转到另1个线程继续执行任务

线程间通信常用方法
  1. NSThread :一个线程传递数据给另一个线程
代码语言:javascript
复制
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
 
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
代码语言:javascript
复制
//点击屏幕开始执行下载方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self performSelectorInBackground:@selector(download) withObject:nil];
}
 
//下载图片
- (void)download
{    
    // 1.图片地址
    NSString *urlStr = @"http://d.jpg"; 
    NSURL *url = [NSURL URLWithString:urlStr]; 
    // 2.根据地址下载图片的二进制数据 
    NSData *data = [NSData dataWithContentsOfURL:url]; 
    // 3.设置图片
    UIImage *image = [UIImage imageWithData:data]; 
    // 4.回到主线程,刷新UI界面(为了线程安全)
    [self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:NO]; 
   // [self performSelector:@selector(downloadFinished:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; 
    
}
 
- (void)downloadFinished:(UIImage *)image
{
    self.imageView.image = image; 
    NSLog(@"downloadFinished---%@", [NSThread currentThread]);
}
  1. GCD :一个线程传递数据给另一个线程
代码语言:javascript
复制
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event    
{  
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"donwload---%@", [NSThread currentThread]);
        
        // 1.子线程下载图片
        NSURL *url = [NSURL URLWithString:@"http://d.jpg"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        UIImage *image = [UIImage imageWithData:data];
        
        // 2.回到主线程设置图片
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSLog(@"setting---%@ %@", [NSThread currentThread], image);
            
            [self.button setImage:image forState:UIControlStateNormal];
        });
    });
死锁

1、定义: 所谓死锁,通常指有两个线程T1和T2都卡住了,并等待对方完成某些操作。T1不能完成是因为它在等待T2完成。但T2也不能完成,因为它在等待T1完成。于是大家都完不成,就导致了死锁。

2、产生死锁的条件: 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。

首先来看一份导致死锁的典型代码:

代码语言:javascript
复制
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"执行任务1");
    
    dispatch_sync(dispatch_get_main_queue(), ^(void){
        NSLog(@"这里死锁了");
    });
    
    NSLog(@"执行任务3");
}

输出:执行任务1 后死锁了 原因:在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。 同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。 而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.09.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 进程
  • 线程
  • 任务
    • 同步
      • 异步
      • 队列
        • 串行队列(Serial Dispatch Queue):
          • 并发队列(Concurrent Dispatch Queue):
          • iOS中的多线程
            • 1. NSThread
            • 2.NSoperationQueue
            • GCD
              • dispatch_semaphore
                • Dispatch Semaphore 在实际开发中主要用于:
                  • dispatch_semaphore_signal:
                    • dispatch_semaphore_wait
                      • 卖火车票经典案例
                        • 使用GCD如何实现这个需求:A、B、C 三个任务并发,完成后执行任务 D?
                        • 线程间通信
                          • 什么叫做线程间通信?
                            • 线程间通信的体现
                              • 线程间通信常用方法
                                • 死锁
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档