C++ 内存模型:用生活中的例子理解并发编程

C++ 内存模型:用生活中的例子理解并发编程

文章目录

  • C++ 内存模型:用生活中的例子理解并发编程
    • 引言:为什么需要内存模型?
    • 核心概念:改动序列
    • 原子类型:不可分割的操作
    • 内存次序:不同的同步级别
      • 1. 宽松次序 (Relaxed Ordering) - 像咖啡店的订单
      • 2. 获取-释放次序 (Acquire-Release Ordering) - 像接力赛跑
      • 3. 顺序一致次序 (Sequentially Consistent Ordering) - 像军事演练
    • 自旋锁:像洗手间的门锁
    • Happens-Before 关系:像烹饪食谱
    • 总结:选择合适的内存次序
  • C++ 内存模型的作用:用生活中的例子理解
    • 内存模型的核心作用
    • 六大核心作用详解
      • 1. 防止数据竞争 - 像超市收银台的排队系统
      • 2. 保证内存访问顺序 - 像烹饪食谱的步骤顺序
      • 3. 提供可见性保证 - 像办公室的公告板
      • 4. 实现高效的线程同步 - 像交通信号灯
      • 5. 优化性能 - 像超市的快速收银通道
      • 6. 提供可移植的并发抽象 - 像国际电源适配器
    • 总结:内存模型的六大作用

引言:为什么需要内存模型?

想象一下,你在一家繁忙的超市购物。多个收银台同时工作(多个线程),顾客们(数据)在不同的收银台之间流动。如果没有明确的规则,可能会出现各种问题:

  • 同一个商品被多次扫描(数据竞争)
  • 顾客不知道应该排哪个队伍(内存访问顺序问题)
  • 收银员之间的协作混乱(线程同步问题)

C++ 内存模型就是为了解决这些问题而制定的一套规则,确保在多线程环境下,数据的访问和修改能够有序、可预测地进行。

核心概念:改动序列

生活比喻:想象一个共享的家庭日历,所有家庭成员都可以在上面添加事项。

#include <iostream>
#include <thread>
#include <atomic>// 家庭共享日历(原子变量)
std::atomic<int> family_calendar{0};void mother_adds_event() {family_calendar.store(1, std::memory_order_relaxed); // 添加"购物"事项family_calendar.store(2, std::memory_order_relaxed); // 添加"做饭"事项
}void father_adds_event() {family_calendar.store(3, std::memory_order_relaxed); // 添加"修车"事项family_calendar.store(4, std::memory_order_relaxed); // 添加"缴费"事项
}void child_reads_calendar() {int last_event = 0;for (int i = 0; i < 10; ++i) {int current_event = family_calendar.load(std::memory_order_relaxed);if (current_event != last_event) {std::cout << "孩子看到日历更新: " << current_event << std::endl;last_event = current_event;}}
}int main() {std::thread mom(mother_adds_event);std::thread dad(father_adds_event);std::thread child(child_reads_calendar);mom.join();dad.join();child.join();return 0;
}

在这个例子中:

  • 每个家庭成员(线程)都在日历上添加事项(写操作)
  • 孩子(读取线程)看到的事项序列就是"改动序列"
  • 虽然每次运行看到的顺序可能不同,但每次运行中所有线程看到的序列是一致的

原子类型:不可分割的操作

生活比喻:超市的收银台扫描商品 - 要么完整扫描一个商品,要么完全不扫描,不会出现扫描一半的情况。

#include <iostream>
#include <atomic>
#include <thread>// 超市库存(原子变量)
std::atomic<int> inventory{100};void customer_buys(int items) {int old_inventory = inventory.load(std::memory_order_relaxed);while (old_inventory >= items && !inventory.compare_exchange_weak(old_inventory, old_inventory - items)) {// 如果库存变化了,重新尝试}std::cout << "顾客购买了 " << items << " 件商品,剩余库存: " << inventory.load(std::memory_order_relaxed) << std::endl;
}int main() {std::thread customers[5];for (int i = 0; i < 5; ++i) {customers[i] = std::thread(customer_buys, 20 + i * 5);}for (auto& c : customers) {c.join();}std::cout << "最终库存: " << inventory.load() << std::endl;return 0;
}

内存次序:不同的同步级别

1. 宽松次序 (Relaxed Ordering) - 像咖啡店的订单

生活比喻:在繁忙的咖啡店,顾客点的咖啡顺序和制作顺序可能不一致,但最终每杯咖啡都会做好。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>std::atomic<int> coffee_orders{0};
std::vector<int> made_coffees;void barista(int id) {for (int i = 0; i < 5; ++i) {// 模拟制作咖啡的时间std::this_thread::sleep_for(std::chrono::milliseconds(10 * (id + 1)));int order = coffee_orders.fetch_add(1, std::memory_order_relaxed);made_coffees.push_back(order);std::cout << "咖啡师 " << id << " 制作了咖啡 #" << order << std::endl;}
}int main() {std::thread baristas[3];for (int i = 0; i < 3; ++i) {baristas[i] = std::thread(barista, i);}for (auto& b : baristas) {b.join();}std::cout << "\n制作的咖啡顺序: ";for (int coffee : made_coffees) {std::cout << coffee << " ";}std::cout << std::endl;return 0;
}

2. 获取-释放次序 (Acquire-Release Ordering) - 像接力赛跑

生活比喻:接力赛中,前一棒选手(释放)必须把接力棒交给后一棒选手(获取),这个交接点确保了顺序。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>std::atomic<bool> ready{false};
std::atomic<int> data[3] = {0, 0, 0};void runner(int id) {// 准备阶段(释放前的工作)data[id].store(id + 1, std::memory_order_relaxed);// 释放:告诉下一棒可以开始了if (id == 0) {ready.store(true, std::memory_order_release);}
}void next_runner() {// 获取:等待前一棒的信号while (!ready.load(std::memory_order_acquire)) {// 等待信号}// 可以看到前一棒设置的所有数据std::cout << "接棒选手看到的数据: ";for (int i = 0; i < 3; ++i) {std::cout << data[i].load(std::memory_order_relaxed) << " ";}std::cout << std::endl;
}int main() {std::thread runners[3];for (int i = 0; i < 3; ++i) {runners[i] = std::thread(runner, i);}std::thread next(next_runner);for (auto& r : runners) {r.join();}next.join();return 0;
}

3. 顺序一致次序 (Sequentially Consistent Ordering) - 像军事演练

生活比喻:军事演练中,所有命令必须严格按照顺序执行,每个士兵看到的事件顺序都完全一致。

#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> command{0};
std::atomic<bool> operation_done{false};void commander() {// 发布命令序列command.store(1, std::memory_order_seq_cst);  // 命令1: 前进command.store(2, std::memory_order_seq_cst);  // 命令2: 左转command.store(3, std::memory_order_seq_cst);  // 命令3: 停止operation_done.store(true, std::memory_order_seq_cst);
}void soldier(int id) {int last_command = 0;while (!operation_done.load(std::memory_order_seq_cst)) {int current_command = command.load(std::memory_order_seq_cst);if (current_command != last_command) {std::cout << "士兵 " << id << " 收到命令: " << current_command << std::endl;last_command = current_command;}}
}int main() {std::thread cmd(commander);std::thread soldiers[3];for (int i = 0; i < 3; ++i) {soldiers[i] = std::thread(soldier, i);}cmd.join();for (auto& s : soldiers) {s.join();}return 0;
}

自旋锁:像洗手间的门锁

生活比喻:洗手间门上的"有人/无人"标志。人们不断检查这个标志(自旋),直到标志显示"无人"。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>class SpinLock {
public:void lock() {// 不断检查门是否锁着,直到成功锁上门while (lock_flag.test_and_set(std::memory_order_acquire)) {// 等待:就像不断尝试推门看看是否还锁着}}void unlock() {// 打开门锁:让其他人可以进入lock_flag.clear(std::memory_order_release);}private:std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
};SpinLock bathroom_lock;
int bathroom_users = 0;void use_bathroom(int person_id) {// 尝试获取锁(检查门是否开着)bathroom_lock.lock();// 进入洗手间bathroom_users++;std::cout << "人物 " << person_id << " 进入洗手间,当前人数: " << bathroom_users << std::endl;// 模拟使用洗手间的时间std::this_thread::sleep_for(std::chrono::milliseconds(100));// 离开洗手间bathroom_users--;std::cout << "人物 " << person_id << " 离开洗手间,当前人数: " << bathroom_users << std::endl;// 释放锁(打开门)bathroom_lock.unlock();
}int main() {const int num_people = 5;std::vector<std::thread> people;for (int i = 0; i < num_people; ++i) {people.emplace_back(use_bathroom, i);}for (auto& person : people) {person.join();}return 0;
}

Happens-Before 关系:像烹饪食谱

生活比喻:烹饪食谱中的步骤顺序。有些步骤必须先完成(切菜),后面的步骤(炒菜)才能开始。

#include <iostream>
#include <atomic>
#include <thread>std::atomic<bool> vegetables_chopped{false};
std::atomic<bool> pan_heated{false};
std::atomic<bool> dish_cooked{false};void chef_1() {// 切菜(必须先完成)std::this_thread::sleep_for(std::chrono::milliseconds(100));vegetables_chopped.store(true, std::memory_order_release);std::cout << "厨师1: 蔬菜切好了" << std::endl;
}void chef_2() {// 热锅(可以与切菜同时进行)std::this_thread::sleep_for(std::chrono::milliseconds(50));pan_heated.store(true, std::memory_order_release);std::cout << "厨师2: 锅热好了" << std::endl;
}void chef_3() {// 等待必要的准备工作完成while (!vegetables_chopped.load(std::memory_order_acquire) || !pan_heated.load(std::memory_order_acquire)) {// 等待食材和锅准备好}// 炒菜(必须在切菜和热锅之后)std::cout << "厨师3: 开始炒菜" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(150));dish_cooked.store(true, std::memory_order_release);std::cout << "厨师3: 菜炒好了" << std::endl;
}void server() {// 等待菜炒好while (!dish_cooked.load(std::memory_order_acquire)) {// 等待烹饪完成}// 上菜(必须在炒菜之后)std::cout << "服务员: 上菜啦!" << std::endl;
}int main() {std::thread c1(chef_1);std::thread c2(chef_2);std::thread c3(chef_3);std::thread s(server);c1.join();c2.join();c3.join();s.join();return 0;
}

总结:选择合适的内存次序

通过生活中的各种比喻,我们可以更好地理解C++内存模型:

  1. 宽松次序:像咖啡店订单,效率高但顺序不确定
  2. 获取-释放次序:像接力赛跑,有明确的交接点
  3. 顺序一致次序:像军事演练,严格保证顺序但性能较低

在实际编程中:

  • 大多数情况下使用默认的顺序一致次序
  • 在对性能要求极高的场景,可以考虑使用获取-释放次序
  • 只有在非常了解并发编程且需要极致性能时,才使用宽松次序

记住:正确性永远比性能更重要!选择最简单、最安全的内存次序,只有在确实需要优化时才考虑更复杂的选项。

C++ 内存模型的作用:用生活中的例子理解

内存模型的核心作用

C++ 内存模型的主要作用是在多线程环境中提供明确的内存访问规则,确保程序的执行结果可预测且一致。就像交通规则让车辆有序通行一样,内存模型让多线程程序能够正确、高效地协作。

六大核心作用详解

1. 防止数据竞争 - 像超市收银台的排队系统

生活比喻:没有规则的超市收银会一片混乱,多个顾客同时试图付钱,收银员不知道应该处理哪个订单。

#include <iostream>
#include <thread>
#include <atomic>// 没有保护的数据(会导致数据竞争)
int unsafe_counter = 0;// 使用原子操作保护的数据
std::atomic<int> safe_counter(0);void unsafe_increment() {for (int i = 0; i < 100000; ++i) {unsafe_counter++;  // 可能发生数据竞争}
}void safe_increment() {for (int i = 0; i < 100000; ++i) {safe_counter++;    // 原子操作,线程安全}
}int main() {std::thread t1(unsafe_increment);std::thread t2(unsafe_increment);t1.join();t2.join();std::cout << "不安全计数器的结果: " << unsafe_counter << std::endl;std::cout << "应该是: 200000" << std::endl;std::thread t3(safe_increment);std::thread t4(safe_increment);t3.join();t4.join();std::cout << "安全计数器的结果: " << safe_counter << std::endl;std::cout << "正确结果: 200000" << std::endl;return 0;
}

作用:内存模型通过原子操作和内存屏障,确保多个线程不会同时修改同一数据。

2. 保证内存访问顺序 - 像烹饪食谱的步骤顺序

生活比喻:做菜时必须先切菜再炒菜,这个顺序不能乱。内存模型确保某些操作在其他操作之前完成。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>std::atomic<bool> data_ready(false);
int important_data = 0;void producer() {// 准备数据(必须在设置标志之前)important_data = 42;// 使用释放语义:确保之前的操作对所有线程可见data_ready.store(true, std::memory_order_release);
}void consumer() {// 使用获取语义:等待数据准备完成while (!data_ready.load(std::memory_order_acquire)) {// 等待数据准备完成}// 这里一定能看到 important_data = 42std::cout << "获取到重要数据: " << important_data << std::endl;
}int main() {std::thread producer_thread(producer);std::thread consumer_thread(consumer);producer_thread.join();consumer_thread.join();return 0;
}

作用:内存模型确保必要的操作顺序,防止编译器或处理器重排指令导致问题。

3. 提供可见性保证 - 像办公室的公告板

生活比喻:当经理在公告板上张贴重要通知后,所有员工都能立即看到这个变化。

#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>std::atomic<int> notice_board(0);  // 办公室公告板void manager() {std::this_thread::sleep_for(std::chrono::milliseconds(100));notice_board.store(1, std::memory_order_release);  // 张贴通知std::cout << "经理张贴了通知 #1" << std::endl;
}void employee(int id) {// 员工不断检查公告板int last_notice = 0;while (true) {int current_notice = notice_board.load(std::memory_order_acquire);if (current_notice != last_notice) {std::cout << "员工 " << id << " 看到了通知 #" << current_notice << std::endl;last_notice = current_notice;if (current_notice >= 3) break;}}
}void senior_manager() {std::this_thread::sleep_for(std::chrono::milliseconds(200));notice_board.store(2, std::memory_order_release);  // 张贴第二个通知std::cout << "高级经理张贴了通知 #2" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));notice_board.store(3, std::memory_order_release);  // 张贴第三个通知std::cout << "高级经理张贴了通知 #3" << std::endl;
}int main() {std::thread m(manager);std::thread sm(senior_manager);std::thread e1(employee, 1);std::thread e2(employee, 2);m.join();sm.join();e1.join();e2.join();return 0;
}

作用:确保一个线程对数据的修改能够及时被其他线程看到。

4. 实现高效的线程同步 - 像交通信号灯

生活比喻:交通信号灯协调不同方向的车辆,让它们有序通过交叉口,避免碰撞。

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>class TrafficLight {
private:std::atomic<int> green_direction{0};  // 0: 南北, 1: 东西public:void wait_for_green(int direction) {// 等待绿灯while (green_direction.load(std::memory_order_acquire) != direction) {// 谦让CPU时间片std::this_thread::yield();}}void change_light() {// 切换信号灯int current = green_direction.load(std::memory_order_relaxed);green_direction.store(1 - current, std::memory_order_release);std::cout << "信号灯切换: " << (current == 0 ? "南北→东西" : "东西→南北") << std::endl;}
};void car(int id, int direction, TrafficLight& light) {std::cout << "车辆 " << id << " 到达" << (direction == 0 ? "南北" : "东西") << "方向" << std::endl;light.wait_for_green(direction);std::cout << "车辆 " << id << " 通过路口" << std::endl;// 模拟通过路口的时间std::this_thread::sleep_for(std::chrono::milliseconds(100));
}void traffic_controller(TrafficLight& light) {for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(500));light.change_light();}
}int main() {TrafficLight light;std::thread controller(traffic_controller, std::ref(light));std::vector<std::thread> cars;// 创建来自不同方向的车辆for (int i = 0; i < 10; ++i) {int direction = i % 2;  // 交替创建南北和东西方向的车辆cars.emplace_back(car, i, direction, std::ref(light));}controller.join();for (auto& c : cars) {c.join();}return 0;
}

作用:提供各种同步原语(如互斥锁、条件变量),协调线程间的执行顺序。

5. 优化性能 - 像超市的快速收银通道

生活比喻:超市为少量商品的顾客设立快速通道,提高整体效率。宽松内存序就像快速通道,在保证正确性的前提下提高性能。

#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>
#include <vector>// 统计信息 - 使用宽松内存序提高性能
struct StoreStats {std::atomic<int> customers_serviced{0};std::atomic<int> items_scanned{0};std::atomic<double> total_revenue{0.0};
};void cashier(int id, StoreStats& stats, int customer_count) {for (int i = 0; i < customer_count; ++i) {// 模拟收银工作std::this_thread::sleep_for(std::chrono::milliseconds(10));// 使用宽松内存序更新统计信息stats.customers_serviced.fetch_add(1, std::memory_order_relaxed);stats.items_scanned.fetch_add(5 + (id + i) % 10, std::memory_order_relaxed);stats.total_revenue.fetch_add(25.0 + (id + i) % 50, std::memory_order_relaxed);}
}void display_stats(const StoreStats& stats) {// 定期显示统计信息(需要较强的内存序保证准确性)for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(300));// 使用顺序一致语义读取,确保获取完整的数据快照int customers = stats.customers_serviced.load(std::memory_order_seq_cst);int items = stats.items_scanned.load(std::memory_order_seq_cst);double revenue = stats.total_revenue.load(std::memory_order_seq_cst);std::cout << "当前统计: " << customers << " 顾客, " << items << " 商品, ¥" << revenue << " 收入" << std::endl;}
}int main() {StoreStats stats;std::vector<std::thread> cashiers;// 启动多个收银员for (int i = 0; i < 4; ++i) {cashiers.emplace_back(cashier, i, std::ref(stats), 20);}// 启动统计显示线程std::thread stats_thread(display_stats, std::cref(stats));for (auto& c : cashiers) {c.join();}stats_thread.join();// 最终统计(使用强内存序)std::cout << "\n最终统计:" << std::endl;std::cout << "总顾客: " << stats.customers_serviced.load(std::memory_order_seq_cst) << std::endl;std::cout << "总商品: " << stats.items_scanned.load(std::memory_order_seq_cst) << std::endl;std::cout << "总收入: ¥" << stats.total_revenue.load(std::memory_order_seq_cst) << std::endl;return 0;
}

作用:允许开发者在保证正确性的前提下,使用更宽松的内存序来提高性能。

6. 提供可移植的并发抽象 - 像国际电源适配器

生活比喻:国际电源适配器让电器在不同国家的电源标准下都能工作。内存模型为不同硬件架构提供统一的并发编程接口。

#include <iostream>
#include <atomic>
#include <thread>// 可移植的并发计数器
class PortableCounter {
private:std::atomic<int> count{0};public:void increment() {// 在不同平台上都能正确工作的原子操作count.fetch_add(1, std::memory_order_relaxed);}void decrement() {count.fetch_sub(1, std::memory_order_relaxed);}int get() const {// 保证获取到最新值return count.load(std::memory_order_acquire);}// 线程安全的重置操作bool reset_if_equal(int value) {int expected = value;return count.compare_exchange_strong(expected, 0, std::memory_order_release,std::memory_order_relaxed);}
};void worker(PortableCounter& counter, int operations) {for (int i = 0; i < operations; ++i) {if (i % 3 == 0) {counter.decrement();} else {counter.increment();}}
}int main() {PortableCounter counter;std::thread threads[3];// 启动多个工作线程for (int i = 0; i < 3; ++i) {threads[i] = std::thread(worker, std::ref(counter), 1000);}// 定期检查计数器状态for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100));std::cout << "当前计数: " << counter.get() << std::endl;}for (auto& t : threads) {t.join();}std::cout << "最终计数: " << counter.get() << std::endl;// 尝试重置if (counter.reset_if_equal(counter.get())) {std::cout << "计数器已重置: " << counter.get() << std::endl;}return 0;
}

作用:为不同的硬件架构(x86、ARM、PowerPC等)提供一致的并发编程模型。

总结:内存模型的六大作用

作用生活比喻技术实现
防止数据竞争超市收银排队系统原子操作、互斥锁
保证内存访问顺序烹饪食谱步骤内存屏障、内存序
提供可见性保证办公室公告板缓存一致性、内存序
实现线程同步交通信号灯条件变量、信号量
优化性能超市快速通道宽松内存序
提供可移植抽象国际电源适配器标准化的原子操作

C++ 内存模型就像多线程世界的交通规则和基础设施,它确保了:

  1. 安全性:避免数据竞争和不确定行为
  2. 可预测性:程序行为在不同平台上一致
  3. 性能:在保证正确性的前提下最大化并发性能
  4. 可移植性:代码在不同硬件架构上都能正确工作

理解和正确使用内存模型,是编写高效、可靠多线程程序的关键。就像遵守交通规则能让道路更安全畅通一样,遵循内存模型的规则能让多线程程序更稳定高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95741.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95741.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI急速搭建网站:Gemini、Bolt或Jules、GitHub、Cloudflare Pages实战全流程!

文章目录AI急速搭建网站&#xff1a;Gemini、Bolt或Jules、GitHub、Cloudflare Pages实战全流程&#xff01;&#x1f680; 极速建站新范式&#xff1a;Gemini、Bolt.new、GitHub & Cloudflare Pages 全流程实战&#xff01;第一步&#xff1a;创意可视化与代码生成 — Goo…

Qwen2.5-VL实现本地GPTQ量化

本文不生产技术,只做技术的搬运工!! 前言 公开的Qwen2.5-VL模型虽然功能非常强大,但有时面对专业垂直领域的问题往往会出现一些莫名其妙的回复,这时候大家一版选择对模型进行微调,而微调后的模型如果直接部署则显存开销过大,这时就需要执行量化,下面将介绍执行本地GPT…

【Redis】常用数据结构之Hash篇:从常用命令到使用场景详解

目录 1.前言 插播一条消息~ 2.正文 2.1Hash与String对比 2.2常用命令 2.2.1HSET 2.2.2HGET 2.2.3HEXISTS 2.2.4HDEL 2.2.5HKEYS 2.2.6HVALS 2.2.7HGETALL 2.2.8HMGET 2.2.9HLEN 2.2.10HSETNX 2.2.11HINCRBY 2.2.12HINCRBYFLOAT 2.3内部编码 2.3.1. ziplist&…

OSPF基础部分知识点

OSPF基础 前言 路由器 根据 路由表 转发数据包&#xff0c;路由表项 可通过手动配置 和动态路由协议 生成。&#xff08;两种生成方式&#xff09;静态路由比动态路由使用更少的带宽&#xff0c;并且不占用CPU资源来计算和分析路由更新。当网络结构比较简单时&#xff0c;只需配…

Flutter 真 3D 游戏引擎来了,flame_3d 了解一下

在刚刚结束的 FlutterNFriends 大会上&#xff0c;Flame 展示了它们关于 3D 游戏的支持&#xff1a;flame_3d &#xff0c;Flame 是一个以组件系统&#xff08;Flame Component System, FCS&#xff09;、游戏循环、碰撞检测和输入处理为核心的 Flutter 游戏框架&#xff0c;而…

无需公网IP,电脑随时与异地飞牛同步互联保持数据一致性

最近小白有这样一个烦恼&#xff1a;随身带着的电脑每天都在更新内容&#xff0c;于是就会有很多很多的存稿。电脑的空间开始变得不够用了。各式各样的图片、视频、文稿等内容&#xff0c;如果要整理到飞牛NAS上&#xff0c;好像很麻烦&#xff0c;而且每次都是需要回到家里才能…

数据库中间件ShardingSphere v5.2.1

数据库中间件ShardingSphere v5.2.1 文章目录数据库中间件ShardingSphere v5.2.1一 概述1 数据库的瓶颈2 优化的手段3 主从复制4 读写分离5 分库分表5.1 背景5.2 垂直分片5.3 水平分片6 ShardingSphere简介二 ShardingSphere-JDBC讲解1 读写分离实现1.1 基于Docker搭建MySQL主从…

[Upscayl图像增强] Electron主进程命令 | 进程间通信IPC

第三章&#xff1a;Electron主进程命令 欢迎回来&#x1f43b;‍❄️ 在第一章&#xff1a;渲染器用户界面&#xff08;前端&#xff09;中&#xff0c;我们探索了您与之交互的按钮和菜单。然后在第二章&#xff1a;AI模型中&#xff0c;我们了解了让您的图像看起来更棒的&qu…

电竞护航小程序成品搭建三角洲行动护航小程序开发俱乐部点单小程序成品游戏派单小程序定制

功能列表&#xff1a;商家入驻 成为管事 平台公告 客服密钥 客服管理 发单模板 快捷发单 自定义发单 打手入驻 订单裁决 即时通讯 &#xff08;接单者员与发单者&#xff09; 打手排行 邀请排行 余额提现技术栈&#xff1a;前端uniapp 后端java

Redis数据库基础

1.关系型数据库和NoSQL数据库数据库主要分为两大类:关系型数据库与NoSQL数据库关系型数据库&#xff0c;是建立在关系模型基础是的数据库&#xff0c;其借助集合代数等数学概念和方法来处理数据库中的数据主流的MySQL&#xff0c;Oracle&#xff0c;MS SQL Server 和DB2都属于这…

【Java实战㉗】Java日志框架实战:Logback与Log4j2的深度探索

目录一、日志框架概述1.1 日志的作用1.2 常见日志框架1.3 日志级别二、Logback 框架实战2.1 Logback 依赖导入2.2 Logback 配置文件2.3 日志输出格式自定义2.4 Logback 进阶配置三、Log4j2 框架实战3.1 Log4j2 依赖导入3.2 Log4j2 配置文件3.3 Log4j2 与 SLF4J 整合3.4 日志框架…

基于WFOA与BP神经网络回归模型的特征选择方法研究(Python实现)

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取 或者私信获取。 1.项目背景 在大数据分析与智能建模领域&#xff0c;高维数据广泛存在于金融预测、环境监测和工业过程控制等场景…

​​AI生成PPT工具推荐,从此以后再也不用担心不会做PPT了​​

对于很多人老说&#xff0c;做ppt实在太麻烦了&#xff0c;快速制作出专业且美观的PPT成为众多人的需求&#xff0c;AI生成PPT工具应运而生&#xff0c;极大地提升了PPT制作的效率。以下为大家推荐多个实用的AI生成PPT工具。 1、AiPPT星级评分&#xff1a;★★★★★ AiPPT是一…

CentOS系统停服,系统迁移Ubuntu LTS

CentOS官方已全面停止维护CentOS Linux项目&#xff0c;公告指出 CentOS 7在2024年6月30日停止技术服务支持&#xff0c;(在此之前 2022年1月1日起CentOS官方已经不再对CentOS 8提供服务支持&#xff09;&#xff0c;详情见CentOS官方公告。 一、系统迁移评估 用户需要开始计…

Linux知识回顾总结----文件系统

上章讲的是 os 如果管理被打开的文件&#xff0c;那么没有被打开的文件&#xff08;也就是在磁盘单中的文件&#xff09;使用文件系统进行管理。了解完这一章&#xff0c;我们就可以理解我们如果想要打开一个文件的是如何找到整个文件&#xff0c;然后如何把它加载到内存中的&a…

iOS蓝牙使用及深入剖析高频高负载传输丢包解决方案(附源码)

最近开发了一套iOS原生的蓝牙SDK&#xff0c;总结了一些有价值的踩过的坑&#xff0c;分享出来给有需要的同学做个参考。 一、蓝牙的使用 iOS有一套封装好的完善的蓝牙API &#xff0c;可以很便捷的实现与蓝牙的连接和通信&#xff0c;蓝牙通信的大体流程如下&#xff0c;先对基…

Python 正则表达式实战:用 Match 对象轻松解析拼接数据流

摘要 这篇文章围绕 Python 的正则表达式 Match 对象&#xff08;特别是 endpos、lastindex、lastgroup 以及 group / groups 等方法/属性&#xff09;做一个从浅入深、贴近日常开发场景的讲解。我们会给出一个真实又常见的使用场景&#xff1a;解析由设备/服务发来的“拼接式”…

基于Pygame的六边形战术推演系统深度剖析——从数据结构到3D渲染的完整实现(附完整代码)

1. 项目概述与技术选型 战术推演系统是军事训练和游戏开发中的重要组成部分,它能够模拟真实的战术场景,为用户提供策略思考的平台。本文将深入分析一套基于Python Pygame框架开发的城市巷战战术推演系统,该系统采用六边形网格布局,实现了恐怖分子与反恐精英的对抗模拟,具…

支持二次开发的代练App源码:订单管理、代练监控、安全护航功能齐全,一站式解决代练护航平台源码(PHP+ Uni-app)

一、技术架构&#xff1a;高性能与跨平台的核心支撑前端框架Uni-app&#xff1a;基于Vue.js的跨平台框架&#xff0c;支持编译至微信小程序、H5、iOS/Android App及PC端&#xff0c;代码复用率超80%&#xff0c;显著降低开发成本。实时通信&#xff1a;集成WebSocket实现订单状…

AI热点周报(8.31~9.6): Qwen3‑Max‑Preview上线、GLM-4.5提供一键迁移、Gemini for Home,AI风向何在?

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录一、3分钟速览版&#xff1a;一张表看懂本周AI大事二、国内&#xff1a;模型与生态的…