多线程(一)

进程
是指在系统中正在运行的应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内。

线程
是进程的基本执行单元,一个进程的所有任务都在线程中执行。进程执行任务,必须要有线程,进程至少要有一个线程。程序启动会默认开启一条线程,这条线程被称为主线程或者UI线程。

线程与进程间的关系

  • 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
  • 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
  • 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
  • 执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。

时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片

  • (单核CPU)同一时间,CPU 只能处理 1 个线程

  • 换言之,同一时间只有 1 个线程在执行

  • 多线程同时执行:

    • 是 CPU 快速的在多个线程之间的切换
    • CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
  • 如果线程数非常多

    • CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源
    • 每个线程被调度的次数会降低,线程的执行效率降低
  • 优点

    • 能适当提高程序的执行效率
    • 能适当提高资源的利用率(CPU,内存)
    • 线程上的任务执行完成后,线程会自动销毁
  • 缺点

    • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
    • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    • 线程越多,CPU 在调用线程上的开销就越大
    • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

因为线程本身也会占用内存空间,开启一条线程系统需要大约90ms,所以开启过多的线程没有意义,可以通过如下方式获取设备能够支持线程的最大的并发数量

func test(){
    print("\(ProcessInfo.processInfo.activeProcessorCount)")
}

线程池
通过打印主线程,number = 1,那么理论上来讲,后面新建的number应该一次增加

<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}
<NSThread: 0x10072a110>{number = 4, name = (null)}
<NSThread: 0x100729d80>{number = 2, name = (null)}
<NSThread: 0x101023a20>{number = 3, name = (null)}

通过for循环创建队列,打印对应的线程,发现number是有重复的,这是因为内部维护了一个线程池,线程不是随用就创建的,在核心线程池里去取空闲的线程,复用线程。
线程池的工作原理如下:

  • 使用线程执行任务的时候,需要到线程池中去取线程进行任务分配
  • 首先判断线程池大小是否小鱼核心线程池大小,如果小于的话,创建新的线程执行任务。
  • 如果当前线程池大小大于核心线程池大小,然后开始判断工作队列是否已满,如果没满,将任务提交到工作队列
  • 如果工作队列已满,判断线程池的线程是否都在工作,如果有空闲线程没有在工作,就交给它去执行任务。
  • 如果线程池中的线程都在工作,那么就交给饱和策略去执行

饱和策略

  • AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行

  • CallerRunsPolicy 将任务回退到调用者

  • DisOldestPolicy 丢掉等待最久的任务

  • DisCardPolicy 直接丢弃任务

  • 这四种拒绝策略均实现的RejectedExecutionHandler接口

GCD

全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数

  • GCD的优势
    *GCD 是苹果公司为多核的并行运算提出的解决方案
    • GCD 会自动利用更多的CPU内核(比如双核、四核)
    • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

GCD源码探究
搜索dispatch_queue_create方法,继续调用了_dispatch_lane_create_with_target

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
    return _dispatch_lane_create_with_target(label, attr,
            DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

    //
    // Step 1: Normalize arguments (qos, overcommit, tq)
    //

    dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
    if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
        dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
    }
    if (qos == DISPATCH_QOS_MAINTENANCE) {
        dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
    }

      ........
//
    // Step 2: Initialize the queue
    //

    if (legacy) {
        // if any of these attributes is specified, use non legacy classes
        if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
            legacy = false;
        }
    } 
      ........
    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s));
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

    dq->dq_label = label;
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
            dqai.dqai_relpri);
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
    if (!dqai.dqai_inactive) {
        _dispatch_queue_priority_inherit_from_target(dq, tq);
        _dispatch_lane_inherit_wlh_from_target(dq, tq);
    }
    _dispatch_retain(tq);
    dq->do_targetq = tq;
    _dispatch_object_debug(dq, "%s", __func__);
    return _dispatch_trace_queue_create(dq)._dq;
}

根据注释来看第一步就是根据传过来的dqa进行设置各种参数封装为为dqai。第二步_dispatch_queue_init初始化队列,根据参数dqai的dqai_concurrent,来决定传递参数是1还是大于1,这个参数就是DQF _WIDTH,串行队列(还有主队列)就是DQF_WIDTH(1),即DQF_WIDTH(1)就是串行队列或者主队列,而_dispatch_queue_serial_numbers这个参数代表如下:

// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned

DQF_WIDTH(1)串行队列,相当于车道中的单行道
DQF_WIDTH(大于1)并行队列,相当于车道中的多行道
无论串行还是并行列,都是FIFO先进先出的结构。当是串行队列的时候,每个任务先放进去必定先执行。并行队列,因为多车道,就不一定了,因为每个任务执行的时间,而且有很多个通道,就不一定是按照顺序先执行。串行就像银行办理业务只有一个窗口,无论前面任务有多久,必须要等待然后才能执行。并行就是有多个窗口,比如有4个窗口,并发执行四个任务,如果2号任务先执行完毕,则5号就去2号窗口执行,执行完成的时间按照各自的任务复杂度。

一般队列配合函数一起使用

函数与队列

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

推荐阅读更多精彩内容