iOS学习笔记11-多线程入门

一、iOS多线程

iOS多线程开发有三种方式:
  1. NSThread
  1. NSOperation
  2. GCD

iOS在每个进程启动后都会创建一个主线程更新UI要在主线程上,所以也称为UI线程,是其他线程的父线程。

线程和进程的区别傻傻分不清楚:
  • 线程(thread):用于指代独立执行的代码段。
  • 进程(process):用于指代一个正在运行的可执行程序,它可以包含多个线程。
多线程加载图片

二、NSThread

NSThreadhi轻量级的多线程开发,需要自己管理线程生命周期

创建线程主要实现方法:
/* 直接将操作添加到新线程中并执行,该方法无法拿到线程对象 */
+ (void)detachNewThreadSelector:(SEL)selector /* 方法名 */
                       toTarget:(id)target /* 调用对象 */
                     withObject:(id)argument; /* 参数 */
/* 创建线程对象,初始化线程任务,调用start方法启动线程 */
- (instancetype)initWithTarget:(id)target /* 调用对象 */
                      selector:(SEL)selector /* 方法名 */
                        object:(id)argument;/* 参数 */
实际使用:
/* 创建一个线程,初始化任务,创建线程并不会启动线程 */
NSThread *thread = [[NSThread alloc] initWithTarget:self 
                                           selector:@selector(loadImage) 
                                             object:nil];
[thread start];//启动线程

/* 直接将操作添加到新线程并启动线程 */
[NSThread detachNewThreadSelector:@selector(loadImage) 
                         toTarget:self 
                       withObject:nil];
  • 每个线程的实际执行顺序并不一定按启动顺序执行
  • 如果是单核CPU,多线程是并发,分时间片切换执行不同线程,多核CPU的多线程才是真正的并行运算。
线程状态分为:
  • isExecuting(正在执行)
  • isFinished(已经完成)
  • isCancellled(已经取消)
下面是常用方法来控制线程:
/* 让线程休眠 */
+ (void)sleepUntilDate:(NSDate *)date;/* 让当前执行线程休眠到某个时间 */
+ (void)sleepForTimeInterval:(NSTimeInterval)time;/* 让当前执行线程休眠固定多少秒 */
/* 终止线程 */
+ (void)exit;
/* 停止线程,注意在主线程中调用仅仅只是设置线程状态,不会立刻停止线程 */
- (void)cancel;
实例:
NSThread *thread = threads[i];
//判断线程是否完成,如果没有完成则设置为取消状态
//注意设置为取消状态仅仅是改变了线程状态而言,并不能立刻终止线程
if ( !thread.isFinished ) {
    [thread cancel];
}
//线程休眠2秒
[NSThread sleepForTimeInterval:2.0];

我们知道了控制单个线程,怎么在线程之间进行通信呢?

下面是线程间通信的常用方法:
/* 在后台执行一个操作,本质就是重新创建一个线程执行当前方法 */
- (void)performSelectorInBackground:(SEL)aSelector
                         withObject:(id)arg;
/* 在指定的线程上执行一个方法,需要用户创建一个线程对象 */
- (void)performSelector:(SEL)aSelector
               onThread:(NSThread *)thr
             withObject:(id)arg
          waitUntilDone:(BOOL)wait;
/* 在主线程上执行一个方法 */
- (void)performSelectorOnMainThread:(SEL)aSelector
                         withObject:(id)arg
                      waitUntilDone:(BOOL)wait;

三、NSOperation

只要将NSOperation放入NSOperationQueue线程队列中,就会启动执行。
NSOperationQueue负责管理、执行所有的NSOperation,这样更加容易管理线程总数控制线程之间的依赖

NSOperation是个基类,我们直接使用的是它的子类:
  • NSInvocationOperation:调用方法SEL的方式执行线程可以使用它
  • NSBlockOperation:调用Block的方式执行线程可以使用它
下面是使用实例:
//创建Invocation线程
NSInvocationOperation *invocationOperation = 
        [[NSInvocationOperation alloc] initWithTarget:self 
                                             selector:@selector(loadImage) 
                                               object:nil];
//注意如果直接调用start方法,则此操作会在主线程中调用
// [invocationOperation start];//一般不会这么操作,而是添加到NSOperationQueue中
//创建线程队列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 5;//设置最大并发线程数
//注意添加到线程队列后,队列里的线程就会开始执行
[operationQueue addOperation:invocationOperation];

//创建Block线程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    [self loadImage:[NSNumber numberWithInt:0]];
}];
//添加进线程队列
[operationQueue addOperation:blockOperation];

如果觉得添加NSBlockOperation线程麻烦,还有个简单的方法,是NSOperationQueue的对象方法:

// 快捷添加NSBlockOperation
[operationQueue addOperationWithBlock:^{
    [self loadImage:[NSNumber numberWithInt:0]];
}];

我说过NSOperationQueue可以控制线程之间的依赖,这是怎么一回事呢?想象一种场景,加载多个图片,但我想最后一张图片一定要先加载,其他图片加载的前提就是最后一张图片要加载完成,这时候就可以使用依赖了。非常简单。

多图片加载实例:
- (void)loadImageWithMultiThread{
    int count = ROW_COUNT*COLUMN_COUNT;
    //创建线程队列
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount = 5;//设置最大并发线程数
    //创建加载最后一张图片的线程
    NSBlockOperation *lastBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:(count-1)]];
    }];
    //创建多个线程用于下载其他图片
    for (int i=0; i<count-1; ++i) {
        //创建多线程操作
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //设置依赖操作为最后一张图片加载操作,只有最后一张图片加载完成,其他图片才开始陆续加载
        [blockOperation addDependency:lastBlockOperation];
        [operationQueue addOperation:blockOperation];
    }
    //将最后一个图片的加载线程加入线程队列
    [operationQueue addOperation:lastBlockOperation];
}
依赖的应用-多图片加载-最后一个图片先加载,其他才开始加载

四、GCD

GCD中也有一个类似于NSOperationQueue的队列,GCD统一管理整个队列中的任务,GCD是C语言下的框架。

GCD中的队列分为并行队列和串行队列:
  • 串行队列(serial):只有一个线程,加入到队列中的操作按添加顺序依次执行。
  • 并发队列(concurrent):有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理,但不是顺序的。

其实在GCD中还有一个特殊队列就是主队列,用来执行主线程上的操作任务。

GCD执行方式也分为异步执行和同步执行:
  • dispatch_async(异步执行) :不管队列中的任务执行完还是没执行完,直接将任务追加到队列
  • dispatch_sync(同步执行) : 等队列中的任务执行完,再将任务追加到队列
串行队列和并发队列的创建:
/*创建一个队列
 第一个参数:队列名称
 第二个参数:队列类型,DISPATCH_QUEUE_SERIAL串行,DISPATCH_QUEUE_CONCURRENT并发
 注意:GCD的queue不是指针类型
*/
dispatch_queue_t serialQueue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

/* 使用dispatch_get_global_queue() 方法取得一个全局的并发队列 */
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

队列的异步执行和同步执行:
//异步执行队列任务,第一个参数是队列,第二个参数是任务Block
dispatch_async(queue, ^{
    [self loadImage:[NSNumber numberWithInt:i]];
}); 
//同步执行队列任务,第一个参数是队列,第二个参数是任务Block
dispatch_sync(queue, ^{
    [self loadImage:[NSNumber numberWithInt:i]];
}); 
  • 在GDC中一个操作是多线程执行还是单线程执行,取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中并发执行
  • 串行队列可以按顺序执行,并行队列的异步方法无法确定执行顺序。
  • 更新UI界面最好采用同步方法,其他操作采用异步方法。
GCD的其他任务执行方法:
/* 重复执行某个任务,为了不阻塞线程可以使用dispatch_async()包装一下再执行 */
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

/* 单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)*/
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

/* 常用:延迟delayInSeconds秒后在队列queue执行block操作 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_queue_t queue, dispatch_block_t block);

五、线程同步

为什么需要线程同步?因为要解决多线程的资源抢夺的问题

1.NSLock同步锁

//初始化锁对象
NSLock *myLock = [[NSLock alloc] init];
//加锁
[myLock lock];//加锁后,下面的代码只能有一个线程进入执行
if (_imageNames.count > 0) {
    name = [_imageNames lastObject];
    [_imageNames removeObject:name];
}
//使用完解锁
[myLock unlock];

2.@synchronized代码块

//线程同步
@synchronized(self){
    if (_imageNames.count > 0) {
        name = [_imageNames lastObject];
        [_imageNames removeObject:name];
    }
}

线程同步就不细讲了,这是一个大块知识。

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

推荐阅读更多精彩内容