iOS 多线程- pThread和NSThread

本文内容:
多线程的优缺点
多线程实现技术方案
如何使用pThread实现多线程
如何使用NSthread执行任务、设置优先级
线程间如何通信
如何保证线程安全
iOS多线程demo地址

1.进程、线程

线程是进程的基本执行单元
进程的所有任务都在线程中执行

2.多线程在开发中的优缺点

2.1、优点

  1. 简化了编程模型
  2. 更加轻量级
  3. 提高了执行效率
  4. 提高了资源利用率

2.2缺点

  1. 增加了程序设计的复杂性
  2. 占用内存空间
  3. 增加了CPU的调度开销

3.多线程实现技术方案

  1. pThread:跨平台,适用于多种操作系统,可移植性强,是一套纯C语言的通用API,线程的生命周期需要程序员自己管理,使用难度较大,开发中不常使用。
  2. NSThread:基于OC语言的API,面向对象操作,线程的生命周期需要程序员自己管理,操作简单易用,在开发中偶尔使用。
  3. GCD基于C语言的API,充分利用设备多核,旨在替换NSThread技术,线程的生命周期系统自动管理,在开发中经常使用。iOS 多线程-GCD
  4. NSOperation基于OC语言的API,底层是GCD,增加了一些简单易用的功能,面向对象操作,线程的生命周期系统自动管理,在开发中经常使用。iOS 多线程-NSOperation + NSOperationQueue

3.1 pThread

本文只是讲解pThread的简单使用,更多讲解还请Google

引入头文件

#import 

创建线程

 pthread_t thread;
 NSString *param = @"i am pthread param";
 pthread_create(&thread, NULL, run, (__bridge void *)(param));
    

pthread_create有四个参数

  1. 代表线程
  2. 线程属性,设置为NULL
  3. 线程开启后,回调用的函数,在里面进行耗时操作
  4. 回调函数的参数

回调函数run


void *run (void *param){
    NSString *str = (__bridge NSString *)(param);
    for (int i = 0; i < 3; i ++) {
        NSLog(@"i = %d,str = %@",i,str);
        sleep(1);
    }
    return NULL;
}

运行结果

2018-12-26 14:29:37.064043+0800 Thread[3051:91506] 我在主线程执行
2018-12-26 14:29:37.064344+0800 Thread[3051:91572] i = 0,str = i am pthread param
2018-12-26 14:29:38.067615+0800 Thread[3051:91572] i = 1,str = i am pthread param
2018-12-26 14:29:39.072235+0800 Thread[3051:91572] i = 2,str = i am pthread param

:前面的编号代表进程编号,一个APP的就是一个进程,进程编号总是一致的;
:后面的编号代表线程的编号, 91506代表主线程的编号,91572代表子线程的编号,编号不一致是因为我们创建了一个线程,run函数在子线程中执行

注意:每次运行进程、线程编号都不一样

3.2 NSthread

3.2.1生命周期

iOS 多线程- pThread和NSThread_第1张图片
NSthread生命周期
  1. 如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态;
  2. 如果CPU在运行当前线程对象的时候调用了sleep方法或者等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时或者得到同步锁,则回到就绪状态;
  3. 如果CPU在运行当前线程对象的时候线程任务执行完毕或者异常强制退出,则当前线程对象进入死亡状态。

3.2.2. 创建线程

创建线程有三种常用方式

  1. alloc init 方式创建线程后,可以拿到线程对象,调用
    start方法启动线程,因为可以拿到线程对象,所以可以设置线程名字、优先级,优先级设置在0-1之间,优先级高,优先执行

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
 //设置名字
 [thread setName:@"thread"];
 //设置优先级,优先级在0 - 1之间
 [thread setThreadPriority:0.2];
[thread start];
  1. detachNewThreadSelector方式创建线程后,自动启动线程,这种方式拿不到线程对象
[NSThread detachNewThreadSelector:@selector(runNSthread) toTarget:self withObject:nil];
  1. performSelectorInBackground方式创建线程后,自动启动线程,这种方式拿不到线程对象
  [self performSelectorInBackground:@selector(runNSthread) withObject:nil];

runNSthread函数

 - (void)runNSthread{
    NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
    for (int i = 0; i <= 2; i ++) {
        NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
        [NSThread sleepForTimeInterval:1.0];
    }
}

输出结果:

2019-01-07 16:14:41.180562+0800 Thread[1769:90933] 我在主线程执行-----NSthread
2019-01-07 16:14:41.183317+0800 Thread[1769:90990] name = thread,我在子线程执行-----NSthread
2019-01-07 16:14:41.183623+0800 Thread[1769:90990] name = thread,i = 0
2019-01-07 16:14:42.185550+0800 Thread[1769:90990] name = thread,i = 1
2019-01-07 16:14:43.186270+0800 Thread[1769:90990] name = thread,i = 2

3.2.3. 线程状态

  1. 启动线程
  2. 阻塞线程
  3. 结束线程
  4. 取消线程

3.2.3.1启动线程

- (void)start;

3.2.3.2阻塞线程

// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

3.2.3.3结束线程

+ (void)exit;

当到达停止条件时,线程强制退出,进入死亡状态

- (void)runNSthread{
    NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
    for (int i = 0; i <= 2; i ++) {
        NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
        if (i == 1) {
            [NSThread exit];
        }
    }
}

输出结果:

2019-01-07 16:36:31.807896+0800 Thread[1876:99814] 我在主线程执行-----NSthread
2019-01-07 16:36:31.810076+0800 Thread[1876:99918] name = thread,我在子线程执行-----NSthread
2019-01-07 16:36:31.810366+0800 Thread[1876:99918] name = thread,i = 0
2019-01-07 16:36:31.810474+0800 Thread[1876:99918] name = thread,i = 1

3.2.3.4取消线程

- (void)cancel

调用cancel方法,仅仅是把thread.canceled = YES;,线程依旧会正常执行,需要搭配thread.isCancelled属性使用,当 thread.isCancelled == YES时,先清理线程,再中断线程。

直接调用cancel方法

- (void)runNSthread{
    NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
    for (int i = 0; i <= 2; i ++) {
        NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
        if (i == 1) {
            [[NSThread currentThread] cancel];
        }
    }
}

输出结果:调用cancel方法后,继续执行

2019-01-07 16:46:25.966691+0800 Thread[1956:103567] 我在主线程执行-----NSthread
2019-01-07 16:46:25.968778+0800 Thread[1956:103723] name = thread,我在子线程执行-----NSthread
2019-01-07 16:46:25.969424+0800 Thread[1956:103723] name = thread,i = 0
2019-01-07 16:46:25.969564+0800 Thread[1956:103723] name = thread,i = 1
2019-01-07 16:46:25.969748+0800 Thread[1956:103723] name = thread,i = 2

调用cancel方法 + thread.isCancelled属性

- (void)runNSthread{
   NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
   for (int i = 0; i <= 2; i ++) {
       if ([NSThread currentThread].isCancelled) {
           return;
       }
       NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
       if (i == 1) {
           [[NSThread currentThread] cancel];
       }
   }
}

输出结果:在i==1时,进程成功取消

2019-01-07 16:49:46.649696+0800 Thread[1989:105064] 我在主线程执行-----NSthread
2019-01-07 16:49:46.654734+0800 Thread[1989:105151] name = thread,我在子线程执行-----NSthread
2019-01-07 16:49:46.655323+0800 Thread[1989:105151] name = thread,i = 0
2019-01-07 16:49:46.657510+0800 Thread[1989:105151] name = thread,i = 1

3.2.4.线程间通信

在iOS开发工程中,我们一般在主线程中进行UI刷新,如:点击、拖拽、滚动事件,耗时操作放在其他线程中,而当耗时操作结束后,回到主线程,就需要用到线程间的通信。

创建两个线程,分别设置名字和优先级,thread2的优先级高于threadthreadi== 1取消线程,运行结束后切回主线程

- (void)clickNSthread{
    NSLog(@"我在主线程执行-----NSthread");
    //1、通过alloc initc创建并执行线程,可以拿到thread对象
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
    //设置名字
    [thread setName:@"thread"];
    //设置优先级,优先级在0 - 1之间
    [thread setThreadPriority:0.2];
    [thread start];

    NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
    [thread2 setName:@"thread2"];
    [thread2 setThreadPriority:0.5];
    [thread2 start];
}


- (void)runNSthread{
    NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
    for (int i = 0; i <= 2; i ++) {
        if ([NSThread currentThread].isCancelled) {
            return;
        }
        NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
        if (i == 1 && [[NSThread currentThread].name isEqualToString:@"thread"]) {
            [[NSThread currentThread] cancel];
        }
        if (i == 2) {
            //回到主线程
            [self performSelectorOnMainThread:@selector(runMianThread) withObject:nil waitUntilDone:YES];
        }
    }
}


- (void)runMianThread{
    NSLog(@"回到主线程执行-----NSthread");
}

输出结果:

  1. thread2thread分别开启了一个线程,
  2. thread2优先于thread执行,因为优先级高
  3. threadi== 1后,线程取消
  4. thread2任务完成后回到主线程,完成线程间通信
2019-01-07 16:57:45.736291+0800 Thread[2065:108373] 我在主线程执行-----NSthread
2019-01-07 16:57:45.737050+0800 Thread[2065:108435] name = thread2,我在子线程执行-----NSthread
2019-01-07 16:57:45.737657+0800 Thread[2065:108435] name = thread2,i = 0
2019-01-07 16:57:45.738507+0800 Thread[2065:108435] name = thread2,i = 1
2019-01-07 16:57:45.739965+0800 Thread[2065:108435] name = thread2,i = 2
2019-01-07 16:57:45.741878+0800 Thread[2065:108373] 回到主线程执行-----NSthread
2019-01-07 16:57:45.745420+0800 Thread[2065:108434] name = thread,我在子线程执行-----NSthread
2019-01-07 16:57:45.745743+0800 Thread[2065:108434] name = thread,i = 0
2019-01-07 16:57:45.745879+0800 Thread[2065:108434] name = thread,i = 1

相关方法:

//在主线程调用
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes
//在子线程中调用
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    // equivalent to the first method with kCFRunLoopCommonModes

3.2.5.线程安全与线程同步

线程安全:在多个线程中同时访问并操作同一对象时,运行结果与预期的值相同就是线程安全。
线程安全问题都是由全局变量静态变量引起的,若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

实现线程安全需要加锁,加锁方式有很多种,深入理解iOS开发中的锁,请点击这里
这里主要讲三种方式

  1. synchronized
  2. NSLockNSConditionLockNSRecursiveLockNSCondition加锁方式都差不多,都是实现NSLocking协议,调用- (void)lock;- (void)unlock;实现线程安全;
  3. dispatch_semaphore(在GCD文章中有详细解释)。

例子:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。创建一个TicketManager,在外面调用startToSale方法

#import "TicketManager.h"


#define Total 50

@interface TicketManager ()

@property (nonatomic,assign) NSInteger tickets;//剩余票数
@property (nonatomic,assign) NSInteger saleCount;//卖出票数

@property (nonatomic,strong) NSThread *threadBJ;//北京票点
@property (nonatomic,strong) NSThread *threadSH;//上海票点
/*
 NSLock、NSConditionLock、NSRecursiveLock、NSCondition加锁方式都一样,都是实现NSLocking协议
 
 */
@property (nonatomic,strong) NSCondition *condition;
@property (nonatomic , strong) dispatch_semaphore_t semaphore;

@end

@implementation TicketManager

- (instancetype)init{
    self = [super init];
    if (self) {
        _condition = [[NSCondition alloc]init];
        _semaphore = dispatch_semaphore_create(1);
        
        _tickets = Total;
        _threadBJ = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
        _threadSH = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
        
        [_threadBJ setName:@"北京"];
        [_threadSH setName:@"上海"];
    }
    return self;
}

- (void)sale{
    while (1) {
        //1、synchronized
        @synchronized (self) {
            if (self.tickets > 0 ) {
                [NSThread sleepForTimeInterval:0.1];
                self.tickets --;
                self.saleCount = Total - self.tickets;
                NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
            }else{
                break;//一定要break,不然就会死循环
            }
        }
//        2、NSCondition
//        [self.condition lock];
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            break;
//        }
//        [self.condition unlock];
//
        //3、dispatch_semaphore方式
//        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            dispatch_semaphore_signal(self.semaphore);
//            break;
//        }
//        dispatch_semaphore_signal(self.semaphore);
    }
}

- (void)startToSale{
    [_threadSH start];
    [_threadBJ start];
}
@end

如果不加锁的话,则屏蔽掉加锁代码 @synchronized (self) {}

输出结果:出现资源抢夺问题,票数卖出不正常

2019-01-07 17:22:32.606457+0800 Thread[2261:116831] 上海 , 卖出 = 1,剩余= 49
2019-01-07 17:22:32.606440+0800 Thread[2261:116832] 北京 , 卖出 = 1,剩余= 49
2019-01-07 17:22:32.710909+0800 Thread[2261:116831] 上海 , 卖出 = 2,剩余= 48
2019-01-07 17:22:32.710910+0800 Thread[2261:116832] 北京 , 卖出 = 2,剩余= 48
2019-01-07 17:22:32.812270+0800 Thread[2261:116832] 北京 , 卖出 = 4,剩余= 46
2019-01-07 17:22:32.812469+0800 Thread[2261:116831] 上海 , 卖出 = 3,剩余= 47

加锁后输出结果:没有资源抢夺的问题,票数正常卖出。

018-12-29 16:06:21.096548+0800 Thread[16887:391801] 上海 , 卖出 = 1,剩余= 49
2018-12-29 16:06:21.197614+0800 Thread[16887:391802] 北京 , 卖出 = 2,剩余= 48
2018-12-29 16:06:21.301553+0800 Thread[16887:391801] 上海 , 卖出 = 3,剩余= 47
2018-12-29 16:06:21.407615+0800 Thread[16887:391802] 北京 , 卖出 = 4,剩余= 46
2018-12-29 16:06:21.509136+0800 Thread[16887:391801] 上海 , 卖出 = 5,剩余= 45
2018-12-29 16:06:21.613467+0800 Thread[16887:391802] 北京 , 卖出 = 6,剩余= 44
2018-12-29 16:06:21.715522+0800 Thread[16887:391801] 上海 , 卖出 = 7,剩余= 43
2018-12-29 16:06:21.819181+0800 Thread[16887:391802] 北京 , 卖出 = 8,剩余= 42
2018-12-29 16:06:21.921224+0800 Thread[16887:391801] 上海 , 卖出 = 9,剩余= 41
2018-12-29 16:06:22.027030+0800 Thread[16887:391802] 北京 , 卖出 = 10,剩余= 40
2018-12-29 16:06:22.130868+0800 Thread[16887:391801] 上海 , 卖出 = 11,剩余= 39
2018-12-29 16:06:22.233431+0800 Thread[16887:391802] 北京 , 卖出 = 12,剩余= 38
2018-12-29 16:06:22.337925+0800 Thread[16887:391801] 上海 , 卖出 = 13,剩余= 37
2018-12-29 16:06:22.439501+0800 Thread[16887:391802] 北京 , 卖出 = 14,剩余= 36
2018-12-29 16:06:22.542079+0800 Thread[16887:391801] 上海 , 卖出 = 15,剩余= 35
2018-12-29 16:06:22.647700+0800 Thread[16887:391802] 北京 , 卖出 = 16,剩余= 34
2018-12-29 16:06:22.752251+0800 Thread[16887:391801] 上海 , 卖出 = 17,剩余= 33
2018-12-29 16:06:22.855627+0800 Thread[16887:391802] 北京 , 卖出 = 18,剩余= 32
2018-12-29 16:06:22.960312+0800 Thread[16887:391801] 上海 , 卖出 = 19,剩余= 31
2018-12-29 16:06:23.065476+0800 Thread[16887:391802] 北京 , 卖出 = 20,剩余= 30
2018-12-29 16:06:23.166326+0800 Thread[16887:391801] 上海 , 卖出 = 21,剩余= 29
2018-12-29 16:06:23.270858+0800 Thread[16887:391802] 北京 , 卖出 = 22,剩余= 28
2018-12-29 16:06:23.373545+0800 Thread[16887:391801] 上海 , 卖出 = 23,剩余= 27
2018-12-29 16:06:23.477072+0800 Thread[16887:391802] 北京 , 卖出 = 24,剩余= 26
2018-12-29 16:06:23.577959+0800 Thread[16887:391801] 上海 , 卖出 = 25,剩余= 25
2018-12-29 16:06:23.682754+0800 Thread[16887:391802] 北京 , 卖出 = 26,剩余= 24
2018-12-29 16:06:23.787569+0800 Thread[16887:391801] 上海 , 卖出 = 27,剩余= 23
2018-12-29 16:06:23.892799+0800 Thread[16887:391802] 北京 , 卖出 = 28,剩余= 22
2018-12-29 16:06:23.995028+0800 Thread[16887:391801] 上海 , 卖出 = 29,剩余= 21
2018-12-29 16:06:24.099468+0800 Thread[16887:391802] 北京 , 卖出 = 30,剩余= 20
2018-12-29 16:06:24.202840+0800 Thread[16887:391801] 上海 , 卖出 = 31,剩余= 19
2018-12-29 16:06:24.307683+0800 Thread[16887:391802] 北京 , 卖出 = 32,剩余= 18
2018-12-29 16:06:24.410991+0800 Thread[16887:391801] 上海 , 卖出 = 33,剩余= 17
2018-12-29 16:06:24.514261+0800 Thread[16887:391802] 北京 , 卖出 = 34,剩余= 16
2018-12-29 16:06:24.621852+0800 Thread[16887:391801] 上海 , 卖出 = 35,剩余= 15
2018-12-29 16:06:24.727306+0800 Thread[16887:391802] 北京 , 卖出 = 36,剩余= 14
2018-12-29 16:06:24.830832+0800 Thread[16887:391801] 上海 , 卖出 = 37,剩余= 13
2018-12-29 16:06:24.934031+0800 Thread[16887:391802] 北京 , 卖出 = 38,剩余= 12
2018-12-29 16:06:25.036285+0800 Thread[16887:391801] 上海 , 卖出 = 39,剩余= 11
2018-12-29 16:06:25.141081+0800 Thread[16887:391802] 北京 , 卖出 = 40,剩余= 10
2018-12-29 16:06:25.244907+0800 Thread[16887:391801] 上海 , 卖出 = 41,剩余= 9
2018-12-29 16:06:25.349845+0800 Thread[16887:391802] 北京 , 卖出 = 42,剩余= 8
2018-12-29 16:06:25.453208+0800 Thread[16887:391801] 上海 , 卖出 = 43,剩余= 7
2018-12-29 16:06:25.557792+0800 Thread[16887:391802] 北京 , 卖出 = 44,剩余= 6
2018-12-29 16:06:25.660827+0800 Thread[16887:391801] 上海 , 卖出 = 45,剩余= 5
2018-12-29 16:06:25.763069+0800 Thread[16887:391802] 北京 , 卖出 = 46,剩余= 4
2018-12-29 16:06:25.867769+0800 Thread[16887:391801] 上海 , 卖出 = 47,剩余= 3
2018-12-29 16:06:25.969181+0800 Thread[16887:391802] 北京 , 卖出 = 48,剩余= 2
2018-12-29 16:06:26.071585+0800 Thread[16887:391801] 上海 , 卖出 = 49,剩余= 1
2018-12-29 16:06:26.176595+0800 Thread[16887:391802] 北京 , 卖出 = 50,剩余= 0

参考文章:
iOS多线程慕课网视频
NSThread总结
iOS 多线程:『pthread、NSThread』详尽总结
深入理解iOS开发中的锁

文章链接:
iOS 多线程-GCD
iOS 多线程-NSOperation + NSOperationQueue

喜欢就点个赞吧✌️✌️
有错之处,还请指出,感谢

你可能感兴趣的:(iOS 多线程- pThread和NSThread)