多线程基础

为避免多线程同时争夺一块资源,将加锁、资源争夺的逻辑交给服务器端处理,减少移动端的压力


并发(concurrency)和并行(parallellism)是:

解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。 


1·.pthread 跨平台,使用难度大                         c语言    程序员管理生命周期

2.NSthread 面向对象                                           OC        程序员管理生命周期

3.GCD      充分利用多核                                      C          自动

4.NSOperation  基于GCD 多了些简单功能    OC      自动


1. pthread


2. NSThread

1). 创建、启动线程

先创建线程,再启动线程

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];

[thread start];    // 线程一启动,就会在线程thread中执行self的run方法

创建线程后自动启动线程

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

隐式创建并启动线程

[self performSelectorInBackground:@selector(run) withObject:nil];

2. 线程相关用法

// 获得主线程

+ (NSThread *)mainThread;

// 判断是否为主线程(对象方法)

- (BOOL)isMainThread;

// 判断是否为主线程(类方法)

+ (BOOL)isMainThread;

// 获得当前线程

NSThread *current = [NSThread currentThread];

// 线程的名字——setter方法

- (void)setName:(NSString *)n;

// 线程的名字——getter方法

- (NSString *)name;

3. 线程状态控制方法

启动线程方法

- (void)start;

// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程方法

+ (void)sleepUntilDate:(NSDate *)date;

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 线程进入阻塞状态

强制停止线程

+ (void)exit;

// 线程进入死亡状态

4. 线程的状态转换

如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态。

如果CPU在运行当前线程对象的时候调用了sleep方法\等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时\得到同步锁,则回到就绪状态。

如果CPU在运行当前线程对象的时候线程任务执行完毕\异常强制退出,则当前线程对象进入死亡状态。

3. GCD

Grand Central Dispatch(GCD) 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。

GCD可用于多核的并行运算

GCD会自动利用更多的CPU内核(比如双核、四核)

GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

任务同步执行异步执行。两者的主要区别是:是否具备开启新线程的能力。

队列:采用FIFO(先进先出)的原则,串行队列并行队列

3. GCD的使用步骤

创建一个队列(串行队列或并行队列)

将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

这里说的并不准确,同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!

如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。

如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

作者:伯恩的遗产链接:https://www.jianshu.com/p/0b0d9b1f1f19 

1. 队列的创建方法

// 串行队列的创建方法

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

// 并行队列的创建方法

dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

对于并行队列,还可以使用dispatch_get_global_queue来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

2. 任务的创建方法

// 同步执行任务创建方法

dispatch_sync(queue, ^{

NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码

});

// 异步执行任务创建方法

dispatch_async(queue, ^{

NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码

});


4. GCD的基本使用


5. GCD线程之间的通讯

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 回到主线程

dispatch_async(dispatch_get_main_queue(), ^{

);

});

6. GCD的其他方法

1. GCD的栅栏方法dispatch_barrier_asyn

- (void)barrier

{

dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{


});

dispatch_barrier_async(queue, ^{


});

dispatch_async(queue, ^{


});

}


2. GCD的延时执行方法dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

// 2秒后异步执行这里的代码..

});

3. GCD的一次性代码(只执行一次)dispatch_once

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

// 只执行1次的代码(这里面默认是线程安全的)

});

4. GCD的快速迭代方法dispatch_apply

5. GCD的队列组dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。

我们可以先把任务放到队列中,然后将队列放入队列组中。

调用队列组的dispatch_group_notify回到主线程执行操作。

dispatch_group_t group =  dispatch_group_create();

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(), ^{

// 等前面的异步操作都执行完毕后,回到主线程...

});


NSLog("之前 - %@", NSThread.currentThread())dispatch_sync(dispatch_get_main_queue(), {

() -> Void in NSLog("sync - %@", NSThread.currentThread())

})

NSLog("之后 - %@", NSThread.currentThread())

答案:只会打印第一句:之前 - {number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。

解释:同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。

作者:伯恩的遗产链接:https://www.jianshu.com/p/0b0d9b1f1f19來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


NSOperation

1. NSOperation简介

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

NSOperation需要配合NSOperationQueue来实现多线程。因为默认情况下,NSOperation单独使用时系统同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。

因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。NSOperation实现多线程的使用步骤分为三步:

创建任务:先将需要执行的操作封装到一个NSOperation对象中。

创建队列:创建NSOperationQueue对象。

将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。

之后呢,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

2.NSOperation和NSOperationQueue的基本使用

1. 创建任务

NSOperation是个抽象类,并不能封装任务。我们只有使用它的子类来封装任务。我们有三种方式来封装任务。

使用子类NSInvocationOperation

使用子类NSBlockOperation

定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。

在不使用NSOperationQueue,单独使用NSOperation的情况下系统同步执行操作,下面我们学习以下任务的三种创建方式。

1. 使用子类- NSInvocationOperation:

// 1.创建NSInvocationOperation对象

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

// 2.调用start方法开始执行操作

[op start];

- (void)run

{

NSLog(@"------%@", [NSThread currentThread]);

}


从中可以看到,在没有使用NSOperationQueue、单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程执行操作,并没有开启新线程。

下边再来看看NSBlockOperation。

2. 使用子类- NSBlockOperation

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

// 在主线程

NSLog(@"------%@", [NSThread currentThread]);

}];

[op start];


但是,NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行。


3. 定义继承自NSOperation的子类


2. 创建队列

主队列

凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

NSOperationQueue *queue = [NSOperationQueue mainQueue];

其他队列(非主队列)

添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行

同时包含了:串行、并发功能

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

3. 将任务加入到队列中

前边说了,NSOperation需要配合NSOperationQueue来实现多线程。

那么我们需要将创建好的任务加入到队列中去。总共有两种方法

- (void)addOperation:(NSOperation *)op;

需要先创建任务,再将创建好的任务加入到创建好的队列中去

- (void)addOperationToQueue

{

// 1.创建队列

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 2. 创建操作

// 创建NSInvocationOperation

NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

// 创建NSBlockOperation

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{

for (int i = 0; i < 2; ++i) {

NSLog(@"1-----%@", [NSThread currentThread]);

}

}];

// 3. 添加操作到队列中:addOperation:

[queue addOperation:op1]; // [op1 start]

[queue addOperation:op2]; // [op2 start]

}


3. 控制串行执行和并行执行的关键

之前我们说过,NSOperationQueue创建的其他队列同时具有串行、并发功能,上边我们演示了并发功能,那么他的串行功能是如何实现的?

这里有个关键参数maxConcurrentOperationCount,叫做最大并发数

最大并发数:maxConcurrentOperationCount

maxConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。

当maxConcurrentOperationCount为1时,进行串行执行。

当maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。

-

4. 操作依赖

[op2 addDependency:op1];    // 让op2 依赖于 op1,则先执行op1,在执行op2

5. 一些其他方法

- (void)cancel;NSOperation提供的方法,可取消单个操作

- (void)cancelAllOperations;NSOperationQueue提供的方法,可以取消队列的所有操作

- (void)setSuspended:(BOOL)b;可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列

- (BOOL)isSuspended;判断暂停状态

注意:

这里的暂停和取消并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。

暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

以下摘自:关于iOS多线程,你看我就够了 - 简书

线程同步

所谓线程同步就是为了防止多个线程抢夺同一个资源造成的数据安全问题,所采取的一种措施。当然也有很多实现方法,请往下看:

1.互斥锁

@synchronized(self) {

//需要执行的代码块

}

2.同步执行

我们可以使用多线程的知识,把多个线程都要执行此段代码添加到同一个串行队列,这样就实现了线程同步的概念。当然这里可以使用 GCD 和 NSOperation 两种方案,我都写出来。

//GCD

//需要一个全局变量queue,要让所有线程的这个操作都加到一个queue中dispatch_sync(queue, ^{

NSInteger ticket = lastTicket;

[NSThread sleepForTimeInterval:0.1];

NSLog(@"%ld - %@",ticket, [NSThread currentThread]);

ticket -= 1;

lastTicket = ticket;

});

//NSOperation & NSOperationQueue//重点:

1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中

// 2. 设置 queue 的 maxConcurrentOperationCount 为 1

// 3. 如果后续操作需要Block中的结果,就需要调用每个操作的waitUntilFinished,阻塞当前线程,一直等到当前操作完成,才允许执行后面的。

waitUntilFinished 要在添加到队列之后!

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

NSInteger ticket = lastTicket;

[NSThread sleepForTimeInterval:1];

NSLog(@"%ld - %@",ticket, [NSThread currentThread]);

ticket -= 1; lastTicket = ticket;}];

[queue addOperation:operation];

[operation waitUntilFinished];

//后续要做的事作者:伯恩的遗产链接:https://www.jianshu.com/p/0b0d9b1f1f19來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.延迟执行

// 3秒后自动调用self的run:方法,并且传递参数:@"abc"[selfperformSelector:@selector(run:) withObject:@"abc"afterDelay:3];

// 创建队列dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 设置延时,单位秒double delay = 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{ // 3秒后需要执行的任务});

4.从其他线程回到主线程的方法


[selfperformSelectorOnMainThread:@selector(run) withObject:nilwaitUntilDone:NO];

dispatch_async(dispatch_get_main_queue(), ^{

});

[[NSOperationQueuemainQueue] addOperationWithBlock:^{

}];


Notification与多线程 - CSDN博客


在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,而不一定是在注册观察者的那个线程中。

也就是说,Notification的发送与接收处理都是在同一个线程中。

解决:“”重定向”,就是我们在Notification所在的默认线程中捕获这些分发的通知,然后将其重定向到指定的线程中。

一种重定向的实现思路是自定义一个通知队列(注意,不是NSNotificationQueue对象,而是一个数组),让这个队列去维护那些我们需要重定向的Notification。我们仍然是像平常一样去注册一个通知的观察者,当Notification来了时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,则将这个Notification存储到我们的队列中,并发送一个信号(signal)到期望的线程中,来告诉这个线程需要处理一个Notification。指定的线程在收到信号后,将Notification从队列中移除,并进行处理。

iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用 - 那一抹风情 - 博客园

1、信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。

其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。

2、信号量主要有3个函数,分别是:

//创建信号量,参数:信号量的初值,如果小于0则会返回NULL

dispatch_semaphore_create(信号量值)

//等待降低信号量

dispatch_semaphore_wait(信号量,等待时间)

//提高信号量

dispatch_semaphore_signal(信号量)

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

推荐阅读更多精彩内容