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
英文地址,請注明出處:http://en.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;模型與生態的…