多线程(pthread,NSThread,GCD)

1.什么是线程?
2.什么是进程?
3.什么又是多线程?

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

线程是进程的一条执行路径.
线程注意点:
1>线程执行任务是串行的. (所以才有了子线程的概念, 如果都放在一个线程中执行任务, 当下载大图片的时候, 就什么操作也做不了了. .....)

关于多线程:
一个进程中可以开启多个线程, (一个应用程序中能够开启多个通道, 让不同的任务进行执行, 而不会导致 阻塞).
这样在同一时间内, 既不影响用户的体验, 也将大数据/耗时操作 得以执行.

关于多线程的原理:
1.同一时间,CPU只能处理一条线程, 只有一条线程在工作(执行)
2.多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换).
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象.

思考:如果线程很多时,会出现什么情况?
1>CPU会在多个线程之间调度, 累死了,消耗了大量的CPU资源
2>每条线程被调度执行的频率降低( 会很卡的).

多线程的优缺点:
优点:
1>能适当提高程序的执行效率.
2>适当的提高资源利用率

缺点:
1>创建线程是有开销的. 创建时间大约 90毫秒, 占用一定内存资源
2>大量的开启线程, 会降低程序的性能.
3>线程越多, CPU在调度线程上的开销就越大(无故消耗资源)
4>程序设计更加复杂,线程之间的通信, 以及资源共享等问题

多线程在iOS开发中的应用:

1>主线程: 和程序的启动有关, 程序已启动,默认开启一个主运行循环, 内部有一个主线程.(UI线程)
2>主线程的主要作用:
显示/刷新UI界面 (一般都是一部请求数据, 会主线程刷新UI)
处理UI事件(比如 点击, 拖拽,滚动等)
3>使用主线程需要注意的
不要将耗时操作放置在主线程执行. (阻塞主线程 , 严重影响UI的流畅度)

Snip20150923_2.png

pthread(纯C语言, 比较难使用,这里只进行简单方法的说明)

// 点击屏幕增加子线程 ,来执行耗时的 方法.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 将耗时操作放在子线程中执行
      pthread_t  thread;
    /*
    第一个的参数:线程的代号(就是代指线程)
     第二个参数:线程的属性
     第三个参数:只想指向函数的指针,就是线程需要执行的方法
     第四个参数: 就是指向函数的指针 传递的参数。
    */
    pthread_create(&thread, NULL,&demo , "lxl");
    
  // 用于验证 ,主线程继续执行操作  (没有阻塞主线程)
    NSLog(@"刘小龙,你好,%@",[NSThread currentThread]);
}
// 设置耗时的方法 ( 只想函数的指针)
void *demo(void *parma){

    // 打印传入的参数, 以及当前的线程, 是1则是主线程,其他都不是
    NSLog(@"%s,%@",parma,[NSThread currentThread]);
    
    // 一些耗时的操作
    for (int i = 0; i < 99999; i++) {
        // NSLog是一个非常耗时的操作
        // 上架之前  全部清掉.
        NSLog(@"%i",i);
    }
   return NULL;
}

NSThread(不长使用, 需要自己手动管理它的生命周期) [现在使用它只有一个目的 就是获取当先所处的线程 [NSThread currentThread] ]

创建和启动线程
1>一个NSThread对象就代表一个线程.
2>创建.启动线程 (直接设置线程的执行者, 和线程执行的方法, 传入的参数)
- initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>
- start开启线程
3>常用的与主线程相关的方法:
+ mainThread获得主线程
- isMainThread是否是主线程
+ isMainThread 是否为主线程
+ currentThread获取当前的线程
- setName:设置线程的名字,方便管理
4> 创建一个直接启动的线程(不能设置名字)
+ detachNewThreadSelector: toTarget: withObject:
5>隐式创建并启动线程(一般我都不会这么用,不好管理)
[self performSelectorInBackground:@selector(run) withObject:nil];

线程的状态


1>启动线程
- start 进入就绪状态--->运行状态. 线程任务执行完毕,自动进入死亡状态
2>阻塞(暂停)线程
+ sleepUntilDate: // 线程休眠到什么时候
+ sleepForTimerInterval:// 休眠几秒
阻塞线程
3>强制停止线程
+ exit 进入死亡状态 ---一旦线程停止(死亡), 就不能再次开启任务, 需要重新定义.
#############
1.创建子线程:新建
2.就绪:启动子线程
3.运行:调度子线程
4.阻塞:调用了sleep/等待同步锁 @synchronized(id){}
5.死亡:线程执行完毕,或者异常退出/强制退出
注意: 对于线程对象,创建的时候被加到了 可调度线程池中, 待调度使用。 如果调用了sleep/同步锁,就会被移除调度池,当sleep/同步锁到时后,会再被引入调度池,做就绪准备

互斥锁,与线程之间的抢夺, 已经在上文讲出

线程之间的通信

1>线程之间不是孤立存在的, 多个线程之间需要经常进行通信.
2> 数据传递, 任务传递

3>常用方法[一般都是控制器自己调用的]
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)obj waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)the withObject:(id)arg waitUntilDone:(BOOL)wait

GCD (Grand Central Dispatch),"牛逼的中枢调度器"

1>纯C语言,提供了很强的函数
2>优势:
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核
GCD会自动管理线程的生命关系(创建线程,调度任务,销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要管理线程
3>任务与队列
任务:执行什么操作
队列:用来存放任务
4>GCD的使用就2个步骤
定制任务
确定想做的事情
5>将任务添加到队列中
线程---->队列----->任务
任务加入队列, 队列放置在线程中执行
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出.

########任务的执行
GCD常用的2个用来执行任务的常用函数
1>
1.同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列 block:任务
2.异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2>同步和异步的区别
同步:只能在当前的线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力

3>执行任务
GCD中还有个来执行任务的函数:(添加依赖)
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
前面的队列执行完任务后,才能执行后面的任务 ,这里的队列不能传全局并发队列, 必须传一个固定的队列.

4>GCD的队列可以分为2个类型:
并发队列(Concurrent Dispatch Queue)
系统创建多条线程来并发(同时)执行队列中的任务, 但开启几条,由系统决定, 这个队列只有在异步函数中有效果
串行队列(Serial Dispatch Queue)
让任务一个个执行 (一个任务执行完毕后,在执行下一个任务)

########同步/异步;并发/串行
同步/异步:能不能开启新的线程
同步:只是在当前线程(一般都是指主线程)中执行任务,不具备开启新线程的能力.
异步:可以在新的线程中执行任务,具备开启新线程的能力

  并发/串行:任务的执行方式
  并发:允许多个任务并发(同时)执行
  串行:一个任务执行完毕后,再执行下一个任务

5>并发队列
5.1dispatch_queue_create函数创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
lable: 队列名称
attr: 队列的类型
简单的创建并发队列:
dispatch_queue_t queue = dispatch_queue_create("123",DISPATCH_QUEUE_CONCURRENT)

5.2全局并发队列
dispatch_get_global_queue函数获得全局的并发队列
函数的书写
dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority ,// 队列的优先级 unsigned long flags //这个只是一个标志, 一般不用 传0)优先级默认也是0

6>串行队列
6.1 dispatch_queue_create函数创建串行队列
创建串行队列(队列类型传递NULL 或者 DISPATCH_QUEUE_SERIAL)
简单创建串行队列
dispatch_queue_t queue = dispatch_queue_create("123",NULL)
6.2 使用主队列 (跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列的任务,都会放在主线程中执行
使用dispatch_get_main_queue()获得主队列

Snip20150924_4.png

7>线程之间的通信
子线程执行耗时操纵, 会主线程刷新UI控件

dispatch_async(
dispatch_get_global_queue(0,0),^{
// 耗时的异步操作
.....
 #warning 必须使用异步调用主队列.  同步的话,会造成阻塞主线程(因为有矛盾, 主线程不想让执行block, 主队列又想, 这样很矛盾的)
dispatch_async(dispatch_get_main_queue(),^{
// 返回主线程,执行UI刷新操作
});
});

8>延迟执行
常见的几种延迟执行的方法:
8.1 调用NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // 延时两秒调用self的run方法

8.2使用GCD函数

dispatch_after(dispatch_time(DISPATCH_TIME_NOW , (int64_t)(2.0 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
// 2秒后执行这里的代码......
});

8.3使用NSTimer

[NSTimer scheduledTimerWithTimeInterval: 2.0 target:self selector:@selector(test) useInfo:nil repeats:NO];

/##############特殊应用##############/
8.4一次性代码
使用dispatch_once函数能保证某段代码在程序运行过程中只被执行一次

static dispatch_once_t oneToken;
dispatch_once(&onceToken,^{
    // 只执行一次的代码(在这里默认是线程安全的)
});

8.5快速迭代
使用dispatch_apply函数能进行快速迭代遍历

dispatch_apply(10,dispatch_get_global_queue(0,0),^(size_t index){
// 执行10次代码, index顺序不确定
.....
})

8.6队列组
有点等同于依赖的关系,
但是对于某些特殊要求还是需要建立队列组来完成
比如有这么一个需求
1>首先:分别异步执行2个耗时操作
2>其次:等2个异步操作都执行完毕后,再回到主线程执行操作

dispatch_group_t group = dispatch_group_creat();
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
    //
执行1个耗时的异步操作
});
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
    //
执行1个耗时的异步操作
});

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

推荐阅读更多精彩内容