多线程单线程,同步异步,并发并行,串行队列并行队列,看这里就对了

多线程开发用了很久,但是一直没去深入了解。长久以来一直有一些迷惑。直到深入了解后,才发现了以前的理解有不少错误的地方。

单线程等于同步,多线程等于异步
  • 这种理解很直观,毕竟只有一个线程怎么异步?

Node.js表示不服,我就是单线程,我也能异步。谈一谈Node中的异步和单线程
看完这篇文章我明白了单线程也能异步,把IO等耗时的操作比作烧水,我可以在这个时候切菜,这就是异步啊。
等等,似乎有点不对,那io又谁来开启,又谁来通知cpu我已经结束了呢?
Node.js异步IO的实现,这篇文章解决了我的疑惑。

  • Node.js里面只有自己写的代码是跑在主线程上,但是内部并不是单线程的,由C编写的底层开启了线程做IO操作。

恍然大悟,我现在的理解就是,会有一个可运行的线程池在等待cpu的使用权。类似IO,网络请求这种耗时干等的操作,线程会放到需要等待的线程池中(阻塞),不会获取cpu的使用权,直到操作完成

这个理解了,并发和并行就很容易了。

  • 每个线程获得cpu的使用权的时间就是一个时间片,用完了就必须要等下次了。时间片非常短,人根本意识不到,感觉就是并行的,但其实只是"伪并行",也就是并发。

概念都讲结束了,现在可以谈谈iOS的多线程了。其实理论都一样,无非线程的获得,开启,结束等。但是iOS有个不同,他有GCD,那真是神器。
关于GCD的串行队列,并行队列,一直以来都有一个错误的理解:

队列就是线程,async就是另开线程,sync就是阻塞线程

实践才能出真知,要想真明白,async,sync,串行队列,并行队列,主队列,还是要亲自测一下才行。

  • 主线程下,是否开启新线程
//主队列
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"%@",[NSThread currentThread]);
});
    
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"%@",[NSThread currentThread]);
});

//串行队列
dispatch_queue_t ser_queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
    
dispatch_async(ser_queue, ^{
    NSLog(@"1-%@",[NSThread currentThread]);
});

dispatch_async(ser_queue, ^{
    NSLog(@"2-%@",[NSThread currentThread]);
});
    
dispatch_sync(ser_queue, ^{
    NSLog(@"3-%@",[NSThread currentThread]);
});

//并行队列
dispatch_queue_t con_queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(con_queue, ^{
    NSLog(@"1-%@",[NSThread currentThread]);
});

dispatch_async(con_queue, ^{
    NSLog(@"2-%@",[NSThread currentThread]);
});
    
dispatch_sync(con_queue, ^{
    NSLog(@"3-%@",[NSThread currentThread]);
});
  • 非主线程下,异步是否新开线程
    dispatch_queue_t ser_queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t con_queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

    
    dispatch_async(ser_queue, ^{
        NSLog(@"1-%@",[NSThread currentThread]);
        dispatch_async(ser_queue, ^{
            NSLog(@"1-%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(ser_queue, ^{
        NSLog(@"2-%@",[NSThread currentThread]);
        dispatch_async(con_queue, ^{
            NSLog(@"2-%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(con_queue, ^{
        NSLog(@"3-%@",[NSThread currentThread]);
        dispatch_async(con_queue, ^{
            NSLog(@"3-%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(con_queue, ^{
        NSLog(@"4-%@",[NSThread currentThread]);
        dispatch_async(ser_queue, ^{
            NSLog(@"4-%@",[NSThread currentThread]);
        });
    });

    dispatch_async(ser_queue, ^{
        NSLog(@"5-%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"5-%@",[NSThread currentThread]);
        });
    });

    dispatch_async(con_queue, ^{
        NSLog(@"6-%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"6-%@",[NSThread currentThread]);
        });
    });
  • 非主线程下,同步是否新开线程
    dispatch_async(ser_queue, ^{
        NSLog(@"1-%@",[NSThread currentThread]);
        dispatch_sync(ser_queue, ^{
            NSLog(@"1-%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(ser_queue, ^{
        NSLog(@"2-%@",[NSThread currentThread]);
        dispatch_sync(con_queue, ^{
            NSLog(@"2-%@",[NSThread currentThread]);
        });
    });

    dispatch_async(con_queue, ^{
        NSLog(@"3-%@",[NSThread currentThread]);
        dispatch_sync(con_queue, ^{
            NSLog(@"3-%@",[NSThread currentThread]);
        });
    });

    dispatch_async(con_queue, ^{
        NSLog(@"4-%@",[NSThread currentThread]);
        dispatch_sync(ser_queue, ^{
            NSLog(@"4-%@",[NSThread currentThread]);
        });
    });

    dispatch_async(ser_queue, ^{
        NSLog(@"5-%@",[NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"5-%@",[NSThread currentThread]);
        });
    });
    
    dispatch_async(con_queue, ^{
        NSLog(@"6-%@",[NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"6-%@",[NSThread currentThread]);
        });
    });
三个问题

结果就不贴出来了,还是自己亲自测下比较好。看看自己的想法和答案是否一致可是一件很快乐的事情。
基本上覆盖了所有可能。感觉更像是面向队列来的,线程的调度是系统自己分配的。

测下来感觉就是回答了三个问题:

  • async / sync 各种队列,Block 会在什么线程中执行
  • sync 什么时候会导致死锁
  • 阻塞线程和阻塞队列的区别
答案
队列 async sync
主队列 主线程 主线程
串行队列 新开线程( 始终只有一条 ) 当前提交Block的线程
并行队列 新开线程( 根据系统负载,会有多条 ) 当前提交Block的线程

死锁条件:

  1. 串行队列。
  2. 同步( sync )。
  3. 其次提交 block 的线程所在队列和把block放进去的队列是同一个。

阻塞线程:sync 会阻塞当前提交 Block 的线程,async 不会。
阻塞队列:其实应该没有设个概念,sync / async 都不能阻塞并行队列,串行队列里的 Block 肯定是一个一个执行的。而阻塞并行队列就涉及到下面的 dispatch_(a)sync。

dispatch_(a)sync

用于承上启下,所以对串行队列无效,就像一个栅栏一样,挡在了一个并行队列中间。

dispatch_barrier_(a)sync 只在自己创建的并发队列上有效,在全局 ( Global ) 并发队列、串行队列上,效果跟 dispatch_(a)sync 效果一样。
dispatch_barrier_sync 这个方法和 dispatch_barrier_async 作用几乎一样,都可以在并行queue 中当做栅栏。
唯一的区别就是:dispatch_barrier_sync 有 GCD 的 sync 共有特性,会阻塞提交 Block 的当前线程,而 dispatch_barrier_async 是异步提交,不会阻塞。

dispatch_barrier_sync / dispatch_sync 区别:dispatch_sync 不能阻塞并行队列,dispatch_barrier_sync 可以。

实现锁

所以用 GCD 可以实现锁,一般有 2 种实现方式:

串行队列:


- (void)serLock {
    dispatch_queue_t queue = dispatch_queue_create("com.ly.ser.queue", NULL);

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
        dispatch_async(queue, ^{   //这边sync和async效果一样。
            NSLog(@"1");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2");
        });
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"3");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4");
        });
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"5");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"6");
        });
    });
}

并行队列 + barrier:


- (void)conLock {
    dispatch_queue_t conQueue = dispatch_queue_create("com.ly.con.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_sync(conQueue, ^{   //读用这个
            NSLog(@"1");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2");
        });
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_barrier_async(conQueue, ^{    //写用这个
            NSLog(@"3");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4");
        });
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_sync(conQueue, ^{
            NSLog(@"5");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"6");
        });
    });
}

串行队列实现锁很好理解,因为串行队列必须一个一个 Block 执行。FMDB 就是用的这个实现的。
并行队列 + barrier 的实现更高级一点,读可以并行,写不行。

GCD是神器,还有好多需要学习的地方,推荐几篇经典的文章:
GCD扫盲篇巧谈GCD
GCD进阶篇
死锁,图文并茂,清晰易懂

作者:levi
comments powered by Disqus