并发编程 - 探索一

0x01 什么是并发

要理解并发首选咱们来区分下并发和并行的概念。java

并发:表示在一段时间内有多个动做存在。
并行:表示在同一时间点有多个动做同时存在。编程

例如:
此刻我正在写博客,可是我写着写着停下来吃一下东西(菠萝片)再写、再吃。这两个动做在一段时间内都在发生着,这能够理解为并发。
另外一方面我在写这个博客的同时我在听音乐。那么同时存在的两个动做(写博客、听音乐)是同时在发生的这就是所谓的并行。安全

从上面两个概念明显能够感觉到并发是包含并行操做。因此咱们一般说的并发编程对于cpu来讲有多是并发的在执行也有多是交替的在执行。多线程


说到这里你可能会问为何咱们须要并发编程?
在求解单个问题的时候凡是涉及多个执行流程的编程模式都叫并发编程。并发

0x02 为何须要并发

  1. 硬件的发展推进软件的进度,多核时代的到来ide

  2. 应用系统对性能和吞吐量的苛刻要求性能

  3. 大数据时代的到来测试

  4. 移动互联网、云计算对计算体系的冲击大数据

0x03 并发编程方式

Java:多进程/多线程的并发实现方式编码

Go:协程--用户态实现的多线程方式(goroutine)

Java并发模型

在介绍java并发模型前咱们来介绍下系统对多线程的实现方式。系统支持用户态线程和内核态两种线程的实现方式,内核态线程是cpu去调度的最小单位,因此这牵涉到用户态线程和内核态线程之间的映射关系,用户态线程:内核态线程 = 1:1 、 N:1 、 M:N。

1:1 这种映射关系充分利用多核的优点,可是这种方式在用户态进行线程切换的过程当中都会涉及到内核态线程之间的切换,切换开销大。(主要涉及内核线程运行时上下文的保存与恢复)
N:1 无法充分利用多核的优点,可是这种因为是用户态的内存切换不涉及内核态线程之间的切换因此这种映射关系在线程之间切换代价小。
M:N 这种是上面两种映射关系的结合体,集合了上面两种映射关系的优点,可是这也增长了线程之间这种映射关系的调度复杂度。

Java的并发编程模式是经过1:1这种映射关系来实现线程之间的并发调度。

Go并发模型

Go的并发模式是经过M:N这种方式来实现并发调度的。
Go调度器中有三种重要结构:M(posix thread)、P(调度上下文,通常数量设置为和机器内核数相同,这样能充分发挥机器的并发性能)、G(goroutine)。

clipboard.png

一个调度上下文能够包含多个Goroutine,多个上下文因此能够全部的Goroutine都能并发的运行在CPU的多核上面。
若是有Goroutine发现找不到调度上下文,就会被放到global runqueue中,等悠闲的调度上下文来捞取它进行调度。
若是调度上下文上面挂载的全部Goroutine都已经执行完毕,此时他会去global runqueue中获取Goroutine,若是发现此时没有获取到,则会去别的调度上文中抢Goroutine,通常一次抢都是抢此时被抢调度上下文的一半Goroutine,确保充分利用M去被多核调度。

0x04 并发带来的问题

在享受并发编程带来的高性能、高吞吐量的同时,也会由于并发编程带来一些意想不到弊端。

  1. 资源的消耗,要管理这么多用户线程、内核线程、用户线程内核线程之间的切换调度,上下文等等这些都是因为引用了并发编程所带来的额外消耗。

  2. 并发过程当中多线程之间的切换调度,上下文的保存恢复等都会带来额外的线程切换开销。

  3. 编码、测试的复杂性。和咱们生活中的例子很相像,三五我的一块儿出去活动很容易把控,若是带着几10、上百人的团队出去活动这些都会带来额外的管理上的开销。

真的是有阳关的地方就有黑暗啊!

上面这些都是咱们无法避免的一些问题,要引用并发编程必然会要付出点额外的代价才行。可是并发编程还带来了一个不能忽视的问题,线程之间对同一资源的竞争访问,形成内存对象状态和本身的想象千差万别。

Java线程和内存对象之间的交互

java线程对内存的理解分为两部分:线程工做内存(每一个线程独有的)、共享内存也叫主内存(全部的线程所共有的),下面是java线程对内存中Count对象的一次修改操做。

clipboard.png

从主线程中读取Count对象放入线程工做内存,后面的读取修改都在线程工做内存中,最后(更新到主内存的时间不是肯定的,可能会插入别的操做在store、write之间)更新到主内存中。全部的上述操做都是顺序执行的,可是不保证连续执行。

volatile变量、synchronized同步块

volatile变量、synchronized块执行结束后能保证每次去更新的值都会当即写入到主内存中。
volatile变量不少人会认为这样就是线程安全的,可是经过上面咱们能够看到若是两个线程同时去读了一个volatile变量,最后一前一后更新到主内存中,这样也会出现写丢失的状况,因此volatile不能保证线程安全。

0x05 并发实战

1) 定义线程池

private static final ExecutorService executor = Executors.newFixedThreadPool(20);

2)定义并发服务

CompletionService<Result> completionService = new 
      ExecutorCompletionService<Result>(executor);

3)提交并发任务

completionService.submit(new Callable<void>() {
    @Override
    public void call() throws Exception {
        return ;
    }
});

4)等待并发结果

for (int i = 0; i < taskSize; ++i) {
    Future<ModelScoreResult> future = completionService.poll(TIME_OUT,
                    TimeUnit.SECONDS);
    Result result = future.get();
}
相关文章
相关标签/搜索