每一个iOS应用程序中都有一个主线程用来更新UI界面、处理用户的触摸事件、解析网络下载的数据,因此不能把一些太耗时的操作(比如网络下载数据)放在主线程中执行,不然会造成主线程堵塞(出现界面卡死,防止界面假死),带来极坏的用户体验。iOS的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程异步编程是防止主线程堵塞,增加运行效率的最佳方法.

多线程技术:

1.异步下载数据,是多线程技术的一个比较常见的应用场景

2.多线程技术使用场景:app中有耗时的操作或功能(比如吐:1.客户端与服务端交互 2.从数据库中一次性读取大量数据 3.对大量数据的解析过程 等),需要在主线程之外,单独开辟一个新的线程(子线程/工作线程)来执行

iOS所支持的多线程方法主要有:

NSThread

NSOperation & NSOperationQueue

GCD

1.NSThread

1.1NSThread线程的创建

     NSThread线程的创建有三种方式:

    第一种:创建后台线程

    [self performSelectorInBackground:@selector(thread1Click:) withObject:@"线程1"];  performSelectorInBackground内部会创建一个线程 专门 执行调用 -thread1Click:

   第二种: 手动开启

   NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"线程2"]; 代码的意思是创建一个线程 执行调用-thread2Click:  object:就是前面函数的参数 还可以设置线程的名字 thread2.name=@"线程2"  .这种方式创建的线程需要手动启动[thread2 start];

   第三种:一旦创建 线程会立即执行

    创建代码:[NSThread detachNewThreadSelector:@selector(thread3Click:) toTarget:self withObject:@"线程3"];

1.2监听线程结束

我们这里采用通知的形式.首页我们应该先注册观察者.注册观察者的时候,内部会专门创建一个线程 监听其他线程有木有结束,线程结束,一般会发送一个结束通知  下面是创建通知中心:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillEnd:) name:NSThreadWillExitNotification object:nil];

当监听到线程结束的时候调用threadWillEnd:这个函数.最后不要忘记 在dealloc里面删除观察者喔.不管是在MRC或ARC中都是要删除的

1.3线程间的通信

这里直接上代码了

1 @interface ViewController ()
 2 {
 3     NSThread *_thread1;
 4 }
 5 @end
 6 
 7 @implementation ViewController
 8 
 9 - (void)viewDidLoad {
10     [super viewDidLoad];
11     _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"线程1"];
12     [_thread1 start];
13     
14     NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"线程2"];
15     [thread2 start];
16     
17     NSThread *thread3  = [[NSThread alloc] initWithTarget:self selector:@selector(thread3Click:) object:@"线程3"];
18     [thread3 start];
19 }
20 - (void)thread1Click:(id) obj {
21     NSLog(@"%s",__func__);
22     while (1) {
23         if ([[NSThread currentThread] isCancelled]) {
24             //要判断 当前这个线程 是否 被取消过(是否发送过取消信号)
25             //如果被取消过,那么我们可以让当前函数结束那么这个线程也就结束了
26             
27             //break;//结束循环
28             NSLog(@"子线程1即将结束");
29             //return;//返回 函数
30             [NSThread exit];//线程退出
31         }
32         
33         NSLog(@"thread1");
34         [NSThread sleepForTimeInterval:0.5];
35     }
36     NSLog(@"子线程1即将结束");
37 }
38 - (void)thread2Click:(id) obj {
39     NSLog(@"%s",__func__);
40     for (NSInteger i = 0; i < 10; i++) {
41         [NSThread sleepForTimeInterval:0.2];
42         NSLog(@"thread2:_i:%ld",i);
43     }
44     NSLog(@"子线程2即将结束");
45     //当 thread2 即将结束之后 通知 thread1结束
46     
47     [_thread1 cancel];// cancel 在这里 只是给_thread1 发送了一个cancel 信号,最终thread1的取消,取决于 thread1的,如果要取消thread1,那么需要在thread1执行调用的函数内部 进行判断
48 }
49 - (void)thread3Click:(id) obj {
50     NSLog(@"%s",__func__);
51     for (NSInteger i = 0; i < 10; i++) {
52         [NSThread sleepForTimeInterval:0.2];
53         NSLog(@"thread3:_i:%ld",i);
54     }
55     NSLog(@"子线程3即将结束");
56 }



 

1.4对线程加锁

有时候会出现一种情况,两个或多个线程同时对某一个比较重要的资源进行读写修改,当一个线程正在修改的时候,另一个线程也对该资源进行读写,这样就会照成错乱,数据就会失去可靠性.为了解决这个问题,我们采用线程锁的方式来防止这种情况发生.它的主要功能就是在一个线程对当前资源访问的时候,对该资源进行上锁,其他线程就不能在对该资源进行访问了,知道该线程访问结束,解开线程锁,其他资源才可以访问该资源.

当然要想加锁,必须要先创建一把线程锁喽,嘿嘿:

_lock = [[NSLock alloc] init];

- (void)thread1Click:(id)obj {
    [_lock lock];//加锁
    
    NSLog(@"thread1开始");
    for (NSInteger i = 0 ; i < 10; i++) {
        _cnt += 2;//想让 _cnt 连续+2
        NSLog(@"thread1_cnt:%ld",_cnt);
        [NSThread sleepForTimeInterval:0.2];
    }
    NSLog(@"thread1即将结束");
    
    [_lock unlock];//解锁  //访问资源结束 解锁
}
- (void)thread2Click:(id)obj {
    [_lock lock];
    NSLog(@"thread2开始");
    for (NSInteger i = 0 ; i < 10; i++) {
        _cnt -= 5;//让 _cnt连续-5
        NSLog(@"thread2_cnt:%ld",_cnt);
        [NSThread sleepForTimeInterval:0.2];
    }
    NSLog(@"thread2即将结束");
    [_lock unlock];
}



 

在使用线程锁的同时,也要防止产生死锁,就如下面的情况,就是一个线程1访问a,对a加锁,另一个线程2访问b对b加锁,与此同时,线程1也要对b进行访问,线程2也要对a资源访问,但是,线程1要访问b只有等线程2释放b之后才能访问,而线程2要访问a也只有当线程1解锁a之后才能访问,于是乎,彼此都在等对方解锁资源,这样就产生了死锁.应该避免这种问题的产生.

_lock1 lock

   访问 a

 xxxxxxx

 _lock2 lock

    访问 b

 _lock2 unlock

 访问 a

 _lock1 unlock

 -----------------

 _lock2 lock

 访问b

 xxxxxxx

 xxxxxx

  _lock1 lock

    访问 a

 _lock1 unlock

 访问 b

  _lock2 unlock

 此外,用@synchronized (self){}也可以达到加锁的效果



- (void)thread1Click:(id)obj {
    
    // @synchronized (self){} 类似于 加锁和解锁过程
    
    @synchronized(self) { //使线程 对当前 对象进行操作 时,同步进行 ,阻塞线程
        //跟加锁原理 是一样的 执行 @synchronized(self)  会判断有没有加锁,加过锁那么阻塞,没有加锁就继续执行
        NSLog(@"thread1开始");
        for (NSInteger i = 0 ; i < 10; i++) {
            _cnt += 2;//想让 _cnt 连续+2
            NSLog(@"thread1_cnt:%ld",_cnt);
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"thread1即将结束");
    }
}
- (void)thread2Click:(id)obj {
    @synchronized(self) {
        NSLog(@"thread2开始");
        for (NSInteger i = 0 ; i < 10; i++) {
            _cnt -= 5;//让 _cnt连续-5
            NSLog(@"thread2_cnt:%ld",_cnt);
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"thread2即将结束");
    }
}