iOS多线程起底

一、三个人物:进程、线程、任务、

1.1 进程(process)

指一个正在运行中的可执行文件,每个进程包含独立的内存空间、系统资源以及端口权限,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出,该线程也退出。

1.2 线程(thread)

一个独立的代码的执行路径

1.3 任务(task)

我们需要执行的工作,一种抽象概念,就是一段执行代码

1.4 总结

参考了阮大神的文章,打个比方,cpu好比一个工厂,而进程就是工厂里的一个个车间,线程就是车间里的工人,他们共享车间里的空间、设备。

image.png

这图实在太形象了,出自阮大神

二、两个操作:互斥锁、信号量

2.1 互斥锁(mutex)

接着上面,每个厂房的大小不同,有的只能容纳一人,比如厕所🚾,里面有人的时候,其他人就进不去了。这代表一个线程使用某些内存时,其他线程必须等他结束,才能使用这块内存。
这时,在厕所门口加一把锁🔐,先到的人上锁🔐,后面的人看到上锁,就在门口排队,等锁打开再进来。互斥锁,就是防止多个线程同时使用同一块内存。

2.2 信号量(semaphore)

继续接着上面,有的车间能容纳n个人,比如厨房,当人数大于n人,外面的人只能在外面等着。怎么办😰?这时,就在原来一把锁🔐基础上挂n把钥匙。进去的人就取一把钥匙,出来时候再把钥匙挂上面。后面的人发现钥匙空了,就只能等着咯。这种就叫信号量

2.3 两者之间联系

image.png

2.4 iOS的几种常见锁

  • @synchronized
  • NSLock 对象锁
  • NSRecursiveLock 递归锁
  • NSConditionLock 条件锁
  • pthread_mutex 互斥锁(C语言)
  • dispatch_semaphore 信号量实现加锁(GCD)
  • OSSpinLock (暂不建议使用,原因参见这里

如果不加锁,会出现如下

- (void)viewDidLoad {
    [super viewDidLoad];
    _concurrentQueue = dispatch_queue_create("com.will.queue(concurrent)", DISPATCH_QUEUE_CONCURRENT);
    _tickets = 5;
    _lock = [[NSLock alloc] init];
}

- (IBAction)onclickRun:(id)sender {
    //售票窗口1
    dispatch_async(_concurrentQueue, ^{
        [self sellTickets];
    });
    
    //售票窗口2
    dispatch_async(_concurrentQueue, ^{
        [self sellTickets];
    });
}


- (void)sellTickets{
    while (1) {
        [NSThread sleepForTimeInterval:1];
        if (_tickets > 0) {
            _tickets --;
            NSLog(@"还剩%ld张票,Thread:%@", (long)_tickets, [NSThread currentThread]);
        }else{
            NSLog(@"票卖完了,Thread:%@", [NSThread currentThread]);
            break;
        }
    }     
}

打印如下,发生错乱


image.png
2.4.1 使出@synchronized

sellTickets方法做如下改变

- (void)sellTickets{
    while (1) {
        @synchronized(self){
            [NSThread sleepForTimeInterval:1];
            if (_tickets > 0) {
                _tickets --;
                NSLog(@"还剩%ld张票,Thread:%@", (long)_tickets, [NSThread currentThread]);
            }else{
                NSLog(@"票卖完了,Thread:%@", [NSThread currentThread]);
                break;
            }
        }
    }     
}
2.4.2 使出NSLock
while (1) {
    [_lock lock];
    [NSThread sleepForTimeInterval:1];
    if (_tickets > 0) {
        _tickets --;
        NSLog(@"还剩%ld张票,Thread:%@", (long)_tickets, [NSThread currentThread]);
    }else{
        NSLog(@"票卖完了,Thread:%@", [NSThread currentThread]);
        break;
    }
    [_lock unlock];
}

三、队列

3.1 队列vs进程vs线程

image.png

3.2 串行队列、并行队列、同步、异步

从线程的时效性,分为同步(synchronization)和异步(asynchronization)
从线程的执行,分为串行(serial)和并行(concurrency)

3.2.1 串行队列

队列里的线程是一个个执行,直到结束

3.2.2 并行队列

队列里的线程是同时结束,所有线程执行完,该队列结束

3.2.3 同步

就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,程序也不会接着往下执行

3.2.4 异步

当一个异步功能调用发出后,调用者不能立刻得到结果,可以继续干其他事情

3.2.5 之间联系
image.png
同步 异步
串行队列
image.png
image.png
并行队列
image.png
image.png

3.3 全局队列、主队列

名称 定义 注意事项
全局队列 属于并行队列 不要与barrier使用,barrier只能与自定义队列使用,可在全局队列里用异步方法执行耗时操作
主队列 属于串行队列,在主线程运行 不能与同步方法搭配使用,会阻塞主线程,造成死锁
3.3.1 全局队列里搭配异步
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i ++) {
    dispatch_async(q, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

打印如下

2017-11-14 17:12:08.641708+0800 tableView[14688:316052] <NSThread: 0x600000266d40>{number = 3, name = (null)} 2
2017-11-14 17:12:08.641736+0800 tableView[14688:316055] <NSThread: 0x600000267000>{number = 6, name = (null)} 0
2017-11-14 17:12:08.641743+0800 tableView[14688:316054] <NSThread: 0x600000266d80>{number = 5, name = (null)} 1
2017-11-14 17:12:08.641763+0800 tableView[14688:316053] <NSThread: 0x60400047e080>{number = 4, name = (null)} 3
2017-11-14 17:12:08.641769+0800 tableView[14688:316079] <NSThread: 0x60400047cb00>{number = 7, name = (null)} 4
2017-11-14 17:12:09.593578+0800 tableView[14688:316052] <NSThread: 0x600000266d40>{number = 3, name = (null)} 6
2017-11-14 17:12:09.593623+0800 tableView[14688:316053] <NSThread: 0x60400047e080>{number = 4, name = (null)} 8
2017-11-14 17:12:09.593647+0800 tableView[14688:316054] <NSThread: 0x600000266d80>{number = 5, name = (null)} 7
2017-11-14 17:12:09.593667+0800 tableView[14688:316055] <NSThread: 0x600000267000>{number = 6, name = (null)} 5
2017-11-14 17:12:09.593687+0800 tableView[14688:316079] <NSThread: 0x60400047cb00>{number = 7, name = (null)} 9

可见,全局队列同时进行多个操作


image.png
3.3.2 全局队列里搭配同步
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i ++) {
    dispatch_sync(q, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

打印如下

2017-11-14 17:13:39.821569+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 0
2017-11-14 17:13:39.821867+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 1
2017-11-14 17:13:39.822292+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 2
2017-11-14 17:13:39.822417+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 3
2017-11-14 17:13:39.822524+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 4
2017-11-14 17:13:39.822689+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 5
2017-11-14 17:13:39.822805+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 6
2017-11-14 17:13:39.822910+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 7
2017-11-14 17:13:39.823008+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 8
2017-11-14 17:13:39.823109+0800 tableView[14740:317904] <NSThread: 0x600000067e80>{number = 1, name = main} 9

所以操作都是在全局队列下进行的


image.png

可见是一个一个执行的

3.3.3 主队列里搭配异步
dispatch_queue_t q = dispatch_get_main_queue();
for (int i = 0; i < 10; i ++) {
    dispatch_async(q, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

因为主队列是串行队列,即使是异步的,也是一个一个完成


image.png

打印如下

2017-11-14 17:26:49.806347+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 0
2017-11-14 17:26:52.051380+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 1
2017-11-14 17:26:53.822800+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 2
2017-11-14 17:26:55.782335+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 3
2017-11-14 17:26:56.851570+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 4
2017-11-14 17:27:11.047209+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 5
2017-11-14 17:27:19.167697+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 6
2017-11-14 17:27:21.188779+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 7
2017-11-14 17:27:22.871865+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 8
2017-11-14 17:27:24.287835+0800 tableView[15142:330532] <NSThread: 0x60400007ff00>{number = 1, name = main} 9
3.3.4 主队列里搭配同步(会崩溃)
- (IBAction)onclickRun:(id)sender {
   dispatch_queue_t q = dispatch_get_main_queue();
    for (int i = 0; i < 10; i ++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}

同步任务需要马上执行,但主队列(串行队列)正在执行onclickRun:,所以需要等待onclickRun:执行完成。而onclickRun:需要等待主队列的同步任务执行完成,相互等待造成主线程阻塞,造成死锁。

image.png

如果我们这里自定义一个串行队列,则不会造成死锁,因为onclickRun:只需等待自定义的串行队列完成即可

- (IBAction)onclickRun:(id)sender {
   dispatch_queue_t q = dispatch_queue_create("com.will.image.decode", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i ++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}
image.png

四、生命周期

1499394752139363.png
1499394732413995.png

由此可见:

  1. NSThread创建的线程需要手动控制线程的销毁[NSThread exit];
  2. GCD和NSOperation都是在线程里任务执行完毕,自动销毁,需要注意,线程的任务多大量创建了临时变量,需要用@autoreleasepool来释放这些临时变量,比如:
dispatch_queue_t serialQueue = dispatch_queue_create("me.tutuge.test.autoreleasepool", DISPATCH_QUEUE_SERIAL);
    
//Run loop with autoReleasePool
dispatch_sync(serialQueue, ^{
    for (int i = 0; i < kIterationCount; i++) {
        @autoreleasepool {
            NSNumber *num = [NSNumber numberWithInt:i];
            NSString *str = [NSString stringWithFormat:@"%d ", i];            
            NSLog(@"%@", [NSString stringWithFormat:@"%@%@", num, str]);
        }
    }
});

//Run loop without autoReleasePool
dispatch_sync(serialQueue, ^{
    for (int i = 0; i < kIterationCount; i++) {
        NSNumber *num = [NSNumber numberWithInt:i];
        NSString *str = [NSString stringWithFormat:@"%d ", i];            
        NSLog(@"%@", [NSString stringWithFormat:@"%@%@", num, str]);
    }
});

使用了@autoreleasepool减少了约20M内存

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

推荐阅读更多精彩内容