IOS多线程编程简介

IOS多线程编程简介

基本概念

线程: 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程是独立调度和分派的基本单位。同一进程中的多条线程将共享该进程中的全部系统资源,但是自有调用堆栈和寄存器环境。
进程: 进程是计算机中已运行程序的实体。其本身并不是几部运行单位,是线程的容器。
任务: 任务(task)用于指代抽象的概念,表示需要执行工作,具体可以是一个函数或者一个block。

关于进程和线程的描述推荐《进程与线程的一个简单解释》,浅显易懂。

IOS常用的多线程编程技术

IOS常用的多线程编程技术包括:NSThread、Cocoa NSOperation、GCD(grand central dispatch)。其优缺点对比如下表所示:

多线程技术 优点 缺点
NSThread 轻量级最低,相对简单 需要手动管理所有的线程活动(生命周期,休眠,同步等)线程同步对数据的加锁会有一定的系统开销
Cocoa NSOperation 自带线程周期管理,可只关注自己处理逻辑 NSOperation是面向对象的抽象类,实现只能是其子类(NSInvocationOperation和NSBlockOperation),对象需要添加到NSOperationQueue队列里执行
GCD 效率高,可避免并发陷阱 基于C而非OC实现

如果对性能效率要求较高,处理大量并发时用GCD,简单而安全的可用NSOPeration或者NSThread。Apple推荐使用GCD

NSThread

NSThread具体的底层实现机制是Mach线程,实现技术有三种Cocoa threads、POSIX threads(UNIX)、Multiprocessing Services。

使用方式

  1. 显式创建方式:
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
+(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument 
  1. 隐式创建方式:
[Object performSelectorInBackground:@selector(doSomething)withObject:nil];

代码示例

- (void)testNSThread{
    _lock = [[NSLock alloc] init];
    _condition = [[NSCondition alloc] init];
    self.total = 20;
    
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread1 setName:@"Thread--1"];
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread2 setName:@"Thread--2"];
    [thread2 start];
    
//    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
//    [self performSelectorInBackground:@selector(run) withObject:nil];
}


- (void)run{
    while (YES) {
        [self.lock lock];
        if(self.total >= 0){
            [NSThread sleepForTimeInterval:0.09];
            NSInteger count = 20 - self.total;
            NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
            self.total--;
        }else{
            break;
        }
        [self.lock unlock];
    }
}

分析说明:

NSThread中线程内存管理,是否循环引用,同步问题都需要手动管理。除了代码中的lock还可以用@synchronized来简化NSLock的使用。

- (void)doSomeThing:(id)anObj 
{ 
    @synchronized(anObj) 
    { 
        // Everything between the braces is protected by the @synchronized directive. 
    } 
} 

还可以用NSCondition中的wait消息设置等待线程,然后通过signal消息 发送信号的方式,在一个线程唤醒另外一个线程的等待。
线程间通信:

//在指定线程上执行操作
//在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
[self performSelector:@selector(run) onThread:threadName withObject:nil waitUntilDone:YES]; 
//在当前线程执行操作
[self performSelector:@selector(run) withObject:nil];

获取线程:

NSThread *current = [NSThread currentThread];
NSThread *main = [NSThread mainThread];

Cocoa NSOperation

使用方式

NSOperation的使用方式有两种:

  • 使用NSInvocationOperation和NSBlockOperation
  • 自定义NSOperation子类。

代码示例

-(void)testNSOPeration{
    self.total = 20;
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
  
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self run2];
    }];
//    [operation1 start];//当前线程直接执行
    [operation2 addDependency:operation1];
//    [operation2 addExecutionBlock:^{
//        [self run];
//    }];
    [queue addOperation:operation2];
//    queue.maxConcurrentOperationCount = 1;
}

- (void)run2{
    while (YES) {
        if(self.total >= 0){
//            [NSThread sleepForTimeInterval:0.09];
            NSInteger count = 20 - self.total;
            NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
            self.total--;
        }else{
            break;
        }
    }
}

自定义NSOperation子类
自定义NSOperation子类需要实现start、main、isExecuting、isFinish、isConcurrnet(asynchronous)方法。

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end
 
@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}
 
- (BOOL)isConcurrent {
    return YES;
}
 
- (BOOL)isExecuting {
    return executing;
}
 
- (BOOL)isFinished {
    return finished;
}
@end

分析说明

NSOperationQueue 是一个并行队列,可以通过设置maxConcurrentOperationCount来设定最大并行操作数,对于自定义实现的NSOperation子类,可以通过实现isConcurrent(isAsynchronous)方法来制定具体的操作是否同步执行。

GCD

使用方式

GCD的工作原理是让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
队列的创建

  • 主线程队列(串行):
    dispatch_get_main_queue()
  • 全局队列(并行):
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    第一个参数说明队列的优先级,第二个参数暂未用到,默认0。根据优先级划分,共有四个全局队列
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
  • 自定义队列:
    dispatch_queue_create(queuename, attr)
    第一个参数制定队列名称,自定义队列可以是串行也可以是并行的,由第二个参数attr决定。
DISPATCH_QUEUE_SERIAL //(NULL) 串行
DISPATCH_QUEUE_SERIAL_INACTIVE
DISPATCH_QUEUE_CONCURRENT //并行

多线程执行

  • 同步执行:
    dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
  • 异步执行:
    dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
  • 一次性执行:

tatic dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ code to be executed once });

  • 延时执行:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ code to be executed after a specified delay });

  • 循环迭代执行:
    dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
    能并发地执行不同的迭代。这个函数是同步的,所以和普通的 for 循环一样,它只会在所有工作都完成后才会返回。
  • 栅栏(barrier)执行:
    dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
    栅栏执行的意思是:在barrier前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。
  • 分组执行:
    dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block)
    dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作
    信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(semaphore); 
dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds); dispatch_semaphore_wait(semaphore, timeoutTime)

Dispatch Source

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatchQueue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, intervalInSeconds * NSEC_PER_SEC, leewayInSeconds * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        code to be executed when timer fires
    });
    dispatch_resume(timer);

代码示例

栅栏执行:

- (void)testBarrier{
    dispatch_queue_t queue = dispatch_queue_create("com.zyw.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:2];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");   
    });
}

输出:

2016-12-02 17:17:24.710 TestThread[1434:164682] dispatch_async1
2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_async2
2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_barrier_async
2016-12-02 17:17:28.720 TestThread[1434:164681] dispatch_async3

分组执行:

- (void)testGroup{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"EndAllgroupTask");
    });
//    第二种使用方法
//    dispatch_group_enter(group);
//    dispatch_group_leave(group);
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        
//    });
}

输出:

2016-12-02 17:11:50.592 TestThread[1407:161853] group1
2016-12-02 17:11:51.591 TestThread[1407:161852] group2
2016-12-02 17:11:52.589 TestThread[1407:161855] group3
2016-12-02 17:11:52.590 TestThread[1407:161635] EndAllgroupTask

信号量:

- (void)testSemaphore{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    for (NSInteger i = 0 ; i < 10; i++) {
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"*****%zd", i);
            [NSThread sleepForTimeInterval:1];
            dispatch_semaphore_signal(semaphore);
        });
    }
}

输出:

2016-12-02 17:36:11.071 TestThread[1496:173204] *****1
2016-12-02 17:36:11.071 TestThread[1496:173207] *****2
2016-12-02 17:36:11.071 TestThread[1496:173203] *****0
2016-12-02 17:36:11.071 TestThread[1496:173242] *****3
2016-12-02 17:36:11.071 TestThread[1496:173243] *****4

2016-12-02 17:36:12.074 TestThread[1496:173244] *****5
2016-12-02 17:36:12.074 TestThread[1496:173250] *****9
2016-12-02 17:36:12.074 TestThread[1496:173246] *****7
2016-12-02 17:36:12.074 TestThread[1496:173245] *****6
2016-12-02 17:36:12.074 TestThread[1496:173247] *****8

Dispatch Source

- (void)testSource{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 5);
    dispatch_source_set_timer(timer, start, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"******Hello");
    });
    //启动
    dispatch_resume(timer);
}

同步异步串行并行简单常用未列出

分析说明

同步异步和并行串行

并行队列 串行队列 主队列
异步执行 开启多个新的线程,任务同时执行 开启一个新的线程,任务按顺序执行 不开启新的线程,任务按顺序执行
同步执行 不开启新的线程,任务按顺序执行 不开启新的线程,任务按顺序执行 死锁

对于GCD中的block执行,都有对应的function执行函数,比如:

dispatch_sync_f(dispatch_queue_t  _Nonnull queue, void * _Nullable context, dispatch_function_t  _Nonnull work)
dispatch_async_f(dispatch_queue_t  _Nonnull queue, void * _Nullable context, dispatch_function_t  _Nonnull work)

信号量
dispatch_semaphore_create(N);N>=0时可以正常执行,当N小于0时一直等待
代码示例的解释:创建了一个初使值为5的semaphore,每一次for循环都会创建一个新的线程(具体看GCD的线程池,此处认为是新建),线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了5个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为5的一个线程队列。
Dispatch Source
它基本上就是一个低级函数的 grab-bag,可监听事件如下:
DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。
DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
DISPATCH_SOURCE_TYPE_READ:读文件事件。
DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。

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

推荐阅读更多精彩内容