iOS 多线程 (NSThread)

2019-12-01  本文已影响0人  若水water

进入文章前,我们先了解一下多线程中的基本概念。
一、进程

二、线程

三、进程和线程的关系

四、多进程

五、多线程

六、多线程的优缺点

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

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

七、任务,队列

八、iOS中的多线程

iOS中的多线程主要有三种:NSThread ,NSoperationQueue,GCD

下面我们一一讲解:

NSThread:

是对pthread 的上层封装,把线程处理为面向对象的逻辑。一个NSThread代表一个线程。
优点:NSThread是一种轻量级的多线程实现方式
缺点:需要自己管理线程的生命周期,线程同步。同时使用NSThread线程同步 对数据加锁 会有一定的性能开销。

1、显示创建方法,并且手动启动线程

/*
target :selector消息发送的对象
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值
object: 为传递给selector方法的参数
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadRun:) object:nil];
//启动线程
[thread start];

//iOS10 的创建方法:
NSThread * thread = [[NSThread alloc]initWithBlock:^{
               NSLog(@"手动创建线程启动 = %@",[NSThread currentThread]);
           }];
[thread start];

通过以上方法创建,可以得到一个thread 对象,可以对对象设置一些相关属性:
//设置线程名
1、thread.name = @"name";
2、threadPriority:设置线程的优先级 (iOS8 之后: setThreadPriority),
优先级为0.0-1.0的double类型,1.0最高。每一个新的线程都有一个默认的优先级,系统的内核调度算法根据线程的优先级来决定线程的执行顺序。通常情况下我们不要改变线程的优先级,提高一些线程的优先级可能会导致低优先级的线程一直得不到执行,如果在我们的应用内存在高优先级线程和低优先级线程的交互的话,因为低优先级的线程得不到执行可能阻塞其他线程的执行。这样会对应用造成性能瓶颈。
脱离线程(detach Thread):线程完成后,系统自动释放它所占用的内存空间
可连接线程(Joinable Thread):线程完成后,不回收可连接线程的资源
在应用程序退出时,脱离线程可以立即被中断,而可连接线程则不可以。每个可连接线程必须在进程被允许可以退出的时候被连接。所以当线程出于周期性工作而不被允许被中断的时候,比如保存数据到硬盘,可连接线程是最佳选择。
当然在iOS开发过程中,很少需要我们创建可连接的线程。通过NSThread创建的线程都是脱离线程。如果你想创建可连接线程,唯一的办法是使用POSIX线程。POSIX默认创建的线程是可连接的。通过pthread_attr_setdetachstate 函数设置是否脱离属性。
iOS8 以后优先级值:
3、stackSize:配置线程栈空间
栈空间是用来存储为线程创建的本地变量的,栈空间的大小必须在线程的创建之前设定,即在调用NSThread的start方法之前 通过setStackSize 设定新的栈空间大小
4、threadDictionary:配置线程的本地存储
每个线程都维护一个在线程任何地方都能获取的字典。我们可以使用NSThread的threadDictionary方法获取一个NSMutableDictionary对象,然后添加我们需要的字段和数据。

iOS8 以后设置优先级的值:
NSQualityOfServiceUserInteractive = 0x21,//最高优先级,用于用户交互事件
NSQualityOfServiceUserInitiated = 0x19,//次高优先级,用于用户需要马上执行的事件
NSQualityOfServiceUtility = 0x11,//默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1

常用方法

[thread start];//实例方法,启动线程
[thread cancel];//实例方法,取消线程
[thread isCancelled];//判断线程是否已经取消
[thread isFinished];//判断线程是否结束
[thread isExecuting];//判断线程是否正在执行
//线程休眠
 [NSThread sleepForTimeInterval:1.0];
//线程休眠 
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
//终止主线程以外的所有线程
[NSThread exit];
[NSThread isMainThread]://判断的是当前线程是否为主线程
[thread isMainThread]: //判断的是thread 是否为主线程

2 创建并自启动线程的方法

//方法返回值为空,方法内自己创建线程对象,并由系统自动启动
[NSThread detachNewThreadSelector:@selector(threadRun:) toTarget:self withObject:nil];
//ios10 以后的方法
[NSThread detachNewThreadWithBlock:^{
    NSLog(@"10.0之后 创建并自启动线程...");
 }];

3、隐式创建,用于线程间通信

/*
@selector 定义我们要执行的方法
withObject:arg 定义了我们执行方法时,传入的参数对象,类型是id。
waitUntilDone:YES:是指当前线程是否要被阻塞,直到主线程将我们指定的代码块执行完
modes:array 指定时间运行的模式
*/
[self performSelectorOnMainThread:@selector(threadRun) withObject:@"" waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES modes:nil];

/*
以上方法的作用是在主线程中执行指定的方法。该方法主要用来回调到主线程来修改页面UI的状态。
*/

//在指定线程中执行
[self performSelector:@selector(testRun2) onThread:[NSThread mainThread] withObject:nil waitUntilDone:false];
//    在主线程指定在后台线程执行
[self performSelectorInBackground:@selector(threadRun) withObject:nil];

//在当前线程执行的方法

[self performSelector:@selector(threadRun)];
 //    传递参数,指定函数在当前线程执行
[self performSelector:@selector(threadRun) withObject:@"123"];
 //    传递参数 指定函数 2s后在当前线程执行
[self performSelector:@selector(threadRun) withObject:@"dd" afterDelay:2];
//--------
需要注意的是:如果afterDelay的延时函数,会在内部创建一个NSTimer,然后添加到当前线程的runloop中。也就是如果当前线程没有开启runloop,该方法会失效,在子线程中需要启动runloop
[[NSRunLoop currentRunLoop] run];
//-----------
而 performSelector:withObject: 只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的runloop中也能执行。
[lock lock]//加锁
[lock unlock]//解锁

//例如 多个线程 修改一个变量操作
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(taskRun) object:nil];
thread1.name = @"task1";

NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(taskRun) object:nil];
thread2.name = @"task2";

NSThread *thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(taskRun) object:nil];
thread3.name = @"task3";

[thread1 start];
[thread2 start];
[thread3 start];

//------taskRun-----------
- (void)taskRun {
      while (count > 0) {
            [NSThread sleepForTimeInterval:0.1];
            count --;
            NSLog(@"threadName = %@ count: %ld",[NSThread currentThread].name,count);
        }
}

可以从以下的打印结果中看出,数据是错乱的,没有顺序的,那是因为多个线程对count 同时做了更改操作,在一个线程还没有结束的时候,另一个线程也开始执行这个方法。
打印结果.png
改进:
/**
     @synchronized(锁对象) { // 需要锁定的代码  }
     注意:锁定一份代码 只用1把锁,多把锁是无效的
     互斥锁的优缺点:
     优点:能有效防止因多线程抢夺资源造成数据安全问题
     缺点: 需要消耗大量的CPU资源
     互斥锁的使用前提:多条线程抢夺统一资源
     
     线程同步:多条线程按顺序的执行任务
     互斥锁:使用了线程同步技术
     */
- (void)taskRun {
         while (count > 0) {
                 @synchronized (self) {//需要锁定的代码
                 [NSThread sleepForTimeInterval:0.1];
                 count --;
                 NSLog(@"threadName = %@ count: %ld",[NSThread  currentThread].name,count);
            }
        }
    }
加锁之后打印结果.png
//或者使用NSLock
 while (count > 0) {
        [_threadLock lock];
        [NSThread sleepForTimeInterval:0.1];
        count --;
         NSLog(@"threadName = %@ count: %ld",[NSThread currentThread].name,count);
        [_threadLock unlock];
     }

atomic:意思就是setter/getter这个函数是一个原子操作。如果有多个线程同时调用setter方法,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter的情况。相当于函数头尾加了锁一样,可以保证数据的完整性。
nonatomic 不保证setter ,getter的原语行,所以你可能会取到不完整的东西。因此在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态,这样取到的东西就会有问题,是不完整的。当然如果不需要多线程支持的话,用nonatomic就够了,因为不涉及到线程锁的操作,所以它执行相对快些。
一般iOS中,所有属性都声明为nonatomic。这样做的原因是:在iOS中使用同步锁的开销比较大,这样会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证线程安全,若要实现线程安全的操作还需要采用更为深层的锁定机制才行。例如:一个线程在连续多次读取到某个属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读取到不同的属性值。因此,iOS程序一般都会使用nonatomic属性。但是在mac os x 程序时,使用atomic属性通常都不会有性能瓶颈。

NSCondition *condition = [[NSCondition alloc]init];
[condition lock]//一般用于多线程同时访问,修改同一个数据源,保证在同一时间内数据源只被访问一次,修改一次,其他线程的命令需要在lock外等待,只有unlock,才可访问
[condition unlock]//与lock 同时使用
[condition wait]//让线程处于等待状态
[condition signal]//CPU发信号告诉线程不用再等待,可以继续执行
//常用API
//释放互斥量,使当前线程等待,切换到其他线程执行
- (void)wait;
//释放互斥量,使当前线程等待到某一个时间,切换到其他线程执行
- (BOOL)waitUntilDate:(NSDate *)limit;
//唤醒一个其它等待该条件变量的线程
- (void)signal;
//唤醒所有其它等待该条件变量的线程
- (void)broadcast;
//应用实例:生产者 消费者,每生产一个商品 就让消费者取走一个商品
@interface NSConditionSample ()

@property(nonatomic, strong)NSCondition *condition;

@property(nonatomic, strong)NSMutableArray *products;

@end

@implementation NSConditionSample

- (instancetype)init
{
    self = [super init];
    if (self) {
        _condition = [[NSCondition alloc]init];
        _products = [NSMutableArray array];
    }
    return self;
}

//消费者
- (void)createConsumenr {
    while (YES) {
        NSLog(@"createconsumer before lock");
        [_condition lock];
        NSLog(@"createconsumer after lock");
        while (_products.count == 0) {
            NSLog(@"wait for products");
            [_condition wait];
        }
        [_products removeObjectAtIndex:0];
        NSLog(@"consume a product");
        [_condition unlock];
    }
}

//生产者
- (void)createProducter {
    while (YES) {
        NSLog(@"createProducter before lock");
        [_condition lock];
        NSLog(@"createproducter after lock");
        [_products addObject:[[NSObject alloc]init]];
        NSLog(@"produce a product");
        [_condition signal];
        [_condition unlock];
    }
}

//执行
- (void)viewDidLoad {
    [super viewDidLoad]; 
    NSConditionSample *sample = [[NSConditionSample alloc]init];
    [NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:sample withObject:nil];
    [NSThread detachNewThreadSelector:@selector(createProducter) toTarget:sample withObject:nil];    
}
打印结果.png
上一篇 下一篇

猜你喜欢

热点阅读