多线程 并没有那么难

作者: 木子小静 | 来源:发表于2016-09-12 11:41 被阅读316次

串行并行的定义

串行:一个线程中执行多个任务,只能一个一个的按照顺序执行这些任务。
并行:一个进程中可以开启多条线程,每条线程可以同时执行不同的任务

多线程的原理

同一时间,cpu只能处理1条线程,只有1条线程在工作,如果存在多条线程,实际上是线程间以非常非常快的速度进行切换,CPU调度线程的速度非常快,看起来就像同时进行。需要注意的是,线程不宜太多,会消耗大量CPU资源

优点

能适当提高程序的执行效率,适当的提高CPU和内存利用率
缺点:创建线程是有开销的,线程的创建需要90毫秒的时间,如果大量的开启线程,会降低程序的性能,而且线程越多,程序越复杂,比如线程之间的通信,多线程的数据共享

安全隐患

  • 资源共享
    一块资源可以被多个线程同时访问(例如,3个线程同时修改一个文件)
    解决方案:加同步锁 @synchronized(锁对象){需要锁定的代码 }
    多个线程之间锁对象必须是同一个,保证线程之间用的锁是同一个,否则锁是无效的,一般用self就可以
    使用前提:多条线程抢夺同一块资源的时候,需要对数据访问修改等问题时
@synchronized (self) {
        // 要锁定的操作
        NSLog(@"%@", [NSThread currentThread]);
    }
  • 原子和非原子属性
    atomic:原子属性,setter方法加锁,调用setter方法前加锁,调用之后锁打开,防止多条线程对属性值的修改,会消耗大量的资源
    nonatomic:非原子属性,适合内存小的移动设备,setter方法和getter方法大部分都在主线程访问,如有在子线程访问的,可单独加锁,在写代码的过程中尽量避免多线程抢夺同一块资源

线程间通信

包括线程间数据传递、在一个线程中执行完特定任务后跳转到其他线程继续执行任务

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _picView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    [self.view addSubview:_picView];
    
    // 创建线程
    [self performSelectorInBackground:@selector(downLoadPic) withObject:nil];
 
}

- (void)downLoadPic
{
    NSURL *url = [NSURL URLWithString:@""];
    
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    UIImage *pic = [UIImage imageWithData:data];
    
    // 回主线程
//    [_picView performSelectorOnMainThread:@selector(setPicView:) withObject:pic waitUntilDone:YES];
    
    [_picView performSelector:@selector(setPicView:) onThread:[NSThread mainThread] withObject:pic waitUntilDone:YES];
}

线程实现方法

1.pthread

基于C语言,需要手动管理,平时几乎不用

先导入头文件#import <pthread.h>

// 指向函数的指针
void *run(void *param)
{
    return NULL;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);
}

2.NSThread

基于OC, 需要手动管理,常用的就几个方法

+(NSThread *)mainThread; // 获取主线程
+(BOOL)isMainThread; // 是否为主线程
-(BOOL)isMainThread; // 是否为主线程
+(NSThread *)currentThread; // 获取当前线程
-(void)setName:(NSString *)name; // 设置线程名字
-(NSString *)name; // 获取线程名字
+(void)sleepUntilDate:(NSDate *)date; // 休眠到什么时间
+(void)sleepForTimeInterval:(NSTimeInterval)ti; // 休眠几秒钟
+(void)exit; // 退出当前线程,停止了就不能恢复了

三种创建方法如下

- (void)run
{
    // 打印当前线程
    NSLog(@"%@", [NSThread currentThread]);
}

- (void)createThread1
{
    // 创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:@"louise"];
    // 启动线程
    [thread start];
}

- (void)createThread2
{
    // 不需要启动,没有返回值,拿不到刚刚创建的线程,所以不能设置名字,用来处理简单的操作
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:@"louise"];
}

- (void)createThread3
{
    // 隐式创建,看不出来是创建线程
    [self performSelectorInBackground:@selector(run) withObject:@"louise"]; 
}

3.GCD

  • 优势:自动管理线程的生命周期(创建,任务的调度,销毁线程)
    核心概念:任务(执行什么操作)、队列(用来存放任务)

  • 执行任务:
    同步:只能在当前线程中执行任务,不具备开启新线程的能力:dispatch_sync
    异步:可以在新的线程中执行任务,具备开启新线程的能力:dispatch_async

  • 队列的类型
    并发队列:可以让多个任务并发执行,并发功能只能在异步(dispatch_async)函数下才有效
    串行队列:让任务一个接着一个的执行

  • 区分几个比较容易混淆的词语
    同步和异步:在于能否开启新的线程,同步只能在当前线程执行任务,不具备开线程的能力,异步可以在新的线程中执行任务,具备开线程的能力

    并发和串行:并发是多个任务并发执行,串行是一个任务接一个任务的执行

  • 并发/串行队列&同步/异步代码实现

    
    // label相当于队列的名字
    // DISPATCH_QUEUE_CONCURRENT 并发队列
    // DISPATCH_QUEUE_SERIAL 串行队列

    // 1.创建一个串行队列
    // dispatch_queue_t queue = dispatch_queue_create("serial_chuan", DISPATCH_QUEUE_SERIAL);
    // 1.创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("concurrent_bing", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.将任务同步加入队列
    // dispatch_sync(queue, ^{
        
        // 要执行的代码
    //    NSLog(@"1----%@", [NSThread currentThread]);
    // });
    // dispatch_sync(queue, ^{
        
        // 要执行的代码
     //   NSLog(@"2----%@", [NSThread currentThread]);
    // });

    // 2.将任务异步加入队列
    dispatch_async(queue, ^{
        
        // 要执行的代码
        NSLog(@"1----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        
        // 要执行的代码
        NSLog(@"2----%@", [NSThread currentThread]);
    });
  • GCD其他常用函数
    • 延时操作
      除了NSObject的performSelector和NSTimer之外还可以使用GCD的方法 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)

        // 2.0 * NSEC_PER_SEC:两秒之后执行
        // dispatch_queue_t queue:在哪个队列里执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        // 两秒后执行这里的代码
    });
  • 一次性代码
    整个程序运行过程中只调用一次,不是每个对象各调用一次,这里需要注意
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 整个程序运行过程中只调用一次
    });
  • 快速迭代
    同时遍历所有的数据 dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t)block)
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        
        // 同时遍历10个数据,比如剪切复制粘贴等 是不需要顺序的,可以同时进行操作
        NSLog(@"----%zu-----%@", index, [NSThread currentThread]);
    });

4.NSOperationQueue

通过NSOperation 和NSOperationQueue配合实现多线程

  • 优点:不需要管理线程的创建和调用,只要注重执行的操作即可

  • 原理:将操作封装到NSOperation对象中,然后将NSOperation对象添加到NSOperationQueue队列中,系统会自动将任务取出放到线程中执行

  • NSOperation的子类:NSOperation是个抽象类,使用它必须用它的子类: NSInvocationOperation,NSBlockOperation,自定义继承自NSOperation的子类.

  • NSOperation的代码实现

- (void)InvocationOperation
{

    // 在当前线程执行,只有将任务添加到队列中才会开新线程
//    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//    [op start];
    
 }

- (void)BlockOperation
{

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        
        // 在当前线程执行
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    // 添加额外的任务,在子线程执行
    [op addExecutionBlock:^{
        
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    [op start];

 }

}

- (void)run
{
    NSLog(@"%@",[NSThread currentThread]);
}

  • NSOperationQueue的代码实现 - 串行

// 默认的都是串行队列
- (void)NSOperationQueue
{

    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建NSInvocationOperation任务
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    
    // 只要将任务添加到队列,就新开启线程,并且不需要调用start方法
    [queue addOperation:op1];
    [queue addOperation:op2];
    
    // 创建NSBlockOperation任务
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
     // 还可再添加额外的任务,在子线程执行
    [op3 addExecutionBlock:^{
    
        NSLog(@"3-----1----%@",[NSThread currentThread]);
    }];

    // 只要添加到队列中,就新开线程
    [queue addOperation:op3];
    
}

- (void)run
{
    NSLog(@"%@",[NSThread currentThread]);
}

当异步执行的代码特别多特别长的时候就使用自定义NSOperation,可以将任务封装起来,使用的时候只需要调用自定义类的alloc init方法,然后把任务添加到队列中即可

  • NSOperationQueue的代码实现 - 并行
    只要设置最大并发数不为1就是并行

- (void)NSOperationQueue
{
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 设置最大并发操作数,如果设置最大并发数为1,就是串行队列
    queue.maxConcurrentOperationCount = 3;
    
    // 创建NSBlockOperation任务
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    // 只要添加到队列中,就新开线程
    [queue addOperation:op1];
    
    // 也可以不创建NSOperation 直接创建任务
    [queue addOperationWithBlock:^{
        
        NSLog(@"2-------%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        
        NSLog(@"3-------%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        
        NSLog(@"4-------%@", [NSThread currentThread]);
    }];
}

  • 线程的暂停
    需要注意的是,暂停是等当前的任务执行完成之后暂停后面的任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.suspended = YES;
  • 取消线程任务
    任务取消了之后不可恢复,同样的,也是等当前的任务执行完成之后在取消
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue cancelAllOperations];
  • 线程间依赖
- (void)addDependency
{

    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建NSBlockOperation任务
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2----%@",[NSThread currentThread]);
    }];

    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3----%@",[NSThread currentThread]);
    }];

    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    // 设置依赖,op3依赖于op1和op2,需要op1和op2都执行完才能执行op3
    [op3 addDependency:op1];
    [op3 addDependency:op2];

    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    
}

需要注意的是,可以跨队列依赖,但是不能互相依赖(A依赖B,B依赖A)

  • 线程间通信
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建NSBlockOperation任务
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        // 开子线程
        NSLog(@"1----%@",[NSThread currentThread]);
        
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            // 回到主线程
        }];
        
        
    }];
    
    [queue addOperation:op1];

到这里多线程基本上就介绍完了,是不是很简单呢,多多实践就会发现真的很简单,有什么问题可以私信我哟,如果喜欢,感觉对你有点帮助,可以点个关注哟O(∩_∩)O哈哈

相关文章

  • 多线程 并没有那么难

    串行并行的定义 串行:一个线程中执行多个任务,只能一个一个的按照顺序执行这些任务。并行:一个进程中可以开启多条线程...

  • iOS多线程

    iOS多线程基础 在iOS中,多线程的概念应该算是比好理解的了.并没有想象中那么难搞.简单的概念像线程/进程这些概...

  • 并没有那么难

    昨晚大酒,三杯白的加零星的啤酒,喝的不至于烂醉如泥,但也是相当的不舒服了,说了些不该说的,漏了一点被推向社区的事,...

  • 离职并没有那么难!

    人都会死,不如大胆的活。 在今天这个特殊的时刻听李欣频老师的分享,有一种强大的能量在心中漂浮。 因为一些价值观的不...

  • 其实并没有那么难

    有一个朋友因为要陪自己还在读幼儿园的孩子去日本参加一个比赛而焦虑了很长的一段时间,因为自己不懂日语,还是个路...

  • 成功并没有那么难

    今天在网上看到一组图片(具体如下),这四张图片说了成功依靠的四种方法。 第一种成功方式:靠信念 一般那些能够取得很...

  • 改变并没有那么难

    【0505读书感悟】3387-吴少轩 大家晚上好,欢迎老读者,也欢迎今天刚加入的新读者。每天学习一点点,坚持带来大...

  • 爱情 并没有那么难

    本来 是已经想好主题了 可是 今天中午没有午睡 打开电脑看了下《我们相爱吧第三季》 本来是冲着无尾熊那一对去看的 ...

  • 其实并没有那么难

    我们在做一件事情的时候总爱把困难无限的放大,其实真正做了就会发现“其实并没有我们想象中那么难” 我本人年龄不少了,...

  • 早起并没有那么难

    距离第一次记录早起已经42天,其中我真正在记录的只有6天,因为整个7月我都在不同的城市奔走忙碌,自己的奋斗的...

网友评论

    本文标题:多线程 并没有那么难

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