在 C++11 之前,C++ 没有原生的线程支持,开发者需要依赖于操作系统提供的线程库,不同平台的实现各不相同,这导致了跨平台开发的复杂性。C++11 引入了标准线程库,使线程的创建和管理变得更加统一、易用且跨平台。


一、线程的创建

在 C++11 中,可以通过 std::thread 类创建线程。std::thread 类提供了几种不同的构造方式,用于灵活地创建线程并执行任务。以下是几种常见的线程创建方法:

1.1 头文件与基本概念

使用线程需要包含头文件:

1
#include <thread>

线程的基本构造函数如下:

  • 默认构造函数thread() noexcept 创建一个空线程对象,不执行任何任务,也不创建子线程。

  • 带参数的构造函数

    1
    2
    
    template< class Function, class... Args >
    explicit thread(Function&& fx, Args&&... args);
    

    这个构造函数用于创建线程并启动任务。fx 是任务函数,args 是传递给任务函数的参数。fx 可以是普通函数、lambda 表达式、类成员函数等。

1.2 创建线程的注意事项

  • 不可复制std::thread 不支持复制,线程对象之间不能拷贝。
  • 任务结束后自动终止:线程的任务函数执行完毕后,线程将自动终止。
  • 主线程退出影响子线程:如果主线程退出,所有尚未完成的子线程会被强行终止。因此在实际编程中,通常需要通过 join()detach() 处理子线程的资源。

二、使用不同方式创建线程的示例

2.1 使用普通函数创建线程

普通函数是最常见的线程任务类型。在下面的例子中,func 是一个普通函数,接收两个参数并通过 std::thread 创建多个线程来执行它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <unistd.h>

void func(int bh, const std::string& str) {
    for (int ii = 1; ii <= 10; ii++) {
        std::cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << std::endl;
        Sleep(1);   // 模拟工作负载,休眠1秒
    }
}

int main() {
    std::thread t1(func, 3, "我是一只傻傻鸟。");  // 创建线程 t1,执行 func
    std::thread t2(func, 8, "我有一只小小鸟。");  // 创建线程 t2,执行 func

    t1.join(); // 主线程等待 t1 结束
    t2.join(); // 主线程等待 t2 结束
    return 0;
}

2.2 使用 lambda 表达式创建线程

C++11 提供了 lambda 表达式的支持,简化了函数对象的创建。以下是使用 lambda 表达式创建线程的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <thread>
#include <unistd.h>

int main() {
    auto f = [](int bh, const std::string& str) {
        for (int ii = 1; ii <= 10; ii++) {
            std::cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << std::endl;
            Sleep(1);  // 模拟工作负载,休眠1秒
        }
    };

    std::thread t3(f, 3, "我是一只傻傻鸟。");  // 使用 lambda 表达式创建线程
    t3.join(); // 主线程等待 t3 结束
    return 0;
}

2.3 使用仿函数(Functor)创建线程

仿函数是通过重载 operator() 实现的类,可以像普通函数一样调用。以下是通过仿函数创建线程的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <unistd.h>

class MyThread {
public:
    void operator()(int bh, const std::string& str) {
        for (int ii = 1; ii <= 10; ii++) {
            std::cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << std::endl;
            Sleep(1);  // 模拟工作负载,休眠1秒
        }
    }
};

int main() {
    std::thread t4(MyThread(), 3, "我是一只傻傻鸟。");  // 创建线程并使用仿函数执行任务
    t4.join(); // 主线程等待 t4 结束
    return 0;
}

2.4 使用类的静态成员函数创建线程

静态成员函数不依赖于具体的对象,因此可以直接使用类名调用。以下是通过静态成员函数创建线程的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <unistd.h>

class MyThread {
public:
    static void func(int bh, const std::string& str) {
        for (int ii = 1; ii <= 10; ii++) {
            std::cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << std::endl;
            Sleep(1);  // 模拟工作负载,休眠1秒
        }
    }
};

int main() {
    std::thread t5(MyThread::func, 3, "我是一只傻傻鸟。");  // 使用静态成员函数创建线程
    t5.join(); // 主线程等待 t5 结束
    return 0;
}

2.5 使用类的非静态成员函数创建线程

非静态成员函数与特定的对象实例关联,调用时需要传递对象指针。以下是通过非静态成员函数创建线程的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>
#include <unistd.h>

class MyThread {
public:
    void func(int bh, const std::string& str) {
        for (int ii = 1; ii <= 10; ii++) {
            std::cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << std::endl;
            Sleep(1);  // 模拟工作负载,休眠1秒
        }
    }
};

int main() {
    MyThread obj;  // 创建类的对象
    std::thread t6(&MyThread::func, &obj, 3, "我是一只傻傻鸟。");  // 创建线程,并绑定对象成员函数

    t6.join(); // 主线程等待 t6 结束
    return 0;
}

三、总结

C++11 通过 std::thread 类为多线程编程提供了跨平台、统一的接口。开发者可以使用普通函数、lambda 表达式、仿函数、类的静态和非静态成员函数来创建线程。每种方式都有其使用场景和优势,开发者应根据实际需求选择合适的线程创建方法。

注意事项

  • std::thread 不支持复制,只能通过移动操作来转移资源。
  • 在使用非静态成员函数时,需要传递类对象的指针,确保对象的生命周期比线程长。
  • join() 用于阻塞主线程,等待子线程结束;detach() 可以使子线程与主线程分离,独立运行。