互斥

互斥是一种用于保护共享资源的机制,实现对资源的独占访问。

  • 确保同一时间只有一个线程可以访问该资源,避免出现数据竞争和冲突。通过使用互斥锁(Mutex),只有获得锁的线程才能进入临界区(访问共享资源的代码段),其他线程需要等待锁的释放。

实现方式

  • 二进制信号量
  • 临界区(Critical Section):一段代码区域,只能由一个线程执行,以保护共享资源。

同步

线程同步是指通过一定的机制确保多个线程按照一定的顺序和规则共享资源或进行协调工作

  • 避免出现并发访问导致的问题,例如竞态条件数据不一致等。
  • 同步可以通过互斥来实现,但也可以使用其他的同步机制,如信号量、条件变量、事件等。

应用场景

  • 信号量(Semaphore):用于控制对共享资源的访问。
  • 条件变量(Condition Variable):用于线程之间的通信,等待某个条件满足后再继续执行。

实例

  • 生产者-消费者问题:生产者生成数据放入缓冲区,消费者从缓冲区取出数据。需要同步以防止缓冲区溢出或空读。

异步

任务或操作可以独立于当前线程继续执行,而不需要等待其他任务完成。

协同(Cooperation)

多个进程或线程之间相互合作,共同完成某个任务。这种合作通常涉及到数据交换和任务分解。

应用场景

  • 管道(Pipe):进程间通信的一种方式,一个进程的输出可以作为另一个进程的输入。
  • 消息队列(Message Queue):进程间通信的一种机制,一个进程发送消息到队列,另一个进程从队列中接收消息。

实例

一个视频处理系统中,一个线程负责读取视频帧,另一个线程负责处理这些帧,第三个线程负责显示处理后的帧。这些线程需要协同工作以确保视频流的流畅播放。

调度

调度是指操作系统根据某种策略选择下一个要运行的进程或线程,以优化系统的性能和资源利用率。

应用场景

  • 进程调度(Process Scheduling):决定哪个进程应该占用CPU。
  • 线程调度(Thread Scheduling):决定哪个线程应该占用CPU。
  • I/O 调度(I/O Scheduling):决定磁盘I/O请求的顺序。

实例

  • 优先级调度:根据进程的优先级选择下一个要运行的进程。
  • 时间片轮转(Round Robin):每个进程轮流占用CPU一段时间。

并发

某一时间段内能够处理多个任务的能力。

并发有可能会打断当前执行的进程,然后替切换成其他进程执行。

C语言共享变量count++对应机器指令:

mov eax, [count]
inc eax
mov [count], eax

原子操作⭐

原子操作是指在执行过程中不会被中断的操作,要么完全执行成功,要么完全不执行。 是并发编程中非常重要的概念,用于确保多线程或多进程环境下的数据一致性并发访问的正确性。

  • 原子布尔操作:在多线程环境中,对于一个布尔变量的读取和写入操作,可以使用原子类型来确保原子性。
#include <atomic>
 
std::atomic<bool> atomicBool;
 
// 原子写入操作
atomicBool.store(true);
 
// 原子读取操作
bool value = atomicBool.load();
  • 原子递增操作:在多线程环境中,多个线程同时递增一个整型变量可能引发竞态条件,使用原子递增操作可以避免这种问题。
#include <atomic>
 
std::atomic<int> atomicInt;
 
// 原子递增操作
atomicInt++;
  • 原子比较和交换操作:在多线程环境中,需要修改一个变量的值时,使用原子比较和交换操作可以保证在并发情况下的一致性。
#include <atomic>
 
std::atomic<int> atomicInt;
int expected = 10;
int newValue = 20;
 
// 原子比较和交换操作,如果 atomicInt 的当前值等于 expected,则将其替换为 newValue
atomicInt.compare_exchange_strong(expected, newValue);

原子操作的特点

  • 不可分割性indivisibility):原子操作不可被中断,要么全部执行成功,要么全部不执行,不存在中间状态。
  • 完整性completeness):原子操作在执行过程中不会被其他并发操作所干扰,能够确保数据的一致性
  • 非并发干扰no concurrent interference):并发执行的多个原子操作之间不会相互干扰,保证并发访问的正确性。

使用原子操作时的注意事项

  • 明确定义操作范围:确保将原子操作限制在必要的范围内,避免过度使用原子操作。
  • 考虑性能开销:原子操作可能引入一定的性能开销,因此在必要性和性能之间需要进行权衡。
  • 避免死锁和饥饿:在使用锁机制时,要注意避免死锁和饥饿等并发编程常见问题。

并行

同一时间能够处理多个任务的能力。

阻塞

阻塞是指当线程遇到某些条件而无法继续执行时,暂时挂起线程的状态。

  • 当线程发起一个阻塞式的操作(如等待I/O、获取锁、等待条件满足等)时,它会进入阻塞状态,暂停执行,直到条件满足或被唤醒。

非阻塞

任务或操作在执行过程中不会暂停等待条件满足,而是立即返回并继续执行其他任务。非阻塞操作可以持续进行,而不会受到其他任务的影响。