Cpp(七) std::thread 标准库多线程

本文详细介绍了C++14中的多线程使用,包括不使用线程与使用线程的区别,以及std::thread的接口如join()、detach()、get_id()和swap()。还探讨了mutex和lock_guard在同步中的作用,展示了线程池的概念,并通过示例展示了如何创建线程池。最后讨论了如何向线程传递参数,包括普通参数和引用类型参数的处理。

C++ 多线程

#1 环境

C++14
CMake 3.17
macOS 10.15.5
Clion

#2 开始

#2.1 不使用线程

#include <iostream>
#include <thread>

void func1(){
    std::cout << "func1" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠
}
void func2(){
    std::cout << "func2" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠
}

int main() {
    std::cout << "Hello, Thread!" << std::endl;
    func1();
    func2();
    return 0;
}

现象:

先打印输出func1两秒后再打印输出func2,再过两秒后退出程序

#2.2 使用多线程

#include <iostream>
#include <thread>

void func1(){
    std::cout << "func1" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠
}
void func2(){
    std::cout << "func2" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠
}

int main() {
    std::cout << "Hello, Thread!" << std::endl;
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
    return 0;
}

现象:

几乎同时打印输出func1 func2, 两秒后退出程序

#3 std::thread 接口

#3.1 std::thread t1(func1)

  • std::thread: 变量类型
  • t1: 多线程变量名
  • func1: 需要执行的方法

#3.2 t1.join();

连接

std::thread t1(func1);
t1.join();
  • join(): 阻塞等func1执行完毕后返回

#3.3 t1.detach();

分离

std::thread t1(func1);
t1.detach();
  • detach(): 不阻塞,直接返回

#3.4 get_id()

获取线程id

std::thread t1(func1);
std::thread::id t1_id = t1.get_id();
std::cout << "t1 id:" << t1_id << std::endl;

#3.5 swap()

交换两个线程对象所代表的底层句柄(underlying handles)。

std::thread t1(func1);
std::thread t2(func2);
std::cout << "t1 id:" << t1.get_id() << std::endl;
std::cout << "t2 id:" << t2.get_id()<< std::endl;
std::swap(t1, t2);
std::cout << "t1 id:" << t1.get_id() << std::endl;
std::cout << "t2 id:" << t2.get_id()<< std::endl;
t1.join();
t2.join();
# output
Hello, Thread!
t1 id:0x7000069c8000
t2 id:0x700006a4b000
t1 id:0x700006a4b000
t2 id:0x7000069c8000
func1
func2

#3.6 mutex()

mutex是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

#include <iostream>
#include <thread>
#include <mutex>

int count = 10;
std::mutex lock;

void func1(){
    while (count>0){
        lock.lock();
        count--;
        std::cout << count << std::endl;
        lock.unlock();
    }
}
void func2(){
    while (count>0){
        lock.lock();
        count--;
        std::cout << count << std::endl;
        lock.unlock();
    }
}

int main() {
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
    return 0;
}
# output
9
8
7
6
5
4
3
2
1
0
-1

但是使用mutex是不安全的,当一个线程在解锁之前异常退出了,那么其它被阻塞的线程就无法继续下去。

#3.7 lock_guard

使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程

...
while (count>0){
    std::lock_guard<std::mutex> lk(lock);
    count--;
    std::cout << count << std::endl;
}
...

#4 线程池

线程的创建和销毁会消耗系统资源,为了避免系统的消耗,加入线程池概念,为的就是创建的线程存到队列中,线程执行结束后,不销毁,等到下一个申请线程时,从队列中取出已有的线程

这里使用GitHub上已经写好的第三方线程池库

GItHub地址: https://github.com/progschj/ThreadPool

#include <iostream>
#include <thread>
#include <mutex>
#include "ThreadPool.h"

void func1(){
    std::cout << "func1: " << std::this_thread::get_id() << " :func1"  << std::endl; // 打印当前线程id
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠
}
void func2(){
    std::cout << "func2: " << std::this_thread::get_id() << " :func2" << std::endl; // 打印当前线程id 
    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // 休眠

}

int main() {
    ThreadPool pool(4); // 线程池最大线程数为4 
    pool.enqueue(func1);
    pool.enqueue(func1);
    pool.enqueue(func2);
    pool.enqueue(func2);
    pool.enqueue(func2); // 创建5个线程, 只要有两个线程的id相同,就能证明线程池可用
    return 0;
}
# output
func1: func1: func2: 0x700002a07000 :func20x700002901000 :func1
func2: 0x700002984000 :func1
0x700002a8a000 :func2
func2: 0x700002901000 :func2

有两个线程的id为0x700002901000,证明线程池可用

#5 传参数

#5.1 普通传参数

class MyThread{
public:
    static void func_1(int a){
        std::cout << "func_1: " << a << std::endl;
    }
};

int main() {
    std::make_shared<std::string>("hello");
    std::thread t1(&MyThread::func_1,1);
    t1.join();

    return 0;
}
# output
func_1: 1

目标线程函数,必须是static(类方法)

#5.2 参数是引用类型

class MyThread{
public:
    static void func_2(std::shared_ptr<std::string> &a){
        std::cout << "func_2: " << a << std::endl;
    }
};

int main() {
    std::shared_ptr<std::string> pt = std::make_shared<std::string>("hello");
    std::thread t2(&MyThread::func_2,pt);
    t2.join();

    return 0;
}

func_2中的形参是一个引用类型,如果直接传参数,编译会报错

typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/5/functional:1526:9: error: no type named ‘type’ in ‘class std:

在这里插入图片描述

class MyThread{
public:
    static void func_2(std::shared_ptr<std::string> &a){
        std::cout << "func_2: " << a << std::endl;
    }
};

int main() {
    std::shared_ptr<std::string> pt = std::make_shared<std::string>("hello");
    std::thread t2(&MyThread::func_2,std::ref(pt)); // 传引用 
    t2.join();

    return 0;
}
# output
func_2: 0x7fcd38405808

因为多线程是抢占式(多线程的全局变量需要加互斥锁),引用的生命周期和多线程的生命周期不一致,在线程结束时,引用变量依然会存在,但是,正常来说,程序员会认为,一个线程结束后,相关的变量也会被销毁,程序员这样的思维与引用违背,所以,多线程传参数时,默认是传值,而不是传引用,如果真的需要传引用,需要特殊的处理(为了提醒程序员,当前传的是引用)

<think>好的,用户之前已经询问过std::thread的基本用法,现在想了解如何用std::thread创建和管理多个线程。我需要结合之前的回答和提供的四个引用来组织内容。首先,用户的问题是如何开辟多个线程,所以需要展示多个线程的创建方法。引用3中的示例代码创建了两个线程t1和t2,这是一个很好的例子,应该包含进来。同时,引用2中的基本用法也提到了join方法,需要强调每个线程都需要正确管理。接下来,用户可能需要知道如何传递参数给多个线程,引用3中的例子传递了不同的参数给每个线程,这可以作为示例。此外,引用4中关于线程ID的获取,可以补充说明如何在线程函数中获取当前线程的ID,帮助用户区分不同线程。然后,需要考虑线程的管理,比如使用join还是detach。之前的回答提到过detach的风险,这里需要再次提醒用户,尤其是在多个线程的情况下,确保资源不被提前释放。另外,用户可能需要线程同步的提示,比如使用互斥锁来防止数据竞争。虽然引用中没有直接提到mutex,但这是管理多个线程时的常见需求,可以简要提及,并建议参考相关问题中的mutex用法。 还需要注意代码示例的完整性和正确性,比如正确的头文件包含和编译命令。引用2和3中的编译指令提到需要链接pthread库,这一点在多个线程的情况下同样适用,应该强调。最后,生成相关问题时要结合用户之前的提问,比如异常处理、线程同步、future和promise的区别,以及线程池的实现,这些都是与多线程管理紧密相关的话题,可以引导用户进一步学习。</think>### C++ `std::thread` 多线程创建与管理详解 使用 `std::thread` 创建和管理多个线程时,需重点关注**线程生命周期管理**、**参数传递**和**资源同步**。以下是具体实现方法及示例: --- #### **一、创建多个线程** 通过实例化多个 `std::thread` 对象实现并行任务: ```cpp #include <iostream> #include <thread> void task(int thread_id) { std::cout << "线程 " << thread_id << " 运行中" << std::endl; } int main() { // 创建3个线程 std::thread t1(task, 1); std::thread t2(task, 2); std::thread t3(task, 3); std::cout << "主线程继续执行..." << std::endl; // 等待所有子线程结束 t1.join(); t2.join(); t3.join(); std::cout << "所有子线程已完成" << std::endl; return 0; } ``` - **关键点**: 1. 每个线程对象必须调用 `join()` 或 `detach()`[^3] 2. 线程函数参数支持值传递和引用传递(需用 `std::ref`)[^3] --- #### **二、使用容器管理线程** 批量创建线程时,推荐使用容器(如 `vector`)统一管理: ```cpp #include <vector> int main() { std::vector<std::thread> threads; // 创建10个线程 for (int i = 0; i < 10; ++i) { threads.emplace_back([i] { std::cout << "线程 " << i << " 启动" << std::endl; }); } // 统一等待所有线程 for (auto& t : threads) { t.join(); } return 0; } ``` - **优势**:避免手动管理多个线程对象,简化代码逻辑[^2] --- #### **三、线程标识与调试** 通过 `std::thread::id` 区分不同线程: ```cpp void worker() { std::cout << "当前线程ID: " << std::this_thread::get_id() << std::endl; } int main() { std::thread t1(worker); std::thread t2(worker); std::cout << "主线程ID: " << std::this_thread::get_id() << std::endl; t1.join(); t2.join(); return 0; } ``` 输出示例: ``` 主线程ID: 140736245890944 当前线程ID: 123145447239680 当前线程ID: 123145452494848 ``` - **应用场景**:调试多线程竞争问题[^4] --- #### **四、注意事项** 1. **编译命令**:需链接 `pthread` 库 ```bash g++ -std=c++11 -pthread main.cpp -o main ``` 2. **资源竞争**:多个线程访问共享数据时需使用 `std::mutex` 同步 3. **异常安全**:在 `try-catch` 块中管理线程,避免未处理的异常导致资源泄漏 --- ### **总结与建议** - **优先选择 `join()`**:确保线程安全结束 - **避免全局变量**:通过参数传递数据 - **性能优化**:线程数量不宜超过CPU核心数,避免过度切换 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值