初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全)

初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全)_第1张图片
![多线程.2.png](http://upload-images.jianshu.io/upload_images/1803560-898b71ebec4c2912.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在说多线程之前,首先要明白线程,进程

线程和进程

进程:
  • 系统中正在运行的程序,称为一个进程
  • 每个进程,都在自己独立的内存空间运行,并且进程之间互不影响。
线程:
  • 进程的所有任务,都是在线程执行的,可以说它是进程的基本执行单元,是程序中独立运行的代码段。
  • 主线程:每个正在运行的程序(即进程),至少包含一个线程,这个线程叫做主线程
  • 单线程:只有一个主线程的程序,称作单线程程序

多线程

顾名思义,多线程就是拥有多个线程的程序,iOS用户可以根据需要在一段进程中开辟新的线程,这些线程相对于主线程来说被称为子线程,子线程和主线程可以并行(同时)执行不同的任务。

原理:

1.同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)
2.多线程并发(同时执行,其实是CPU快速地在多条线程之间)调度(切换)
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
4.多核CPU,每个核心都可以同时处理不同任务,从而真正达到了多线程并发执行任务
5.线程非常多:
1> CPU会在N多条线程之间调度,CPU会累死,消耗大量的CPU资源
2> 每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程优点

1.能适当提高程序的执行效率
2.能适当提高资源利用率(CPU、内存利用率)

多线程缺点

1.开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
2.线程越多,CPU在调度线程上的开销就越大
3.程序设计更加复杂:比如线程之间的通信,多线程的数据共享

多线程在iOS开发中的应用

1.什么是主线程
1> 一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"
2> 每一个进程都有一个独立的主线程
2.主线程的主要作用
1> 显示\刷新UI界面
2> 处理UI事件 (比如点击事件、滚动事件、拖拽事件等)
3.主线程的使用注意
1> 别将比较耗时的操作放到主线程中
2> 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的坏体验
例如:首先随便在viewController上拖一个textView或者是写一个button的点击事件,然后在viewDidLoad里写上一个稍微耗时的操作:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (int i = 0; i<10000; i++) {
        NSLog(@"---------%d", i);
    }
}

那么当点击页面后快速的滑动textView,这时会发现,它是没反应的

初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全)_第2张图片
阻塞主线程.gif

一直等到for循环走完之后,才会有反应,继续执行操作,这就是阻塞主线程。

多线程的实现方案

技术方案 简介 语言 线程声明周期 使用频率
pathread 一套通用的多线程API
适用于Unix\Linux\Windows等系统
跨平台\可移植
使用难度大
C 程序员管理 几乎不用
NSThread 使用更加面向对象
简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD 旨在替代NSThread等线程技术
充分利用设备的多核
C 自动管理 经常使用
NSOperation 基于GCD(底层是GCD)
比GCD多了一些简单实用的功能
实用更加面向对象
OC 自动管理 经常使用

pthread的创建:

    //创建
    pthread_t myRestrict;
    pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data){
    NSLog(@"*******%@",[NSThread currentThread]);
    return NULL;
}

因为不常用,不做多解释,从打印结果可以看出,开辟了子线程

NSThread创建和启动线程的3种方式:

#pragma mark ---- 1.使用NSThread手动开辟子线程 ----
//    1、创建线程(NSThread)
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downLoad:) object:"http//:abcde.png"];
//线程的名字
thread.name = @"下载线程";
//    2、开启线程
        [thread start];
//    3、关闭线程(可写可不写)
        [NSThread exit];
//    4、取消线程(实际上就是做了个标记,表示被取消了)
        [thread cancel];
#pragma mark ---- 2.使用NSThread自动开辟子线程(无需手动) ----
 [NSThread detachNewThreadSelector:@selector(downLoad:) toTarget:self withObject:"http//:abcde.png"];
[thread start];
#pragma mark ---- 3.使用NSObject开辟子线程(在后台执行某个方法),也叫隐式创建(无需手动) ----
/*
 [self performSelector:@selector(downLoad:) withObject:@"http//:abcde.png"];
//相当于下面这段代码
   [self downLoad:@"http//:abcde.png"];
#因此,这个方法是不开辟子线程的
*/
[self performSelectorInBackground:@selector(downLoad:) withObject:@"http//:abcde.png"];
//这个方法开辟子线程

常见的方法:

1> 获得当前线程
+ (NSThread *)currentThread;
 2> 获得主线程
+ (NSThread *)mainThread;
 3>睡眠(暂停)线程
 +(void)sleepUntilDate:(NSDate *)date;
 + (void)sleepForTimeInterval:(NSTimeInterval)ti
------
//回到主线程刷新数据
    [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];

-(void)reloadData{
    //刷新UI
    if ([NSThread isMainThread]) {
        //这里做一些需要的操作
    }
}

利用上面的一些方法,可以做一些简单的多线程之间的通信,例如开辟子线程下载图片,回到主线程刷新,这里不再操作

多线程的安全隐患

1.资源共享
1> 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
2> 比如多个线程访问同一个对象、同一个变量、同一个文件
2.当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

安全隐患解决--加锁(互斥锁)

1.互斥锁的优缺点
1> 优点:能有效的防止多线程抢夺资源造成的数据安全问题
2> 缺点:需要消耗大量的CPU资源
2.互斥锁的使用前提:多条线程抢夺同一块资源
线程同步的意思是:多条线程实在同一条线上执行(按顺序的执行任务)
互斥锁,就是使用了线程同步技术
单纯的靠文字叙述可能会比较难理解,可以参考以下例子:写一个卖票的简单程序

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.leftTicketCount = 50;
    
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread1.name = @"1号窗口";
    
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread2.name = @"2号窗口";
    
    self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread3.name = @"3号窗口";
    

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}

/**
 *  卖票
 */
- (void)saleTicket
{
    while (1) {
      
            int count = self.leftTicketCount;
            if (count > 0) {
                [NSThread sleepForTimeInterval:0.05];
                
                self.leftTicketCount = count - 1;
                
                NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
            } else {
                return; // 退出循环
            }
    }
}

运行结果:


初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全)_第3张图片
卖票.png

这就是多线程访问同一块资源造成的
加锁:在刚才的方法里加上@synchronized

- (void)saleTicket
{
    while (1) {
        // ()小括号里面放的是锁对象
        @synchronized(self) { // 开始加锁
            int count = self.leftTicketCount;
            if (count > 0) {
                [NSThread sleepForTimeInterval:0.05];
                
                self.leftTicketCount = count - 1;
                
                NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
            } else {
                return; // 退出循环
            }
        } // 解锁
    }
}


GCD的内容较多,我会在下篇文章里详细介绍

NSOperation

1.首先,它是一个抽象类,所以执行任务的是它的子类:NSInvocationOperation和NSBlockOperation(还有:自定义子类继承NSOperation,实现内部相应的⽅法)。这两个子类,相当于一个方法选择器“prefromSelector()”,由它俩本身发起的任务,并不是在子线程中执行。
2.NSOperation和它的子类,本身并不会进行线程的创建,所以,在他们的任务方法中打印当前线程,显示为主线程
3.NSOperation和它的子类,只是一个操作,本身没有主线程、子线程之分,可以在任何线程中使用,通常和NSOperationQueue结合使用。

NSOperationQueue

1、一个NSOperationQueue操作队列,就相当于一个线程管理器,将NSOperation和子类的对象放入队列中,然后由队列负责派发任务,所以NSOperationQueue并不是一个线程。但是,你可以设置队列中运行的线程的数量

---- 优点 ----
不需要手动关联线程,只需要把精力放在自己要执行的操作上面。

---- 缺点 ----
它是基于OC对象的,那么相对于C函数来说效率要低

配合使用NSOperation和NSOperationQueue也能实现多线程编程


//子类一:NSInvocationOperation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
//operation在单独使用的时候,需要手动调用开启方法
  [operation start];
#operation直接调用start,是同步执行(在当前线程中执行操作),说白了就相当于[self hehehe];并没有什么卵用
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
[queue addOperation:operation];//********注意:如果搭配了NSOperationQueue中的add方法创建多线程的话,就不需要使用satrt方法,否则会崩溃
//子类二:NSBlockOperation
//任务数量 > 1,才会开始异步执行
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block:%@",[NSThread currentThread]);
        NSLog(@"block:%@",[NSThread mainThread]);
        NSLog(@"block:%d",[NSThread isMainThread]);
        NSLog(@"*********");
    }];
//开启
    [blockOperation start];
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block:%@",[NSThread currentThread]);
        NSLog(@"block:%@",[NSThread mainThread]);
        NSLog(@"block:%d",[NSThread isMainThread]);
        NSLog(@"*********");
    }];
[queue addOperation:blockOperation];
//简单的做法:
 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
        NSLog(@"*******%@", [NSThread currentThread]);
    }];
#------设置最大并发数-----
 //当设置最大并发数为1时 :也可叫做串行,顺序执行
    //当设置最大并发数大于1时:叫并行,多条通道同时进行各自的任务,互不影响。
    queue.maxConcurrentOperationCount = 3;

除了GCD,其他两种主要的创建多线程的方法基本已经介绍完了,如果觉得有什么问题或者错误,欢迎留言或发简信!!

你可能感兴趣的:(初学iOS----多线程(一:NSThread、pathread、NSOperation、线程安全))