美文网首页
iOS之多线程-1

iOS之多线程-1

作者: 请输入账号名 | 来源:发表于2017-08-06 23:52 被阅读36次

    预备知识:

    1.进程与线程

    进程:进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。(进程的查看可以在活动监视器去查看)
    线程:1个进程要想执行任务,必须得有线程,每个程序至少要有一条线程。一个进程中所有的任务都是在线程中执行的。如果一个线程有多个任务需要处理,这就需要一一处理这些任务,这就是是线程的串行。

    线程与进程的比较:

    • 线程是CPU调用(执行任务)的最小单位。
    • 进程是CPU分配资源和调度的单位。
    • 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程。
    • 同一个进程内的线程共享进程的资源。

    2.多线程

    多线程:1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。
    串行:如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务(也就是说线程中同一时间段只能做一件事情)。

    串行图解

    并行:多个线程都是执行,就是叫并行。


    多线程图解

    多线程原理(单个CPU的情况下):
    同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
    线程也不是开的越多越好,开得较多的话反而会降低效率,线程一般就3~5条最好。

    3.主线程

    主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。

    主线程的作用:

    • 显示\刷新UI界面
    • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)

    主线程的使用注意:

    • 将耗时操作尽量不放在主线程中执行。(文件上传等都是耗时操作)
    • 耗时操作会卡住主线程,会造成UI界面的卡顿,造成不好的用户体验。

    由于在线程中是串行处理的,如果一个耗时操作放在主线程中执行,如果用户点击了按钮,而和UI相关的都是放在主线程的,所以只有当耗时操作完成后才会去执行按钮的点击事件,这样就会造成卡顿,用户体验不好。
    耗时操作优化处理的方式:将耗时操作放在子线程(非主线程,后台线程)中进行处理。
    由于子线程和主线程是同时执行的,这样耗时操作放在子线程中执行的,在点击按钮的时候,就不会造成卡顿的现象出现。

    4.多线程的实现方案

    多线程的实现方案
    • pthread
    
    - (IBAction)btnClickAction:(id)sender {
        // 创建线程对象
        pthread_t thread;
        
        // 创建线程
        
        /**
         创建线程
    
         @param _Nonnull#> 线程对象传递地址 description#>
         @param _Nullable#> 线程的属性(优先级等) description#>
         @param _Nonnull 指向函数的指针
         @return 函数需要传递的函数
         */
        pthread_create(&thread, nil, task, NULL); // 创建一条线程,如果要创建多个线程就可以将上面的代码copy下就可以了。
     
        pthread_t threadB;
        pthread_create(&threadB, nil, task, NULL); // 第二个线程
        
        pthread_equal(thread, threadB); // 判断两个线程是不是相等。
    }
    
    
    void * task(void *parameter) {
        NSLog(@"%@ ---- ", [NSThread currentThread]);
        // 可以将耗时操作放在这里执行。
        return NULL;
    }
    
    // 打印结果
    2017-08-06 22:47:48.854 1-pthread[4855:328489] <NSThread: 0x608000266d40>{number = 3, name = (null)} ---- 
    2017-08-06 22:47:48.854 1-pthread[4855:328490] <NSThread: 0x608000265b80>{number = 4, name = (null)} ---- 
    
    • NSThread

    创建线程的几个方法

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self creatNewThreadMethod];
    }
    
    - (void)creatNewThreadMethod {
        // 开启一条后台线程
        [self performSelectorInBackground:@selector(run:) withObject:@"开启后台子线程"];
    }
    
    - (void)creatDetachNewThreadMethod {
        // 创建分离子线程->会自动启动子线程
        [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    }
    
    - (void)creatNewThread {
        
        // 创建线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
        // 启动线程,默认是暂停状态的
        [thread start];
        
    }
    
    - (void)run:(NSString *)param {
        NSLog(@"---run--%@", [NSThread currentThread]);
    }
    

    设置线程的名字以及优先级等属性

    // 创建多个线程->给每个线程命名以及优先级
    - (void)creatMutilThread {
        NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
        thread1.name = @"thread 1";
        thread1.threadPriority = 0.6; // 设置优先级(0.0~1.0,默认的优先级是0.5)
        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
        thread2.name = @"thread 2";
        thread2.threadPriority = 0.3;
        NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
        thread3.name = @"thread 3";
        thread3.threadPriority = 1.0;
        [thread1 start];
        [thread2 start];
        [thread3 start];
    }
    
    - (void)run:(NSString *)param {
        for (int i = 0; i < 10; i++) {
            NSLog(@"---run--%@", [NSThread currentThread].name);
        }
    }
    // 打印结果
    2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.487 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.488 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.490 2-NSThread基本使用[5136:356390] ---run--thread 3
    2017-08-06 23:11:51.489 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.492 2-NSThread基本使用[5136:356388] ---run--thread 1
    2017-08-06 23:11:51.493 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.495 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.496 2-NSThread基本使用[5136:356389] ---run--thread 2
    2017-08-06 23:11:51.497 2-NSThread基本使用[5136:356389] ---run--thread 2
    打印次数越多,打印的数字就越趋于准确的值。
    
    • 线程状态
    线程状态图解

    线程的控制状态等操作:

    // 启动
    - (void)start;
    // 阻塞(暂停)线程
    + (void)sleepUntilDate:(NSDate *)date;
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    // 强制停止线程
    + (void)exit; // 一旦线程停止(死亡)了,就不能再次开启任务
    // 和break不一致,表示任务执行完毕后才退出的。
    

    5.多线程安全隐患

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


    同一时间存取钱

    以上的图展示存取钱是同时处理的,造成了最终的钱变少了,造成了数据的不当处理。
    处理的方案
    苹果的文档说明的解决方案就是加上一个互斥锁,在访问数据的时候加上互斥锁,只要次个线程才能访问处理,在访问结束的时候打开把互斥锁打开,然后另一个线程进行访问,同样加上互斥锁,循环如此,这样就解决了数据安全的隐患。


    互斥锁
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.totalCount = 100;
        self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
        self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
        self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
        self.threadA.name = @"售票员A";
        self.threadB.name = @"售票员B";
        self.threadC.name = @"售票员C";
        [self.threadA start];
        [self.threadB start];
        [self.threadC start];
    }
    
    - (void)saleTicket {
        while (1) {
            // 锁必须是全局唯一的。
            // 1.注意点,不能随便能乱加,加锁的位置要注意
            // 2.加锁的前提条件,--多线程共享同一个资源
            // 3.注意加锁是需要代价的--需要耗费性能的
            // 4.加锁的结果是造成线程同步。->ABC ABC ABC的循环执行任务
            @synchronized (self) {
                NSInteger count = self.totalCount;
                if (count > 0) {
                    self.totalCount = count - 1;
                    // 卖出去一张票,还剩下多少张
                    NSLog(@"%@卖出去一张票, 还剩下%ld张", [NSThread currentThread].name, self.totalCount);
                } else {
                    NSLog(@"没票了");
                    break;
                }
            }
        }  
    }
    

    6.原子和非原子性

    在oc中定义属性时有nonatomic和atomic两种选择;
    atomic:原子属性,为setter方法加锁(默认就是atomic),意味着线程是安全的。
    nonatomic:非原子属性,不会为setter方法加锁。

    7.线程间的通信

    有的时候,同一个进程跑了多个线程,有的子线程的输出的结果是另一个子线程的输入,这就需要两个子线程中进行一种通信。所以线程往往不是孤立存在的,多个线程之间需要经常进行通信。
    线程之间的通信的方法:

    // 回到主线程
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
    // 回到线程对象,可以回到主线程也可以回到子线程
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
    

    例子:下载图片并展示
    如果不做线程的处理,

    
    - (void)wjDownloadImageCountTime {
        NSDate *start = [NSDate date]; // 获得当前时间
        NSURL *url = [NSURL URLWithString:@"http://pic1.win4000.com/wallpaper/d/573d37cc14e3c.jpg"];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:imageData];
        self.imageView.image = [img imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
        NSDate *end = [NSDate date];
        NSLog(@"count time is: %f", [end timeIntervalSinceDate:start]);
    }
    

    以上能够完成图片的下载,图片的展示也能成功,但是一旦UI上的图片比较多的话,就会阻塞主线程,所以不采取。
    优化的处理

    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 放在子线程去下载图片
        [NSThread detachNewThreadSelector:@selector(wjDownloadImage) toTarget:self withObject:nil];
    
    }
    
    - (void)wjDownloadImage {
        // http://img4.imgtn.bdimg.com/it/u=816246739,294523191&fm=214&gp=0.jpg
        // 1.下载图片的url
        NSURL *url = [NSURL URLWithString:@"http://pic1.win4000.com/wallpaper/d/573d37cc14e3c.jpg"];
        // 2.下载图片到本地->二进制数据 -> 耗时操作
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        // 3.将二进制数据转为图片
        UIImage *img = [UIImage imageWithData:imageData];
        // 4.显示UI->回到主线程去刷新UI界面
        [self performSelectorOnMainThread:@selector(wjShowImage:) withObject:img waitUntilDone:YES];
    //  简便方法,就不需要写下面的那个方法了。
    //    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:img waitUntilDone:YES];
    }
    
    - (void)wjShowImage:(UIImage *)img {
        self.imageView.image = img;
    }
    

    相关文章

      网友评论

          本文标题:iOS之多线程-1

          本文链接:https://www.haomeiwen.com/subject/spvnlxtx.html