(一)并发编程基础概念

0 阅读3分钟

并发与并行的概念

1.基础定义:

  • 并发指的是多个任务在重叠的时间段内执行,但不一定同时执行。在单核处理器上,通过时间片轮转实现多个任务的交替执行。
  • 并行指的是多个任务真正同时执行,这需要多核处理器的支持。

image.png

多线程编程的意义与应用场景

1.为什么需要多线程?

在工业软件开发中,多线程编程主要解决以下问题:

  • 提高性能:充分利用多核CPU资源
  • 改善响应性:避免UI线程被阻塞,一般UI的刷新线程都是主线程,而其他操作都是子线程
  • 实时处理:同时处理多个数据集
  • 资源利用:提高系统资源利用率

线程安全与数据竞争

1.数据竞争问题

数据竞争发生在多个线程同时访问同一内存位置,且至少有一个是写操作时。

原子操作概念

1.什么是原子操作?

原子操作是不可中断的操作,要么完全执行,要么完全不执行,不会出现部分执行的状态。 C++11提供了原子类型std::atomic,可以使用任意的类型作为模板参数。在多线程中如果使用了原子变量,其本身就保证了数据访问的互斥性,所以不需要使用互斥量来保护该变量了。 可以参考:cppreference.cn/w/cpp/atomi…

死锁、活锁和饥饿

死锁

死锁发生在两个或者多个线程互相等待对方释放资源时。 我们有两个线程A和B,还要两个锁1和2,考虑一种情况:线程A获取了锁1,然后尝试获取锁2,同时线程B获取了锁2,尝试获取锁1,两个线程都等待对方先释放自己想要的锁,就死锁了。

活锁

活锁中线程不断改变状态来响应其他线程,但无法继续前进。

举个下面的例子,下面的代码可能会造成两个线程同步的情况:

线程1时间线: 尝试 → 失败 → 等待50ms → 尝试 → 成功 → 使用100ms → 释放 → 等待50ms → 尝试...
线程2时间线: 尝试 → 失败 → 等待50ms → 尝试 → 失败 → 等待50ms → 尝试 → 失败...

也就是看似两个线程,但其实两个线程同步运行,线程2一直没有获取锁,造成看似线程1释放了锁的状态,但其实别的线程也没有获取到过,称为活锁。

饥饿

饥饿发生在某个线程长期无法获得所需资源。造成饥饿的原因有很多,例如优先级导致的饥饿、不公平锁导致的饥饿、CPU密集型线程导致的饥饿,举个例子:

CPU密集型的线程,在多线程竞争中是处于优势地位的,就会导致IO线程处于饥饿状态,因为IO线程竞争不过CPU密集型的线程。造成IO线程一直没有被给资源运行,称为饥饿。 就像两个幼崽吃奶,一个含着奶头不松口,那另一个就可能一直处于饥饿状态。

总结

  • 并发是逻辑上的同时,并行是物理上的同时
  • 数据竞争是并发编程中最常见的问题
  • 原子操作提供硬件级别的同步保证
  • 死锁、活锁、饥饿是三种不同的资源竞争问题