多线程 并没有那么难

串行并行的定义

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

多线程的原理

同一时间,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哈哈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 784评论 0 3
  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 7,169评论 11 70
  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 599评论 0 0
  • Summary What Is Argument? What is an argument? We may reg...
    读者_在路上阅读 261评论 0 0
  • sklearn内部集成了一些手写体数字图片数据集,现在我们使用这些数据,用SVM支持向量机算法进行训练识别...
    lzp12138阅读 2,081评论 0 6