【C++基础】多线程并发场景下的同步方法
如果在多线程程序中对全局变量的访问没有进行适当的同步控制(例如使用互斥锁、原子变量等),会导致多个线程同时访问和修改全局变量时发生竞态条件(race condition)。这种竞态条件可能会导致一系列不确定和严重的后果。
在C++中,可以通过使用互斥锁(mutex)、原子操作、读写锁来实现对全局变量的互斥访问。
一、缺乏同步控制造成的后果
1. 数据竞争(Data Race)
数据竞争发生在多个线程同时访问同一个变量,并且至少有一个线程在写该变量时没有进行同步。由于缺少同步机制,多个线程对全局变量的操作可能会相互干扰,导致变量的值不可预测。
示例:
#include <iostream>
#include <thread>int globalVar = 0;void increment() {globalVar++;
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Global variable: " << globalVar << std::endl;return 0;
}
后果:
- 上面的代码中,
globalVar++并不是一个原子操作。它由多个步骤组成:读取值、增加值、写回。在这段代码中,t1和t2可能会同时读取globalVar的值,导致两个线程同时修改它的值,最终的结果会小于预期的2。这就是典型的数据竞争。
2. 不一致的状态(Inconsistent State)
在没有同步控制的情况下,多个线程可能会对全局变量进行同时读写操作,导致变量处于不一致的状态。例如,多个线程可能会同时读取和修改相同的变量,导致最终状态不符合预期。
示例: 假设你有一个程序要求维护一个全局的计数器。如果没有加锁来确保线程安全,两个线程同时执行时,计数器可能会被写成一个无意义的值。
#include <iostream>
#include <thread>int counter = 0;void increment() {for (int i = 0; i < 100000; ++i) {counter++; // 非线程安全操作}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Counter: " << counter << std::endl;return 0;
}
后果:
- 在没有同步的情况下,
counter++可能会导致多个线程在同一时刻读取到相同的计数器值,并同时将相同的更新值写回变量,这会使得counter的最终值远小于预期的200000。 - 这可能会导致程序的业务逻辑错误,特别是如果全局变量用作关键状态的标识。
3. 崩溃或程序未定义行为
由于数据竞争或者不一致的状态,程序可能会进入一个不可预测的状态,导致崩溃。全局变量的值在多线程的竞争中可能会发生损坏,从而导致未定义的行为(undefined behavior)。
例如:
- 访问已释放内存:一个线程修改了全局变量并释放了相关内存,但其他线程仍然试图访问该内存。
- 内存覆盖:多个线程同时修改全局变量,导致不同线程的操作互相覆盖,从而引发崩溃。
二、互斥锁std::mutex实现同步
std::mutex 是C++标准库中的一种机制,用于避免多个线程同时访问同一个资源(如全局变量)时发生竞争条件。
下面是一个示例,展示了如何使用std::mutex来保护全局变量:
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 定义全局互斥锁
int globalVar = 0; // 定义全局变量void threadFunction() {std::lock_guard<std::mutex> lock(mtx); // 上锁,确保互斥// 访问和修改全局变量++globalVar;std::cout << "Global variable: " << globalVar << std::endl;// 锁会在lock_guard离开作用域时自动释放
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}
说明:
std::mutex: 用于保护共享资源(如全局变量)。std::lock_guard<std::mutex>: 是一个RAII风格的封装器,它在构造时自动上锁,在析构时自动解锁,确保了线程安全。threadFunction中,每个线程在访问globalVar之前都会先获得互斥锁,这样就能确保线程之间不会同时访问和修改全局变量。
使用std::mutex可以防止不同线程之间因竞争访问全局变量而引发的错误或不一致问题。
有时如果你需要更细粒度的控制,还可以考虑使用std::unique_lock,它比std::lock_guard更灵活,允许手动控制锁的获取和释放。
三、独占锁std::unique_lock实现同步
std::unique_lock 是 C++11 标准库中的一种互斥锁包装器,它提供了比 std::lock_guard 更灵活的锁管理方式。std::unique_lock 允许手动控制锁的获取和释放,而不仅仅是在对象生命周期结束时自动释放锁(如 std::lock_guard 所做的那样)。这使得它比 std::lock_guard 更加灵活,适用于更复杂的场景,比如需要在同一作用域内多次锁定或解锁,或者需要在锁定期间进行一些其他操作。
std::unique_lock 的关键特性:
- 手动控制锁的获取和释放:
std::unique_lock支持手动解锁和重新锁定,它比std::lock_guard更加灵活。 - 延迟锁定和提前解锁:你可以选择在对象创建时延迟锁定,或者在锁定后手动释放锁。
- 支持条件变量:
std::unique_lock支持与条件变量一起使用,这是std::lock_guard无法做到的。
基本用法:
1. 构造时自动加锁:
std::unique_lock 默认会在构造时自动加锁。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void threadFunction() {std::unique_lock<std::mutex> lock(mtx); // 构造时自动上锁std::cout << "Thread is running\n";// 临界区的操作// 锁会在 lock 对象超出作用域时自动释放
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}
2. 手动解锁与重新加锁:
std::unique_lock 允许你在锁定期间手动解锁和重新加锁,这对于一些需要临时释放锁的场景非常有用。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void threadFunction() {std::unique_lock<std::mutex> lock(mtx); // 构造时自动上锁std::cout << "Thread is running\n";// 临界区的操作lock.unlock(); // 手动解锁std::cout << "Lock released temporarily\n";// 临界区之外的操作lock.lock(); // 重新加锁std::cout << "Lock acquired again\n";// 临界区操作继续进行
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}
3. 延迟锁定:
std::unique_lock 也允许你延迟锁定,通过传递一个 std::defer_lock 参数给构造函数来实现。这会创建一个未锁定的 std::unique_lock,你可以在稍后手动调用 lock() 来加锁。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void threadFunction() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁std::cout << "Thread is preparing to run\n";// 做一些不需要加锁的操作lock.lock(); // 手动加锁std::cout << "Thread is running under lock\n";// 临界区的操作
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}
4. 条件变量:
std::unique_lock 是与条件变量一起使用的理想选择,它支持对互斥锁的手动解锁和重新加锁。这在条件变量的使用场景中非常有用,因为在等待条件时需要解锁互斥锁,而在条件满足时重新加锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void threadFunction() {std::unique_lock<std::mutex> lock(mtx); // 上锁while (!ready) { // 等待 ready 为 truecv.wait(lock); // 等待,自动解锁并挂起线程}std::cout << "Thread is running\n";
}void notify() {std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟一些操作std::cout << "Notifying the threads\n";std::unique_lock<std::mutex> lock(mtx); // 上锁ready = true;cv.notify_all(); // 通知所有线程
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);std::thread notifier(notify);t1.join();t2.join();notifier.join();return 0;
}
解释:
-
std::condition_variable和std::unique_lock:- 在
threadFunction中,cv.wait(lock)会释放锁并等待条件变量的通知。 std::unique_lock能够在调用wait时自动释放锁,并且在wait返回时会重新加锁,这使得std::unique_lock成为使用条件变量的最佳选择。
- 在
-
cv.notify_all():通知所有等待该条件的线程,thread1和thread2都会在条件满足时继续执行。
四、共享锁std::shared_mutex实现同步
std::shared_mutex 是 C++17 引入的一个同步原语,它提供了一种读写锁机制,允许多个线程共享读取同一资源,而只有一个线程能够独占写入该资源。相比于传统的 std::mutex(只支持独占锁),std::shared_mutex 可以提高并发性,特别是在读操作远多于写操作的情况下。
std::shared_mutex 的工作原理:
- 共享锁(shared lock):多个线程可以同时获取共享锁,这意味着多个线程可以同时读取共享资源。多个线程获取共享锁时不会发生冲突。
- 独占锁(unique lock):只有一个线程可以获取独占锁,这意味着写操作会阻塞其他所有操作(无论是读操作还是写操作),以保证数据的一致性。
使用 std::shared_mutex:
std::shared_mutex 提供了两种类型的锁:
std::unique_lock<std::shared_mutex>:用于获取独占锁。std::shared_lock<std::shared_mutex>:用于获取共享锁。
1. 基本使用示例:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>std::shared_mutex mtx; // 定义一个 shared_mutex
int sharedData = 0;void readData(int threadId) {std::shared_lock<std::shared_mutex> lock(mtx); // 获取共享锁std::cout << "Thread " << threadId << " is reading data: " << sharedData << std::endl;
}void writeData(int threadId, int value) {std::unique_lock<std::shared_mutex> lock(mtx); // 获取独占锁sharedData = value;std::cout << "Thread " << threadId << " is writing data: " << sharedData << std::endl;
}int main() {std::vector<std::thread> threads;// 启动多个线程进行读取操作for (int i = 0; i < 5; ++i) {threads.push_back(std::thread(readData, i));}// 启动一个线程进行写入操作threads.push_back(std::thread(writeData, 100, 42));// 等待所有线程结束for (auto& t : threads) {t.join();}return 0;
}
解释:
- 共享锁 (
std::shared_lock):线程readData使用std::shared_lock获取共享锁,这允许多个线程同时读取sharedData,因为读取操作是线程安全的。 - 独占锁 (
std::unique_lock):线程writeData使用std::unique_lock获取独占锁,这确保了只有一个线程可以写sharedData,并且写操作会阻塞所有其他线程(包括读操作和写操作)。
2. 多个读线程与单个写线程的并发控制:
在这个示例中,多个读线程可以并行执行,因为它们都获取了共享锁。只有当写线程(获取独占锁)执行时,其他线程(无论是读线程还是写线程)会被阻塞。
- 写操作:获取独占锁,所有读操作和写操作都会被阻塞,直到写操作完成。
- 读操作:多个线程可以同时获取共享锁,只有在没有写操作时才会执行。
3. 共享锁与独占锁的冲突:
- 共享锁:多个线程可以同时获取共享锁,只要没有线程持有独占锁。共享锁不会阻塞其他共享锁请求。
- 独占锁:当一个线程持有独占锁时,其他任何线程的共享锁或独占锁请求都会被阻塞,直到独占锁释放。
4. 使用场景:
std::shared_mutex 主要适用于读多写少的场景。假设有一个资源(如缓存、数据结构),它在大部分时间内被多个线程读取,但偶尔需要被更新。在这种情况下,std::shared_mutex 可以让多个读操作并行执行,同时避免写操作导致的不必要的阻塞。
例如:
- 缓存数据读取:多个线程可以并发读取缓存中的数据,而当缓存需要更新时,独占锁会确保数据一致性。
- 数据库的并发查询和修改:多个线程可以并发查询数据库,但只有一个线程可以执行写操作。
5. std::shared_mutex 与 std::mutex 比较:
std::mutex:提供独占锁,适用于写操作频繁且不需要并发读的场景。每次加锁时,其他线程都无法进入临界区。std::shared_mutex:适用于读多写少的场景,允许多个线程同时读取共享资源,但写操作会阻塞所有其他操作。
6. 性能考虑:
- 读操作频繁时:使用
std::shared_mutex可以提高并发性,因为多个线程可以同时读取数据。 - 写操作频繁时:性能可能会低于
std::mutex,因为写操作需要独占资源并阻塞所有其他操作。
7. 条件变量:
与 std::mutex 一样,std::shared_mutex 也可以与条件变量(std::condition_variable)一起使用,不过在使用时要注意,不同的线程需要加锁和解锁对应的锁。
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <condition_variable>std::shared_mutex mtx;
std::condition_variable_any cv;
int sharedData = 0;void readData() {std::shared_lock<std::shared_mutex> lock(mtx); // 获取共享锁while (sharedData == 0) { // 等待数据可用cv.wait(lock); // 等待数据被写入}std::cout << "Reading data: " << sharedData << std::endl;
}void writeData(int value) {std::unique_lock<std::shared_mutex> lock(mtx); // 获取独占锁sharedData = value;std::cout << "Writing data: " << sharedData << std::endl;cv.notify_all(); // 通知所有等待的线程
}int main() {std::thread reader(readData);std::thread writer(writeData, 42);reader.join();writer.join();return 0;
}
解释:
std::shared_lock:用于共享读锁,允许多个线程同时读取。cv.wait(lock):使用共享锁来等待某些条件的变化。cv.notify_all():通知所有等待线程,唤醒它们继续执行。
五、std::atomic实现同步
std::atomic 是 C++11 标准引入的一种类型,用于实现原子操作。原子操作指的是操作在执行过程中不可被中断,因此能够保证数据的一致性和正确性。
std::atomic 提供了一些基本的原子操作方法,这些操作是不可分割的,保证了在多线程环境下线程安全。它主要用于数据的同步与协作,避免了传统同步原语(如锁、条件变量)所带来的性能瓶颈。
原子操作的基本概念:
- 原子性:在执行时,操作不能被打断,保证线程之间对共享变量的操作不会产生竞态条件。
- 内存顺序(Memory Ordering):控制操作的执行顺序和对共享数据的可见性,
std::atomic允许通过内存顺序来显式指定不同线程间的同步行为。
std::atomic 提供的原子操作:
- 加载(Load):从原子变量中读取数据。
- 存储(Store):将数据存储到原子变量中。
std::atomic 支持的内存顺序(Memory Ordering):
std::memory_order_acquire:确保前面的操作在加载之后执行,即它会阻止后续的操作在此之前执行。std::memory_order_release:确保后面的操作在存储之前执行,即它会阻止前面的操作在此之后执行。
通常情况下,在使用 std::atomic 进行同步时,使用 memory_order_release 在 store 操作时,使用 memory_order_acquire 在 load 操作时,是一种常见的模式,特别是在生产者-消费者模式或者其他类似的同步模式下。
memory_order_release 和 memory_order_acquire 一般搭配使用。
这种组合是为了确保 内存顺序的一致性,并且保证数据正确的可见性。具体来说:
-
memory_order_release:在执行store操作时,它会确保在store之前的所有操作(如数据写入)不会被重排序到store之后,保证当前线程的写操作对其他线程是可见的。因此,store操作保证所有前置的写操作都会在这个store完成后被其他线程看到。 -
memory_order_acquire:在执行load操作时,它会确保在load之后的所有操作(如数据读取)不会被重排序到load之前,保证当前线程在读取共享数据后,后续的操作可以看到正确的数据。在load之前的所有操作(包括对共享变量的写入)会在读取这个值之后对当前线程可见。
这两者配合使用,确保了线程间的同步,避免了数据竞态条件。
具体场景
考虑一个生产者-消费者模型,生产者负责写入数据并通知消费者,消费者负责读取数据并处理。
示例:
#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> data(0);
std::atomic<bool> ready(false);void consumer() {while (!ready.load(std::memory_order_acquire)) {// 等待 ready 为 true}std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl;
}void producer() {data.store(42, std::memory_order_relaxed); // 写数据ready.store(true, std::memory_order_release); // 设置 ready 为 true
}int main() {std::thread t1(consumer);std::thread t2(producer);t1.join();t2.join();return 0;
}
解释:
-
ready.store(true, std::memory_order_release):生产者线程在写入ready时使用memory_order_release,这意味着在ready设置为true之后,所有在此之前的操作(如对data的写入)对消费者线程是可见的。 -
ready.load(std::memory_order_acquire):消费者线程在读取ready时使用memory_order_acquire,这意味着消费者线程在读取ready后,确保它能够看到生产者线程在storeready之前所做的所有修改(如data的值)。
这种组合保证了生产者线程的写操作(例如 data.store(42))对于消费者线程是可见的,且在读取 ready 后,消费者线程可以安全地读取到更新后的 data。
相关文章:
【C++基础】多线程并发场景下的同步方法
如果在多线程程序中对全局变量的访问没有进行适当的同步控制(例如使用互斥锁、原子变量等),会导致多个线程同时访问和修改全局变量时发生竞态条件(race condition)。这种竞态条件可能会导致一系列不确定和严重的后果。…...
Linux常见问题解决方法--1
常见安全工具、设备 工具 端口及漏洞扫描:Namp、Masscan 抓包:Wireshark,Burpsuite、Fiddler、HttpCanary Web自动化安全扫描:Nessus、Awvs、Appscan、Xray 信息收集:Oneforall、hole 漏洞利用:MSF、…...
银行卡三要素验证接口:方便快捷地实现银行卡核验功能
银行卡三要素验证API:防止欺诈交易的有力武器 随着互联网的发展,电子支付方式也越来越普及。在支付过程中,银行卡是最常用的支付工具之一。然而,在一些支付场景中,需要对用户的银行卡信息进行验证,以确保支…...
利用JSON数据类型优化关系型数据库设计
利用JSON数据类型优化关系型数据库设计 前言 在关系型数据库中,传统的结构化存储方式要求预先定义好所有的列及其数据类型。 然而,随着业务的发展,这种设计可能会显得不够灵活,尤其是在需要扩展单个列的描述功能时。 JSON数据…...
极简壁纸js逆向
首先抓包,翻页可以看到数据储存在该包 可以看到随着页面变化,只有current在变化 而且载荷都没有加密,看来不用js逆向了 爬取代码 import os import asyncio import aiohttp import jsonheaders {"accept": "application/j…...
Kafka 入门与应用实战:吞吐量优化与与 RabbitMQ、RocketMQ 的对比
前言 在现代微服务架构和分布式系统中,消息队列作为解耦组件,承担着重要的职责。它不仅提供了异步处理的能力,还能确保系统的高可用性、容错性和扩展性。常见的消息队列包括 Kafka、RabbitMQ 和 RocketMQ,其中 Kafka 因其高吞吐量…...
JAVA 接口、抽象类的关系和用处 详细解析
接口 - Java教程 - 廖雪峰的官方网站 一个 抽象类 如果实现了一个接口,可以只选择实现接口中的 部分方法(所有的方法都要有,可以一部分已经写具体,另一部分继续保留抽象),原因在于: 抽象类本身…...
数据结构与算法再探(六)动态规划
目录 动态规划 (Dynamic Programming, DP) 动态规划的基本思想 动态规划的核心概念 动态规划的实现步骤 动态规划实例 1、爬楼梯 c 递归(超时)需要使用记忆化递归 循环 2、打家劫舍 3、最小路径和 4、完全平方数 5、最长公共子序列 6、0-1背…...
使用PC版本剪映制作照片MV
目录 制作MV模板时长调整拖动边缘缩短法分割删除法变速法整体调整法 制作MV 导入音乐 导入歌词 点击歌词 和片头可以修改字体: 还可以给字幕添加动画效果: 导入照片,自动创建照片轨: 修改片头字幕:增加两条字幕轨&…...
Python爬虫获取custom-1688自定义API操作接口
一、引言 在电子商务领域,1688作为国内领先的B2B平台,提供了丰富的API接口,允许开发者获取商品信息、店铺信息等。其中,custom接口允许开发者进行自定义操作,获取特定的数据。本文将详细介绍如何使用Python调用1688的…...
Autogen_core: Reflection
目录 代码代码逻辑解释:数据类定义:CoderAgent 类:ReviewerAgent 类:主程序: 完成的功能: 代码 from dataclasses import dataclassdataclass class CodeWritingTask:task: strdataclass class CodeWritin…...
GitHub 仓库的 Archived 功能详解:中英双语
GitHub 仓库的 Archived 功能详解 一、什么是 GitHub 仓库的 “Archived” 功能? 在 GitHub 上,“Archived” 是一个专门用于标记仓库状态的功能。当仓库被归档后,它变为只读模式,所有的功能如提交代码、创建 issue 和 pull req…...
.NET Core缓存
目录 缓存的概念 客户端响应缓存 cache-control 服务器端响应缓存 内存缓存(In-memory cache) 用法 GetOrCreateAsync 缓存过期时间策略 缓存的过期时间 解决方法: 两种过期时间策略: 绝对过期时间 滑动过期时间 两…...
Ubuntu 20.04安装Protocol Buffers 2.5.0
个人博客地址:Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载:https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容: curl htt…...
【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...
14.模型,纹理,着色器
模型、纹理和着色器是计算机图形学中的三个核心概念,用通俗易懂的方式来解释: 1. 模型:3D物体的骨架 通俗解释: 模型就像3D物体的骨架,定义了物体的形状和结构。 比如,一个房子的模型包括墙、屋顶、窗户等…...
【微服务与分布式实践】探索 Dubbo
核心组件 服务注册与发现原理 服务提供者启动时,会将其服务信息(如服务名、版本、所在节点的网络地址等)注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表,并与之通信。注册中心会存储服务的信息,…...
Scale AI 创始人兼 CEO采访
Scale AI 创始人兼 CEO 亚历山大王(Alexander Wang)首次亮相节目接受采访。他的公司专注于为人工智能工具提供准确标注的数据。早在 2022 年,王成为世界上最年轻的白手起家亿万富翁。 美国在全球人工智能竞赛中的地位,以及它与中…...
Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
NeuIPS 2024 | CoT推理的新突破:推理边界框架(RBF)
近年来,大型语言模型(LLMs)在推理任务上的能力不断提升,尤其是 思维链(Chain-of-Thought, CoT) 技术,使得模型可以逐步推演逻辑,提高预测准确率。然而,当前的CoT推理仍然…...
【C】memory 详解
<memory.h> 是一个 C 标准库头文件,提供了一组内存管理函数,用于分配、释放和操作动态内存。这些函数主要操作的是未初始化的内存块,是早期 C 编程中常用的内存操作工具。 尽管在现代 C 编程中更推荐使用<cstring>或<memory&…...
linux——进程树的概念和示例
一些程序进程运行后,会调用其他进程,这样就组成了一个进程树。 比如,在Windows XP的“运行”对话框中输入“cmd”启动命令行控制台,然后在命令行中输入“notepad”启动记事本,那么命令行控制台进程“cmd.exe”和记事本进程“note…...
分布式系统相关面试题收集
目录 什么是分布式系统,以及它有哪些主要特性? 分布式系统中如何保证数据的一致性? 解释一下CAP理论,并说明在分布式系统中如何权衡CAP三者? 什么是分布式事务,以及它的实现方式有哪些? 什么是…...
CSAPP学习:前言
前言 本书简称CS:APP。 背景知识 一些基础的C语言知识 如何阅读 Do-做系统 在真正的系统上解决具体的问题,或是编写和运行程序。 章节 2025-1-27 个人认为如下章节将会对学习408中的操作系统与计算机组成原理提供帮助,于是先凭借记忆将其简单…...
kaggle比赛入门 - House Prices - Advanced Regression Techniques(第三部分)
本文承接上一篇。 1. 数据预处理流水线(pipelines) from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEnc…...
Linux 命令之技巧(Tips for Linux Commands)
Linux 命令之技巧 简介 Linux 是一种免费使用和自由传播的类Unix操作系统,其内核由林纳斯本纳第克特托瓦兹(Linus Benedict Torvalds)于1991年10月5日首次发布。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户…...
从 GShard 到 DeepSeek-V3:回顾 MoE 大模型负载均衡策略演进
作者:小天狼星不来客 原文:https://zhuanlan.zhihu.com/p/19117825360 故事要从 GShard 说起——当时,人们意识到拥有数十亿甚至数万亿参数的模型可以通过某种形式的“稀疏化(sparsified)”来在保持高精度的同时加速训…...
【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 这一节课我们不学习新的知识,我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…...
【反悔堆】力扣1642. 可以到达的最远建筑
给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1(下标 从 0 开始 )…...
字符串算法笔记
字符串笔记 说到字符串,首先我们要注意的就是字符串的输入以及输出,因为字符串的输入格式以及要求也分为很多种,我们就来说几个比较常见的格式 g e t s gets gets 我们先来说这个函数的含义...
