iOS 多线程总结

热门标签

,

特别声明:文章多为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

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

进程

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

线程

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

任务

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

同步

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

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");
异步

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

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):

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

串行队列

// 串行队列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)函数下才有效。

// 串行队列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(@"结束");

打印

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

手动开启线程

 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];

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

[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];
2.NSoperationQueue
 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];

打印

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

卖火车票经典案例
- (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中的任务。

 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任务结束---");
        }];
    });
- (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 :一个线程传递数据给另一个线程
- (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);
//点击屏幕开始执行下载方法
- (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 :一个线程传递数据给另一个线程
- (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) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。

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

- (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和这个任务就形成了相互循环等待,就造成了死锁。

标签:

未经允许不得转载:作者:SheaYang, 转载或复制请以 超链接形式 并注明出处 技术狗|博客
原文地址:《iOS 多线程总结》 发布于2019-10-09

分享到:
赞(0)

评论 抢沙发

2 + 9 =


iOS 多线程总结

长按图片转发给朋友

Vieu4.0主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录