同步(Synchrous) 与异步(Asynchronous)

同步和异步 通常用来形容一次方法调用。

同步:调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为(阻塞)

异步:调用一旦开始,方法调用就会立刻返回,调用者可以继续后续的操作。(非阻塞,更像是一个消息传递,异步方法通常会在另一个线程中执行)。

并发(Concurrency) 与并行(Parallelism)

并发与并行都可以表示两个或者多个任务一起执行,但是偏重点不同;

并行:并行是真正意义上的‘同时执行’,严格意义上来说,真实的并行只可能出现在多核CPU中,毕竟一个CPU一次只能执行一条指令。

并发:侧重于多个任务交替执行,多个任务之间也有可能是串行的

临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。在并行程序中,临界区资源是保护的对象,每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

阻塞(Blocking)和非阻塞(Non-Blocking)

通常形容多线程间的相互影响。

如果一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起,这种情况就是阻塞。如果占用资源的线程一直不释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。

非阻塞与之相反,它强调没有一个线程可以妨碍其他线程执行。

死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)

死锁、饥饿和活锁都属于多线程的活跃性问题。

死锁:多个线程由于竞争资源或者由于彼此通信而造成互相阻塞(等待)的现象;例如,如果线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

饥饿:指某一个或多个线程因为种种原因(比如线程优先级太低),无法获得所需要的资源,导致一直无法执行。

活锁:指的是线程并没有被阻塞,但某些条件没有满足(比如两个线程间不断的谦让,导致没有一个线程可以同时拿到所有资源而正常执行),导致一直重复尝试,失败,尝试,失败。

活锁可认为是一种特殊的饥饿;

活锁与死锁的区别:活锁有可能自行解开,死锁则不能。

并发级别

  • 阻塞

    当我们使用synchronized关键字,或者重入锁时,我们得到的就是阻塞的线程

  • 无饥饿(Starvation-Free)

    对于公平的锁来说,满足先来后到,那么饥饿就不会产生;对同一个资源的分配,不管新来的线程优先级多高,必须乖乖排队。

  • 无障碍(Obstruction-Free)

    非阻塞式调度。

    多个线程可以无障碍的执行,对无障碍线程来说,当多个线程一起修改了临界区资源,一旦检测到这种情况,他就会立刻对自己所做的修改进行回滚,以确保数据的安全。

    所以当临界区中存在严重的冲突时,所有的线程可能会不断地回滚自己的操作,造成没有一个线程可以走出临界区。在这种模式中,我们需要至少有一个线程能够在有限的时间内完成自己的操作。实现方法:依赖一个‘‘一致性标记’’,线程在操作之前,先读取并保存这个标记,在操作完成后,再次读取,检查这个标记是否被更改过,如果两者是一致的,则说明给资源访问没有冲突。如果不一致,则说明资源可能在操作过程中与其他写程序冲突,需要重新操作。而任何对资源有修改操作的线程,在修改数据前,都需要更新这个一致性标记,表示数据不再安全。

阻塞的控制方式是悲观策略,系统认为两个线程之间很有可能发生不幸的冲突,因此,以保护共享数据为第一优先级。

非无障碍的调用时一种乐观策略,它认为两个线程之间很有可能不会发生冲突,或者说这种概率不大。

  • 无锁(Lock-Free)

    与无障碍的区别,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。

  • 无等待(Wait-Free)

    在无锁的基础上更进一步进行扩展,它要求所有线程都必须在有限步内完全,这样就不会引起饥饿问题。

    一种典型的无等待结构就是RCU(Read-Copy-Update):对数据读写不加以控制,对于读线程不会被锁定也不会被冲突;对于写线程,先取得原始数据的副本,接着只修改副本数据,修改完成后,再适时回写。

Amdahl定律和Gustafson定律

$$加速比 = 优化前系统耗时 / 优化后系统耗时 $$

Amdahl定律:仅增加CPU数量而不降低程序的串行化比重,并不一定能提高系统性能。

Gustafson定律:如果可被并行化的代码所占比重足够多,那么加速比就能随着CPU的数量线性增长。

results matching ""

    No results matching ""