C++线程创建

引言

在C++11标准之前,并没有引入线程这个概念,如果我们想要在C++中实现多线程,需要借助操作系统平台提供的API,例如在Linux中的或者windows下的。在C++11为了提供语言层面的多线程,引入了<thread>头文件,它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。本节我们先来了解线程创建部分。

创建线程

创建线程的关键是传递一个callable的对象。

普通函数无参

#include <iostream>
#include <thread>
using namespace std;

void basicFunc() {
    cout << "Hello World!" << endl;
}

int main() {
    thread t(basicFunc);
    t.join();

    return 0;
}

输出:

Hello World!

普通函数 + 参数

#include <iostream>
#include <thread>
using namespace std;

void printMsg(int x, const string& s) {
    cout << "x = " << x << " s = " << s << endl;
}

int main() {
    thread t(printMsg, 100, move(string("Hello World!")));
    t.join();

    return 0;
}

输出:

x = 100 s = Hello World!

类非静态成员函数

#include <iostream>
#include <thread>
using namespace std;

class Task {
public:
    void work(int hours) {
        cout << "works for " << hours << " hours" << endl;
    }
};
int main() {

    Task task;
    // 构造线程: 成员函数指针 + 对象指针 + 参数列表
    thread t(&Task::work, &task, 8);
    t.join();

    return 0;
}

输出:

works for 8 hours

类静态成员函数

#include <iostream>
#include <thread>
using namespace std;

class Util {
public:
    static void staticFunc() { cout << "静态成员函数线程" << endl; }
};

int main() {
    thread t1(Util::staticFunc); // 静态成员函数
    t1.join();
    return 0;
}

输出:

静态成员函数线程

函数对象(仿函数)

#include <iostream>
#include <thread>
using namespace std;

class Counter {
private:
    int count;
public:
    Counter(int init) : count(init) {}
    // 重载()操作符
    void operator()(int step) {
        for (int i = 0; i < 3; ++i) {
            count += step;
            cout << "当前计数:" << count << endl;
        }
    }

    int getCount() {
        return count;
    }
};

int main() {
    Counter counter(10);
    cout << "before thread: " << counter.getCount() << endl;
    thread t(counter, 5); // 函数对象作为任务(会拷贝对象)
    t.join();
    cout << "after thread: " << counter.getCount() << endl;

    return 0;
}

输出:

before thread: 10
当前计数:15
当前计数:20
当前计数:25
after thread: 10

lambda表达式

#include <iostream>
#include <thread>
using namespace std;

int main() {
    int num = 100;
    string msg = "Lambda线程";

    // 捕获外部变量:值捕获msg,引用捕获num
    thread t([msg, &num]() {
        num += 200;
        cout << "msg=" << msg << ", num=" << num << endl;
    });

    t.join();
    cout << "主线程num:" << num << endl; // 输出300(被线程修改)
    return 0;
}

输出:

msg=Lambda线程, num=300
主线程num:300

参数传递与移动语义

在上文的创建线程中,对线程的参数传递做了几个示例,这里再做一下总结。

普通参数传递

值传递:直接传值会将参数拷贝到线程栈空间中;

引用传递:使用std::ref或者std::cref包装,可以将主线程中的变量按引用方式传递到线程中

移动语义

对于大对象场景,例如string、vector,不可拷贝对象unique_ptr, thread可通过std::move将对象转移到线程内部。

这里以string为例举例说明:

#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include <utility>
using namespace std;

void processString(string&& s) {
    s = "处理后:" + s;
    cout << s << endl;
}

int main() {
    vector<string> task_list = {"任务1", "任务2", "任务3"};
    vector<thread> threads;

    for (auto& s : task_list) {
        // 移动传递string,避免拷贝大对象
        threads.emplace_back(processString, move(s));
    }

    for (auto& t : threads) t.join();

    for (int i = 0; i < task_list.size(); i++)  {
        if (task_list[i].empty()) {
            cout << "task " << i << " empty" << endl;
        }
    }
    return 0;
}

输出:

处理后:任务1
处理后:任务2
处理后:任务3
task 0 empty
task 1 empty
task 2 empty

这里模拟大任务场景下,通过移动语义避免copy开销。

async/packaged_task方式创建线程

为了更方便地使用thread,C++11中还引入了async和packaged_task两种方式创建线程。可以认为是高层并发组件。可以认为是thread的封装和扩展(扩展了结果传递、任务绑定、异常转发等能力)。

这是因为thread作为底层线程工具,存在两个核心的缺陷,导致日常开发效率低、易出错:

  • 缺陷 1:无法直接获取任务返回值std::thread 的线程函数返回值会被忽略,需手动用「共享变量 + 互斥锁」传递结果(繁琐且易引发线程安全问题);
  • 缺陷 2:无法传递线程内异常:线程函数抛出的未捕获异常会直接导致程序崩溃,需手动在线程内捕获并传递到主线程(代码冗余);
  • 缺陷 3:任务与线程强耦合std::thread 直接绑定执行流,无法单独存储 “任务”(如线程池场景需要先存储任务再分配线程)。

std::packaged_taskstd::async 的核心作用,就是通过封装解决这些缺陷,让开发者聚焦 “任务逻辑” 而非 “线程管理细节”。

std::packaged_task

std::packaged_task 是一个任务包装器,核心功能是:

  • 包装任意可调用对象(函数、Lambda、函数对象等);
  • 将任务的返回值与一个 std::future 对象绑定;
  • 任务执行后,返回值会自动存入 future,主线程通过 future.get() 获取(支持同步等待)。

需要注意 CLion中的默认编译器是精简版,不支持packaged_task

#include <iostream>
#include <thread>
#include <packaged_task>
#include <future>
#include <string>
using namespace std;

// 任务:处理字符串并返回
string process(string s) {
    return "[处理后] " + s;
}

int main() {
    // 1. 包装任务:绑定任务函数和结果类型(string(string))
    packaged_task<string(string)> task(process);

    // 2. 获取与任务绑定的 future(用于接收返回值)
    future<string> fut = task.get_future();

    // 3. 手动创建线程执行任务(必须用 move 转移 task,因为它不可拷贝)
    thread t(move(task), "测试任务");
    t.join(); // 等待线程完成

    // 4. 获取任务返回值(get() 会阻塞,直到任务完成)
    string res = fut.get();
    cout << res << endl; // 输出:[处理后] 测试任务

    return 0;
}

std::async

std::async 是一个异步任务创建函数,核心功能是:

  • 自动包装任务(无需手动创建 packaged_task);
  • 自动创建线程(或复用线程池)执行任务;
  • 自动返回 std::future 对象,供主线程获取结果;
  • 无需手动管理 std::threadjoin()/detach()(内部自动处理线程生命周期)。
#include <iostream>
#include <future>
#include <string>
using namespace std;

string process(string s) {
    return "[处理后] " + s;
}

int main() {
    // 1. 自动创建异步任务,返回 future(无需手动管理 thread)
    future<string> fut = async(process, "测试任务");

    // 主线程可并行执行其他逻辑
    cout << "主线程执行其他工作..." << endl;

    // 2. 获取结果(若任务未完成,get() 会阻塞)
    string res = fut.get();
    cout << res << endl; // 输出:[处理后] 测试任务

    return 0;
}

线程异常处理

线程函数中未捕获的异常会导致 std::terminate() 调用,程序直接崩溃(无论是否 joindetach)。

举例:

#include <iostream>
#include <thread>
#include <stdexcept>
using namespace std;

void throwFunc() {
    throw runtime_error("线程内异常"); // 未捕获异常
}

int main() {
    thread t(throwFunc);
    t.join(); // 程序依然崩溃
    return 0;
}

输出:

terminate called after throwing an instance of 'std::runtime_error'
  what():  线程内异常

对于这种情况有两种处理方式:

方式一:线程内部处理

#include <iostream>
#include <thread>
#include <stdexcept>

using namespace std;

void safeFunc() {
    try {
        throw runtime_error("线程内异常");
    } catch (const exception& e) {
        // 线程内处理异常
        cout << "线程内捕获异常:" << e.what() << endl;
    }
}

int main() {
    thread t(safeFunc);
    t.join();
    cout << "主线程正常执行" << endl;  // 会执行
    return 0;
}

方式 2:通过 std::future 传递异常到主线程(推荐)

std::async/std::packaged_task 会自动捕获线程内的异常,存储到 future 中,主线程调用 get() 时会重新抛出异常,便于统一处理。

#include <iostream>
#include <future>
#include <stdexcept>
#include <string>

using namespace std;

string throwFunc() {
    // 线程内抛出异常
    throw runtime_error("线程处理失败:数据格式错误");
    return "处理成功";  // 永远不会执行
}

int main() {
    future<string> fut = async(throwFunc);

    try {
        string res = fut.get();  // get() 会重新抛出线程内的异常
        cout << res << endl;
    } catch (const exception& e) {
        // 主线程统一捕获处理
        cout << "主线程捕获线程异常:" << e.what() << endl;
    }

    cout << "主线程正常继续" << endl;  // 会执行
    return 0;
}
主线程捕获线程异常:线程处理失败:数据格式错误
主线程正常继续

主子线程交互

join方法

主线程阻塞等待子线程完成,回收线程资源。

detatch方法

子线程与主线程分离,后台独立运行,由系统自动回收资源(需确保子线程访问的变量生命周期足够长)。

总结

本小节详细介绍了C++线程的创建相关内容,在使用时需要基于场景灵活选择创建方式,并注意参数传递时移动语义的使用。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇