iOS多线程起底

作者: 踏云小子 | 来源:发表于2017-11-14 15:06 被阅读14次

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

    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内存

    相关文章

      网友评论

        本文标题:iOS多线程起底

        本文链接:https://www.haomeiwen.com/subject/gatfvxtx.html