C++并发编程简史:一段你不得不知道的传奇

开发
C++17 和 C++20 更是锦上添花,带来了shared_mutex、协程等炫酷特性 ✨。看看现在的 C++,简直就像变了个人似的,让我们能优雅地驾驭多核时代的浪潮!

嘿,让我们来聊聊 C++ 并发编程的精彩旅程吧!🚀 想象一下,在 1998 年那个"单线程时代",C++ 就像个固执的独行侠,完全不懂多线程的魅力。开发者们只能依赖各种系统专属的 API,就像在用方言交流一样难懂 😅。

后来,像 Boost.Thread 这样的"翻译官"🗣️ 出现了,让并发编程变得优雅了不少。但真正的转折点是 2011 年的 C++11 标准,它给 C++ 装上了"并发引擎"🏎️,带来了std::thread、std::mutex 等超强工具。

C++17 和 C++20 更是锦上添花,带来了shared_mutex、协程等炫酷特性 ✨。看看现在的 C++,简直就像变了个人似的,让我们能优雅地驾驭多核时代的浪潮!🌊

C++多线程的前世今生

啊,让我们坐上时光机,回到1998年 ⏰。那时的C++还是个"单线程主义者" - 它根本不承认多线程的存在!就像一个固执的老顽固,坚持"一次只做一件事"。不仅如此,它连个像样的内存模型都没有。程序员们想写多线程程序?抱歉,除非你愿意依赖编译器特定的扩展... 🤷

但是呢,编译器厂商们可不这么想。他们看到了程序员们渴望多线程的眼神 👀。于是乎,他们开始通过各种方式支持多线程。Windows程序员有了Windows API,Unix党有了POSIX线程库。虽然这些支持比较基础(基本就是把C的API搬过来),但是好歹能用不是? 😅

来看个具体例子。假设我们想写个简单的多线程程序,在Windows下可能是这样:

#include <windows.h>
#include <stdio.h>

// 🧵 线程函数 - 就像一个独立的小工人
DWORD WINAPI PrintHello(LPVOID lpParam) {
    printf("Hello from Windows thread!\n");  // 👋 打个招呼
    return0;  // ✅ 工作完成,安全退出
}

int main() {
    // 🏭 创建新线程 - 就像开启一条新的生产线
    HANDLE hThread = CreateThread(
        NULL,       // 🔒 默认安全属性
        0,         // ⚖️ 默认栈大小
        PrintHello, // 👨🔧 指定工人要做的工作
        NULL,       // 📦 没有参数传递
        0,         // 🚀 立即启动线程
        NULL        // 🏷️ 不需要线程ID
    );
    
    // ⏳ 等待线程完成 - 像等待工人完成工作
    WaitForSingleObject(hThread, INFINITE);
    
    // 🧹 清理线程句柄 - 收拾好工作台
    CloseHandle(hThread);
    return0;  // 🎉 主程序圆满完成
}

而在POSIX系统上,同样的程序要这么写:

#include <pthread.h>  // 🧵 POSIX 线程库头文件
#include <stdio.h>    // 📝 标准输入输出

// 🏃 线程执行函数 - 就像一个独立的工作者
void* PrintHello(void* arg) {
    printf("Hello from POSIX thread!\n");  // 👋 打个招呼
    returnNULL;  // ✅ 工作完成,安全返回
}

int main() {
    pthread_t thread;  // 📌 声明线程变量,像是工人的工牌

    // 🚀 创建并启动新线程
    // 参数分别是:
    // 1️⃣ 线程标识符的指针
    // 2️⃣ 线程属性(NULL表示使用默认属性)
    // 3️⃣ 线程将要执行的函数
    // 4️⃣ 传递给线程函数的参数
    pthread_create(&thread, NULL, PrintHello, NULL);

    // ⏳ 等待线程完成 - 就像等待工人干完活
    pthread_join(thread, NULL);
    
    return0;  // 🎉 主程序圆满结束
}

看到没? 同样的功能,两种完全不同的写法! 这简直就像是在写两种不同的语言。

但是C++程序员们可不满足于此。他们想要更优雅的解决方案! 于是像MFC、Boost这样的库横空出世了 🦸♂️。这些库把底层的API包装得漂漂亮亮的,还加入了很多实用的功能。比如说,使用Boost.Thread的话,上面的代码就可以写成这样:

#include <boost/thread.hpp>    // 🧵 Boost的线程库
#include <iostream>           // 📝 输入输出流

// 🎯 线程要执行的任务函数
void PrintHello() {
    std::cout << "Hello from Boost thread!" << std::endl;  // 👋 打个招呼
}

int main() {
    // 🚀 创建并启动新线程
    boost::thread t(PrintHello);  // 🏃 线程开始执行
    
    t.join();  // ⏳ 等待线程完成
    return0;  // 🎉 主程序结束
}

是不是清爽多了? 🌟 特别是它引入了RAII(资源获取即初始化)的概念,让互斥锁的使用变得更安全。就像这样:

boost::mutex mtx;  // 🔒 创建一个互斥锁 - 就像一把神奇的锁
{
    // 🎯 RAII方式加锁 - 进入区域自动上锁
    boost::mutex::scoped_lock lock(mtx);  
    
    // 🔐 这里是受保护的代码区域
    // 💫 只有一个线程能在同一时间进入这里
    // 🎭 可以安全地访问共享资源
    // 📝 比如修改共享数据...
    
} // 🔓 离开作用域时自动解锁 - 非常优雅且安全!
  // 🛡️ 即使发生异常也能确保解锁

但是呢,这些库再好,终究不是语言标准的一部分。跨平台时还是会遇到各种奇怪的问题 🤔。直到C++11的出现,这个问题才得到了彻底的解决。但这个,就是另外一个精彩的故事了... ✨

这段历史告诉我们什么呢?它让我们看到了C++社区的创造力和适应力。即使在标准不支持的情况下,依然找到了方法来满足多线程编程的需求。这种精神,才是真正让C++成为一门伟大语言的原因啊! 🌈

C++11: 多线程的春天来了! 

2011年,C++终于迎来了期待已久的官方多线程支持! 就像给C++装上了一台"并发引擎" 🏎️。来看看这个全新的世界吧:

#include <iostream>     // 📝 标准输入输出流
#include <thread>       // 🧵 线程支持
#include <mutex>        // 🔒 互斥锁支持

// 🛡️ 保护std::cout的互斥锁,防止输出混乱
std::mutex cout_mutex;  

// 👨🍳 咖啡师的工作流程
void MakeCoffee() {
    std::lock_guard<std::mutex> lock(cout_mutex);  // 🔐 自动加锁解锁,很安全!
    std::cout << "☕ 正在煮咖啡..." << std::endl;  // 📢 安全地输出信息
}

// 👩🍳 茶师的工作流程
void MakeTea() {
    std::lock_guard<std::mutex> lock(cout_mutex);  // 🔐 获取输出权限
    std::cout << "🍵 正在泡茶..." << std::endl;    // 📢 安全地输出信息
}

int main() {
    // 🏭 开启两条并行的工作流水线
    std::thread coffee_master(MakeCoffee);    // 🚀 启动咖啡师的线程
    std::thread tea_master(MakeTea);          // 🚀 启动茶师的线程
    
    // ⌛ 等待两位师傅完成他们的工作
    coffee_master.join();    // 🤝 等待咖啡师
    tea_master.join();       // 🤝 等待茶师
    
    return0;  // 🎉 圆满完成!
}

看到了吗?这就是现代C++的魅力! 🌟 不需要再记那些晦涩的API了,一切都变得如此自然。就像用中文写代码一样顺畅~

而且C++11不只是提供了基础的线程支持,它还给了我们一整套并发工具箱! 

比如说,如果我们想让咖啡师和茶师轮流工作,可以用条件变量:

首先是基本的头文件和全局变量设置:

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

// 🎭 我们需要这些工具来协调咖啡师和茶师的工作
std::mutex mtx;                    // 🔒 互斥锁:就像休息室的门锁
std::condition_variable cv;        // 🚦 条件变量:像是咖啡师和茶师之间的对讲机
bool coffee_ready = false;         // 🎯 状态标志:咖啡完成的信号

接下来是咖啡师的工作流程:

void BaristaMakeCoffee() {
    // 第一步:宣布开始工作 📢
    {
        std::lock_guard<std::mutex> lock(mtx);    // 🔐 先锁门
        std::cout << "咖啡师: 开始煮咖啡..." << std::endl;
    }   // 🔓 自动解锁,其他人可以用输出了

    // 第二步:认真煮咖啡 ☕️
    std::this_thread::sleep_for(std::chrono::seconds(2));  // ⏰ 煮咖啡需要时间

    // 第三步:完成并通知茶师 📣
    {
        std::lock_guard<std::mutex> lock(mtx);
        coffee_ready = true;   // 🚀 设置完成标志
        std::cout << "咖啡师: 咖啡准备好了! 🎉" << std::endl;
    }
    cv.notify_one();  // 📱 给茶师发消息
}

然后是茶师的工作流程:

void TeaMasterWaitAndMakeTea() {
    // 第一步:等待咖啡师的信号 👂
    {
        std::unique_lock<std::mutex> lock(mtx);   // 🔓 特殊的锁,可以被条件变量解开
        cv.wait(lock, [] { return coffee_ready; });  // 🚦 等待咖啡完成信号
    }

    // 第二步:开始泡茶 🍵
    {
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << "茶师: 咖啡好了,我开始泡茶..." << std::endl;
    }

    std::this_thread::sleep_for(std::chrono::seconds(1));  // ⏲️ 泡茶也需要时间

    // 第三步:完成泡茶 ✨
    {
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << "茶师: 茶也准备好了! 🎊" << std::endl;
    }
}

最后是主程序的协调部分:

int main() {
    std::cout << "☕️ 咖啡厅开始营业..." << std::endl;

    // 🎬 安排两位师傅开始工作
    std::thread barista(BaristaMakeCoffee);       // 👨🍳 咖啡师上岗
    std::thread tea_master(TeaMasterWaitAndMakeTea); // 👩🍳 茶师上岗

    // ⌛️ 等待工作完成
    barista.join();    // 🤝 等咖啡师完成
    tea_master.join(); // 🤝 等茶师完成

    std::cout << "🏪 今天的饮品都准备完成啦!" << std::endl;
    return 0;

这不就是现实生活中的场景吗? 茶师要等咖啡师完成才开始工作,多么和谐的工作流程! 😊

  • C++11还引入了很多其他好用的工具:
  • std::async 和std::future 用于异步任务 🎯
  • std::atomic 用于原子操作 ⚛️

各种同步原语(互斥锁、条件变量、信号量等) 🔒

最重要的是,这些工具都是标准库的一部分,意味着你的代码可以在任何支持C++11的平台上运行! 再也不用担心跨平台问题了~ 🌈

C++17与C++20: 并发编程的新篇章

哇!让我们一起来看看C++17和C++20在并发编程方面带来的超级大礼包吧! 

还记得以前处理多个互斥锁时那种提心吊胆的感觉吗?生怕搞出死锁来 😱。现在好啦!C++17给我们带来了超级实用的scoped_lock!它就像一个聪明的管家 🫅,自动帮我们按照正确的顺序处理多个锁,再也不用担心死锁啦!

// 看看这个超级管家是怎么工作的 🎩
std::mutex m1, m2, m3;  // 三把小锁 🔐
{
    std::scoped_lock locks(m1, m2, m3);  // 交给管家,一切都搞定!
    // 安心写代码... 😌
}  // 管家会自动帮我们解锁,贴心! ✨

C++17还给我们带来了shared_mutex - 这简直就是给读写操作开了个派对! 🎉 多个读者可以一起蹦迪,但写者需要包场独舞~ 这不就是传说中的"共享-独占"模式嘛!

std::shared_mutex party_room; 🎪
// 读者们可以一起嗨! 💃🕺
{
    std::shared_lock<std::shared_mutex> group_entry(party_room);
    // 大家一起读数据,热闹! 🎊
}
// 写者需要包场 🎭
{
    std::unique_lock<std::shared_mutex> vip_entry(party_room);
    // 独自修改数据,安静... 🤫
}

到了C++20,简直就是开了挂! 🚀 它带来了jthread(智能线程)、闭锁、屏障、信号量这些厉害角色!特别是jthread,它就像是给普通线程装上了自动驾驶系统 🚗,不用手动join,还能随时喊停!

// 来看看这个智能线程有多聪明 🧠
void future_threads() {
    std::jthread smart_worker([](std::stop_token stoken) {
        while (!stoken.stop_requested()) {  // 随时准备停车! 🚦
            // 干活ing... 👨💻
        }
    });
    // 不用管它,下班自己会收工! 😎
}

最让人兴奋的是协程的加入! 🎢 它就像给你的代码加上了任意门,可以随时暂停、继续,玩出各种花样!比如这个生成斐波那契数列的协程,简直优雅得不要不要的~ ✨

generator<int> fibonacci() {  // 数学界的魔术师 🎩
    int a = 0, b = 1;
    while (true) {
        co_yield a;  // 变个魔术,产生下一个数! ✨
        auto tmp = a;
        a = b;
        b = tmp + b;
    }
}

有了这些强大的新工具,写并发代码简直就像在玩积木一样有趣! 🎮 再也不用被那些繁琐的同步问题困扰啦!让我们一起拥抱这个多线程的新时代吧! 🌈

性能与调试提示

嘿,小伙伴们!写多线程代码时,最容易掉进的坑就是"线程越多越好"的误区啦!🤔 这就像开派对一样,人多不一定热闹,可能反而会踩踩踩!

来看个常见的"踩坑"案例:

// ❌ 不推荐
void process_items(const std::vector<Item>& items) {
    std::vector<std::thread> threads;
    // 每个任务都开一个新线程,CPU: 我太难了! 😱
    for (constauto& item : items) {
        threads.emplace_back([&item]{ process(item); });
    }
}

// ✅ 推荐
void process_items(const std::vector<Item>& items) {
    // 让CPU告诉我们它能同时处理多少线程 🤖
    constauto thread_count = std::thread::hardware_concurrency();
    ThreadPool pool(thread_count);  // 建个温馨的线程小家庭 🏠
    
    // 往线程池丢任务,它自己会安排得明明白白的 📝
    for (constauto& item : items) {
        pool.enqueue([&item]{ process(item); });
    }
}

还有个省心小技巧:如果只是想给个数字加加减减,用原子操作就够啦!🎈 就像点外卖,一个人点完全程序,比叫一群人一起点要顺畅多了:

// 这个计数器特别乖,不用加锁也不会乱 🎪
std::atomic<int> counter{0};  
counter++;  // 一个顶一个,稳得很!💪

记住啦:线程不是越多越好,原子操作不是越多越妙,关键是要用对地方!就像调味料一样,适量才能让代码更美味~ 🍳

写并发代码就是这样,与其把时间花在处理复杂的同步问题上,不如好好想想怎么让架构更简单!毕竟,能用一把锁解决的问题,干嘛要用两把呢?😉

责任编辑:赵宁宁 来源: everystep
相关推荐

2020-10-21 09:36:40

Vue项目技巧

2022-08-30 23:54:42

MySQL数据库工具

2022-10-27 09:55:00

2017-08-10 16:54:47

MySQL优化MySQL

2020-02-13 18:05:18

数组reduce前端

2023-08-29 08:41:42

2022-08-08 11:13:35

API接口前端

2015-09-22 10:03:25

大数据秘诀

2015-09-23 10:27:04

大数据秘诀

2017-11-02 06:51:38

5G移动网络技术

2020-09-02 07:30:31

前端Vue.set代码

2019-07-17 10:55:40

Kubernetes工具Katacoda

2017-08-16 18:03:12

Docker安全工具容器

2020-05-18 09:33:27

前端开发工具

2017-09-22 09:10:41

2009-05-31 09:02:23

2009-11-11 16:48:29

Visual C++

2010-08-27 10:40:55

Android

2011-03-31 10:46:54

LinuxCLI软件

2010-08-18 11:36:40

DB2简史
点赞
收藏

51CTO技术栈公众号