多线程

概念

进程:一个正在运行的程序叫一个进程,进程拥有独立运行所需的全部资源。

线程:线程是指进程内的一块执行单元,也是执行任务的基本单位。一个程序,或者说,一个进程,都会有一个或多个线程,如果只有一个,我们叫它主线程,主线程负责用户能看见的任务,例如:添加控件,刷新页面,除了主线程以外,都叫子线程,线程之间是独立的,并没有任何联系,子线程一般负责用户之间看不到的任务,例如,加载图片的过程,下载视频等

线程要明确的:只要用户看的见的,或者跟用户看的见的有关的,咱们都是用主线程操作,因为开启子线程操作的时候,是为了更好的用户体验,用户体验直接表现为:看到的或者点击的流畅

线程是耗费资源的,虽然多线程操作,可以提高用户体验,但是,不建议,进行很多线程同时进行操作
一个进程是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

与进程的区别:

(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间。

(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。

(3)线程是处理器调度的基本单位,但进程不是。

(4)二者均可并发执行。

线程的主要内容:

单线程:每个正在运行的程序(即进程),至少包含一个线程,这个线程叫主线程。

1.主线程在程序启动时被创建,用于执行 main 函数。

2.只有一个主线程的程序,称作单线程程序。

3.主线程负责执行程序的所有代码(UI 展现以及刷新,网络请求,本地存储等等)。这些代码只能顺序执行,无法并发执行。

多线程

1.拥有多个线程的程序,称作多线程程序

2.iOS 允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程。

3.可以根据需要开辟若干子线程

4.子线程和主线程都是独立的运行单元,各自的执行互不影响,因此能够并发执行。

3、单、多线程区别

单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)。

多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。

注意:iOS 中关于 UI 的添加和刷新必须在主线程中操作。

iOS 平台下的多线程

iOS 多线程实现种类

  • NSThread
  • NSOperationQueue
  • pthread
  • GCD

NSThread

NSThread 是一个轻量级的多线程,它有以下两种创建方法:

  • 初始化一个子线程,但需要手动开启
    - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
  • 初始化一个子线程并自动开启
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument
  • 开启子线程
    start
  • 取消当前子线程
    cancel
  • 开启后台执行任务的方法:
    [self performSelectorInBackground:@selector(run:) withObject:@"nsobject thread"];

注意:

1、在多线程方法中需要添加自动释放池

2、在应用程序打开的时候,系统会自动为主线程创建一个自动释放池

3、我们手动创建的子程序需要我们手动添加自动释放池

  
   // NSThread 轻量级的线程,可以开启线程和终止线程
   // 创建一个线程,其实就是给他一个方法去执行
   // 创建一个子线程,专门用来打印
  
   NSThread *thread = [[NSThread alloc] initWithTarget: self selector:@selector(actionButtonNslog) object:nil];
  
   // 开启这个线程
   [thread start];
  
   // 线程操作的时候
   // 在主线程的时候,系统自动给咱们添加了一个自动释放池,那么咱们再开启子线程的时候,也要添加一个自动释放池
   // 如果你的线程开的比较多,会造成代码比较乱,阅读性不高
   // 一般方法中,添加了自动释放池,基本上都是线程方法

NSOperationQueue

NSOperation 也是抽象类 没有实现具体功能
NSInvocationOperation 调用操作(相当于任务)
NSBlockOperation 在block 操作
NSOperationQueue 线程队列

  // 创建任务一
    NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocation1) object:nil];
    // 创建任务二
    NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        // block块中就相当于添加的任务
        [self blockOP2];
    }];
   
    // 创建一个队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 注意 添加任务前要设置依赖关系
    // 依赖性(串行)
    [invocation addDependency:block2];
    // 把任务 添加进队列当中
    [queue addOperation:invocation];
    [queue addOperation:block2];
    // 设置线程最大并发数
    queue.maxConcurrentOperationCount = 2;
  
    // 需求 进行同步请求一张图片 不产生屏幕卡顿
    // 思路: 开启一个子线程 进行同步请求图片 图片到手后 把图片返回到主线程 显示到ImageView上
}

// 实现任务一
- (void)invocation1
{
    // 添加一个自动释放池
    @autoreleasepool {
        // [NSThread currentThread] 当前线程的信息
        // [NSThread isMainThread] 是否是主线程
        // 打印出来额number 是线程的个数
        NSLog(@"%@ 是不是主线程:%d", [NSThread currentThread], [NSThread isMainThread]);
    }
   
}
// 创建任务二
- (void)blockOP2
{
    // 添加释放池
    @autoreleasepool {
       
        NSLog(@"%@ 是不是主线程:%d", [NSThread currentThread], [NSThread isMainThread]);
    }
}
- (void)actionButton
{
    if ([self.imageView isAnimating] == NO) {
        [self.imageView startAnimating];
    } else {
        [self.imageView stopAnimating];
    }
}
- (void)anotherActionButton
{
    // 添加一个自动释放池
    @autoreleasepool {
        for (int i = 0; i < 88888; i++) {
            NSLog(@"%d", i);
        }
    }
   
}

pthread

不经常使用
特点:
1 一套通用的多线程API
2 适用于Unix\Linux\Windows等系统
3 跨平台\可移植
4 使用难度大
使用语言:c语言

GCD

GCD简介

  1. Grand Central Dispatch 简称(CGD),是苹果公司开发的技术。以优化应用程序支持多核心处理器和其他的对称多处理系统的系统。( 对称多处理"(Symmetrical Multi-Processing)简称SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。它是相对非对称多处理技术而言的、应用十分广泛的并行技术。)
  2. GCD 属于函数级的多线程,性能更高,功能也更加强大。
  3. 它首次发布在 Mac OS X 10.6 , iOS4及以上也可以使用。

GCD 核心概念

  1. 任务:具有一定功能的代码段。一般是一个 block 或者函数。
  2. 分发队列:GCD 以队列的方式进行工作,FIFO。
  3. GCD 会根据分发队列的类型,创建合适数量的线程执行队列中的任务。
  • 主队列 是GCD自带的一种特殊串行队列,放在主队列中的任务,都会放在主线程中执行 dispatch_get_main_queue()
  • 全局队列 队列 任务执行方式 并发多个任务同时执行,串行一个一个执行
    1 串行队列 让任务一个接一个地执行 (一个任务执行完毕后,再执行下一个任务)
    2 并发队列 可以多个任务同时执行 (自动开启多个线程同时执行任务)
    3 主队列 专门负责调度主线程任务,没有办法开辟新线程,任务在主线程只会顺序执行
    执行方式 执行的顺序 同步按顺序执行,异步不按顺序执行
    1.同步执行 在当前线程中执行任务,不具备开启新线程的能力
    2.异步执行 在新的线程中执行任务,具备开启新线程的能力
    任务 block
    死锁 如果向主队列中添加一个同步任务会死锁
    死锁原因:我们知道dispatch_sync表示同步的执行任务,也就是说执行dispatch_sync后,当前队列会阻塞。而dispatch_sync中的block如果要在当前队列中执行,就得等待当前队列程执行完成。
    主队列在执行dispatch_sync,随后队列中新增一个任务block。因为主队列是同步队列,所以block要等dispatch_sync执行完才能执行,但是dispatch_sync是同步派发,要等block执行完才算是结束。在主队列中的两个任务互相等待,导致了死锁。

GCD 中两种队列

dispatch queue分为下面两种:

  • SerialQueue:一次只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。Serial queue能实现线程同步。
  • Concurrent:可以并发的执行多个任务,但是遵守 FIFO。

GCD功能

  • dispatch_async( ) 往队列中添加任务,任务会排队执行
  • dispatch_after( ) 往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
  • dispatch_apply( ) 往队列中添加任务,任务会重复执行 n次
  • dispatch_group_async( ) 将任务添加到队列中,并添加分组标记
  • dispatch_group_notify( ) 将任务添加到队列中,当某个分组的所有任务执行完之后,此任务才会执行
  • dispatch_barrier_async( ) 将任务添加到队列中,此任务执行的时候,其他任务停止执行
  • dispatch_once( ) 任务添加到队列中,但任务在程序运行过程中,只执行一次
  • dispatch_sync ( ) 将任务添加到队列中,block 不执行完,下面代码不会执行
  • dispatch_async_f ( ) 将任务添加到队列中,任务是函数非 block

线程间的通信

线程间通信分为两种:

  • 主线程进入子线程(前面的方法都可以)
  • 子线程回到主线程
    返回主线程:
    GCD:dispatch_get_main_queue()
    NSObject : - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

线程互斥

线程互斥场景:

  • 线程互斥是指某一资源同上=时只允许一个访问者对其进行访问,具有唯一性和排它性。
  • 互斥无法限制访问者对资源的访问顺序,即访问是无序的。因此需要加上互斥锁来进行顺利访问,最具代表性的就是买票系统。
  • NSLock 类能协助完成互斥操作。
#pragma mark -- 买火车票的线程锁实现
- (void)tickets
{
    // 初始化票数
    _totalTickets = 100;
    
    // 初始化剩余票数
    _subTickets = 100;
    
    // 初始化线程锁
    self.lock = [[NSLock alloc] init];
    
    // 先创建两个并行队列
    dispatch_queue_t queue1 = dispatch_queue_create("火车站", DISPATCH_QUEUE_CONCURRENT);
    // 给火车站,添加卖票的任务
    dispatch_async(queue1, ^{
        // 卖票
        [self saleTickets:queue1];
    });
    
    // 创建12306队列
    dispatch_queue_t queue2 = dispatch_queue_create("com.12306.www", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue2, ^{
        // 卖票
        [self saleTickets:queue2];
    });
}

#pragma mark -- 售票方法
- (void)saleTickets:(dispatch_queue_t)queue
{
    // 循环卖票
    while (_subTickets > 0) {
        // 添加锁
        // 线程锁 跟自动释放池 使用的方法差不多
        // 中间夹的部分,是锁的内容
        [self.lock lock];
        
        // 咱们要知道,是哪个队列来卖票
        // 通过队列表示符,得到
        // dispatch_queue_get_label 得到队列的标识符
        char *name = (char *) dispatch_queue_get_label(queue);
        NSString *str = [NSString stringWithUTF8String:name];
        NSLog(@"%@ 还剩%ld张票", str, _subTickets);
        
        // 来一回,减少一张
        _subTickets--;
        
        // 锁结束
        [self.lock unlock];
    }
}

group任务

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue2 = dispatch_queue_create("com.sinosoft.queue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue2, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"1======%d===%@", i, [NSThread currentThread]);
        }
    });
    dispatch_group_async(group, queue2, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"2======%d===%@", i, [NSThread currentThread]);
        }
    });
    dispatch_group_notify(group, queue2, ^{
     NSLog(@"组1和组2都完成了=========%@", [NSThread currentThread]);
    });
   long isCompleted =  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"%ld", isCompleted);
    /*
     dispatch_group_wait方法是一个很有用的方法,它的完整定义如下:
     
     dispatch_group_wait(group: dispatch_group_t, _ timeout: dispatch_time_t) -> Int
     
     第一个参数表示要等待的group,第二个则表示等待时间。返回值表示经过指定的等待时间,属于这个group的任务是否已经全部执行完,如果是则返回0,否则返回非0。
     
     第二个dispatch_time_t类型的参数还有两个特殊值:DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER。
     
     前者表示立刻检查属于这个group的任务是否已经完成,后者则表示一直等到属于这个group的任务全部完成。
     */
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容