当前位置: 首页 > article >正文

【操作系统】同步与异步,同步与互斥

文章目录

  • 同步与异步,同步与互斥
    • 1. 同步与异步
      • 1.1 同步
      • 1.2 异步
      • 1.3 异步的实现方式
      • 1.4 异步的工作原理
    • 2. 同步与互斥
      • 2.1 为什么需要互斥?
      • 2.2 互斥锁
      • 2.3 条件变量
      • 2.4 信号量
    • 3. 总结

在这里插入图片描述

同步与异步,同步与互斥

在编程世界中,尤其是当我们涉及到多线程、并发编程时,同步异步互斥这些概念是绕不开的。它们不仅是理解并发编程的基础,也是写出高效、安全代码的关键。今天,我们就来聊聊这些概念,尽量用通俗易懂的方式来解释它们。

1. 同步与异步

1.1 同步

同步,顾名思义,就是“一起行动”。在编程中,同步指的是任务按照一定的顺序依次执行,前一个任务完成后,后一个任务才能开始。同步操作通常是阻塞的,也就是说,当前任务没有完成之前,后续的任务必须等待。

举个例子,假设你在写一个程序,需要从网络上获取数据,然后对数据进行处理。如果你使用同步的方式,代码可能会是这样的:

void fetchDataAndProcess() {
    // 同步获取数据
    std::string data = fetchDataFromNetwork();
    
    // 数据处理
    processData(data);
}

在这个例子中,fetchDataFromNetwork() 是一个同步操作,程序会一直等待,直到数据从网络返回后,才会继续执行 processData(data)。这种方式简单直接,但有一个明显的缺点:如果网络请求很慢,程序就会一直卡在那里,无法做其他事情。

1.2 异步

异步则不同,异步操作是非阻塞的。当你发起一个异步操作时,程序不会等待这个操作完成,而是继续执行后续的代码。异步操作通常会在后台执行,等到操作完成后,再通过某种方式通知程序。

继续上面的例子,如果我们使用异步的方式来获取数据,代码可能会是这样的:

void fetchDataAndProcess() {
    // 异步获取数据
    fetchDataFromNetworkAsync([](std::string data) {
        // 数据处理
        processData(data);
    });
    
    // 继续执行其他任务
    doOtherTasks();
}

在这个例子中,fetchDataFromNetworkAsync() 是一个异步操作,它不会阻塞程序的执行。程序会立即继续执行 doOtherTasks(),而网络请求则在后台进行。当数据返回时,通过回调函数 [](std::string data) 来处理数据。

1.3 异步的实现方式

异步编程的实现方式有很多种,常见的有:

  • 回调函数:就像上面的例子一样,异步操作完成后调用一个回调函数来处理结果。
  • Future/Promise:通过 std::futurestd::promise 来实现异步操作。std::future 表示一个异步操作的结果,而 std::promise 则用于设置这个结果。
  • 事件循环:像 Node.js 这样的平台使用事件循环来处理异步操作。事件循环会不断地检查是否有事件发生,如果有,就调用相应的回调函数。
  • 协程:C++20 引入了协程(Coroutine),协程可以让我们以同步的方式编写异步代码,极大地简化了异步编程的复杂性。

1.4 异步的工作原理

异步操作的核心思想是将耗时的操作放到后台执行,避免阻塞主线程。操作系统或运行时环境通常会提供一些机制来支持异步操作,比如多线程、I/O 多路复用等。

举个例子,假设你有一个程序需要同时处理多个网络请求。如果使用同步的方式,每个请求都会阻塞程序,导致性能低下。而使用异步的方式,程序可以同时发起多个请求,并在请求返回时处理结果,极大地提高了程序的并发能力。

2. 同步与互斥

2.1 为什么需要互斥?

在多线程编程中,互斥是一个非常重要的概念。互斥的目的是保护共享资源,防止多个线程同时访问或修改这些资源,从而导致数据不一致或其他问题。

举个例子,假设我们有一个全局变量 int counter = 0;,多个线程会同时对这个变量进行加一操作。如果没有互斥机制,可能会出现以下情况:

void incrementCounter() {
    counter = counter + 1;
}

假设有两个线程 A 和 B 同时执行 incrementCounter(),它们可能会同时读取 counter 的值(比如都是 0),然后分别加一,最后将结果写回 counter。这样,counter 的值最终是 1,而不是我们期望的 2。

这就是典型的竞态条件(Race Condition)问题。为了避免这个问题,我们需要使用互斥锁(Mutex)来保护 counter

2.2 互斥锁

互斥锁(Mutex)是一种同步机制,用于确保同一时间只有一个线程可以访问共享资源。当一个线程获取了互斥锁后,其他线程必须等待,直到这个线程释放锁。

我们可以用互斥锁来修复上面的例子:

std::mutex mtx;
int counter = 0;

void incrementCounter() {
    mtx.lock();
    counter = counter + 1;
    mtx.unlock();
}

在这个例子中,mtx.lock()mtx.unlock() 分别用于获取和释放互斥锁。这样,即使有多个线程同时执行 incrementCounter(),也只有一个线程能够进入临界区(counter = counter + 1;),从而避免了竞态条件。

2.3 条件变量

互斥锁可以解决简单的同步问题,但在某些情况下,我们需要更复杂的同步机制。比如,当某个线程需要等待某个条件满足时,我们可以使用条件变量(Condition Variable)。

条件变量通常与互斥锁一起使用。一个线程可以等待条件变量,而另一个线程可以在条件满足时通知等待的线程。

举个例子,假设我们有一个任务队列,多个线程从队列中取出任务执行。如果队列为空,线程需要等待,直到有新任务加入队列。我们可以使用条件变量来实现这个功能:

std::mutex mtx;
std::condition_variable cv;
std::queue<Task> taskQueue;

void workerThread() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !taskQueue.empty(); });
        
        Task task = taskQueue.front();
        taskQueue.pop();
        lock.unlock();
        
        // 执行任务
        executeTask(task);
    }
}

void addTask(Task task) {
    std::lock_guard<std::mutex> lock(mtx);
    taskQueue.push(task);
    cv.notify_one();
}

在这个例子中,cv.wait(lock, []{ return !taskQueue.empty(); }); 会让线程等待,直到队列不为空。当有新任务加入队列时,cv.notify_one() 会通知一个等待的线程。

2.4 信号量

信号量(Semaphore)是另一种同步机制,它可以用来控制对共享资源的访问。信号量与互斥锁类似,但它允许多个线程同时访问资源,而互斥锁只允许一个线程访问。

信号量有一个计数器,表示当前可用的资源数量。当一个线程获取信号量时,计数器减一;当线程释放信号量时,计数器加一。如果计数器为零,线程必须等待,直到有其他线程释放信号量。

信号量通常用于限制同时访问某个资源的线程数量。比如,假设我们有一个连接池,最多允许 10 个线程同时使用数据库连接。我们可以使用信号量来实现这个限制:

std::semaphore sem(10);

void useDatabase() {
    sem.acquire();
    
    // 使用数据库连接
    // ...
    
    sem.release();
}

在这个例子中,sem.acquire() 会减少信号量的计数器,如果计数器为零,线程会等待。sem.release() 会增加信号量的计数器,允许其他线程获取信号量。

3. 总结

  • 同步是任务按照顺序依次执行,通常是阻塞的。
  • 异步是任务在后台执行,不会阻塞主线程,通常通过回调、Future/Promise、事件循环或协程来实现。
  • 互斥用于保护共享资源,防止多个线程同时访问,常用的机制有互斥锁、条件变量和信号量。

理解这些概念并正确使用它们,是写出高效、安全并发代码的关键。希望这篇文章能帮助你更好地理解同步、异步和互斥的概念。如果你有任何问题或想法,欢迎在评论区讨论!


http://www.kler.cn/a/532301.html

相关文章:

  • 2024年度十大网络安全热点事件盘点:时代暗涌下的安全危机
  • 企业商业秘密百问百答之三十八【商务保密协议签订】
  • Kotlin 使用 Springboot 反射执行方法并自动传参
  • Java中的泛型及其用途是什么?
  • Vue3学习笔记-模板语法和属性绑定-2
  • 给AI加知识库
  • 【学习笔记】计算机图形学的几何数学基础知识
  • 【Redis】主从模式,哨兵,集群
  • 每日一题——小根堆实现堆排序算法
  • 低通滤波算法的数学原理和C语言实现
  • vim-plug的自动安装与基本使用介绍
  • 【学术征稿-组织单位 武汉理工大学西安理工大学、西安财经大学】第三届通信网络与机器学习(CNML 2025)
  • Codeforces Round 1002 (Div. 2)(部分题解)
  • 利用Python高效处理大规模词汇数据
  • MongoDB 聚合
  • 简易CPU设计入门:指令单元(三)
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.29 NumPy+Scikit-learn(sklearn):机器学习基石揭秘
  • DeepSeek蒸馏模型:轻量化AI的演进与突破
  • 测试csdn图片发布
  • 为何在Kubernetes容器中以root身份运行存在风险?
  • 机器学习在环境科学中的应用
  • BUU16 [ACTF2020 新生赛]BackupFile1
  • 通信易懂唠唠SOME/IP——SOME/IP 协议规范
  • 分布式微服务系统架构第91集:系统性能指标总结
  • 额外题目汇总1:数组
  • deepseek出现以后国产AI大降价--分析各品牌AI的分效用和价格