《C++并发编程》《线程管理》

文章目录

一、线程的基本管控

1.1发起线程

线程通过构建std::thread对象而启动,该对象指明线程要运行的任务。

举一个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
  
// 线程函数  
void threadFunction() {  
    for (int i = 0; i < 10; ++i) {  
        std::cout << "Thread function executing..." << std::endl;  
    }  
}  
int main() {  
    // 创建线程并启动  
    std::thread t(threadFunction);   
    // 等待线程完成  
    t.join();  
    return 0;  
}

1.2等待线程完成

若需要等待线程完成,那么可以在与之关联的std::thread实例上,通过调用成员函数join()实现。除了join(),还可以对线程进行判断joinable()。

举一个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
  
void threadFunction() {  
    std::cout << "Thread function executing..." << std::endl;  
}  
  
int main() {  
    // 创建线程并启动  
    std::thread t(threadFunction);  
  
    // 检查线程是否可joinable  
    if (t.joinable()) {  
        // 等待线程完成  
        t.join();  
    } else {  
        std::cout << "Thread is not joinable." << std::endl;  
    }  
  
    return 0;  
}

1.3出现异常情况下等待

当然程序在运行时,可能会出一些意外情况。为了防止因抛出异常而导致的应用程序终结,我们需要决定如何处理这种情况。

举一个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
  
std::mutex mtx;  
std::condition_variable cv;  
bool ready = false;  
  
void workerThread() {  
    try {  
        // 模拟线程执行过程中可能会出现的问题  
        std::this_thread::sleep_for(std::chrono::seconds(1));  
        mtx.lock();  
        ready = true;  
        cv.notify_one();  
        mtx.unlock();  
    } catch (const std::exception& e) {  
        std::cout << "Thread caught exception: " << e.what() << std::endl;  
    }  
}  
  
int main() {  
    std::thread t(workerThread);  
    t.detach(); // 分离线程,使其在后台运行  
  
    // 主线程等待子线程完成  
    std::unique_lock<std::mutex> lock(mtx);  
    cv.wait(lock, []{ return ready; });  
    std::cout << "Thread finished." << std::endl;  
  
    return 0;  
}

1.4后台运行线程

std::thread::detach 是 C++11 标准库中 头文件提供的一个函数,用于将线程对象与其底层线程分离。一旦线程被分离,当它结束时,其资源会自动被回收,而不需要通过 std::thread::join 进行显式等待。

当调用 detach 之后,该 std::thread 对象将不再代表一个活动线程,并且无法再被 join。如果尝试对已分离的线程调用 join,程序会抛出一个 std::system_error 异常。

举个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
#include <chrono>  
  
void threadFunction() {  
    for (int i = 0; i < 5; ++i) {  
        std::this_thread::sleep_for(std::chrono::seconds(1));  
        std::cout << "Thread function executing..." << std::endl;  
    }  
}  
  
int main() {  
    // 创建线程并启动  
    std::thread t(threadFunction);  
      
    // 分离线程,使其在后台运行  
    t.detach();  
      
    // 主线程继续执行其他任务  
    for (int i = 0; i < 5; ++i) {  
        std::this_thread::sleep_for(std::chrono::seconds(1));  
        std::cout << "Main thread executing..." << std::endl;  
    }  
      
    return 0;  
}

detach函数使用注意事项:

  • 线程资源管理:detach函数将线程与主线程分离,这意味着主线程不再负责等待该线程的完成。一旦线程被分离,它的资源将由运行时库负责清理。因此,如果主线程在后台线程完成之前结束,那么后台线程可能会变成"僵尸线程",这可能导致资源泄漏或其他问题。
  • 线程安全:使用detach时,需要注意线程安全。如果多个线程同时操作同一个数据结构,而其中一个线程调用了detach,那么这个数据结构可能会在未同步的情况下被修改,导致数据不一致或其他问题。
  • 异常处理:如果在线程函数中抛出了异常,而该异常未被捕获和处理,那么当线程结束时,该异常可能会被抛出并导致程序崩溃。因此,在使用detach时,需要确保线程函数中的异常被正确处理。
  • joinable检查:在调用detach之前,最好先检查线程是否可以被join。如果线程已经被join或detach过,再次调用detach会导致未定义的行为。

二、向线程函数传递参数

若要向新线程上的函数或可调用对象传递参数,方法相当简单,直接向std::thread的构造函数添加更多参数即可。

举个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
  
// 这是线程函数,接受一个整数参数  
void threadFunction(int param) {  
    std::cout << "Thread function received: " << param << std::endl;  
}  
  
int main() {  
    // 创建一个线程,并传递参数  
    std::thread t(threadFunction, 42);  
  
    // 等待线程完成  
    t.join();  
    return 0;  
}

向线程传递参数的注意点:

向线程函数传递参数时,需要注意以下几点:

  • 参数类型匹配:确保传递给线程函数的参数类型与函数期望的参数类型匹配。否则,可能会导致编译错误或运行时错误。
  • 参数的拷贝语义:在传递参数时,要注意参数的拷贝语义。如果传递的是大型对象或可变对象,可能会导致不必要的内存分配和拷贝。可以使用引用或指针来避免不必要的拷贝。
  • 线程安全:如果多个线程同时访问和修改共享数据,需要确保线程安全。可以使用互斥锁、条件变量等同步机制来保护共享数据。
  • 异常处理:如果线程函数可能抛出异常,需要确保异常能够被正确处理。可以使用try-catch语句块来捕获并处理异常。
  • 参数的传递方式:可以使用值传递、引用传递或指针传递等方式传递参数。需要根据具体情况选择合适的传递方式,以避免不必要的拷贝和内存分配。
  • 线程函数的返回值:如果线程函数有返回值,需要确保返回值的正确性和线程安全性。可以使用std::future或std::async等机制来获取线程函数的返回值。

总之,向线程函数传递参数时,需要注意参数的类型、拷贝语义、线程安全、异常处理、传递方式和返回值等问题,以确保程序的正确性和性能。

三、转移线程归属权

线程运行的过程中会有出现线程归属权需要进行转移的情况:

(1)编写函数,创建线程,线程置于后台运行;但函数本身不等待线程完成,将这个线程的归属权向上移交给函数的调用者;

(2)想创建一个线程,将其归属权传入某个函数,由他负责等待该线程结束;

要想实现线程归属权的转移,可以使用std::move函数来实现;

举个简单的栗子:

cpp 复制代码
#include <iostream>
#include <thread>

void threadFunction(){
	//线程中执行的函数
	std::cout << "Thread is runnning!" <<std::endl;
}
int main(){
	//创建一个线程
	std::thread myThread(threadFunction);
	//转移线程所有权
	std::thread anotherThread = std::move(myThread);
	//等待另一个线程执行完毕
	anotherThread.join();
	return 0;
	//此时,myThread不再拥有线程的所有权,不能再join,
	//myThread.join();这一行会导致编译错误;
}

四、运行时选择线程数量

通过hardware_concurrency() 来确定系统上可以并发执行的线程数。

std::thread::hardware_concurrency() 是 C++11 标准库中的一个函数,它返回一个无符号整数,表示在给定的系统上可以并发执行的线程数。这个数字通常表示系统的核心数或逻辑处理器数。

这个函数在编程中有多种用途:

(1)线程数量选择:当你需要并行执行多个任务时,可以使用 std::thread::hardware_concurrency() 来确定最适合的线程数量。这通常是一个合理的起点,但你仍然需要考虑其他因素,如任务的工作负载、内存使用情况等。

(2)资源利用:通过使用与系统核心数相匹配的线程数,可以更有效地利用系统资源。过多的线程可能会导致上下文切换的开销增加,而太少的线程则可能无法充分利用系统资源。

(3)性能测试:在测试多线程应用程序的性能时,可以使用 std::thread::hardware_concurrency() 来确定基准线程数。然后,可以逐渐增加或减少线程数量,以观察性能的变化。

举个简单的栗子:

cpp 复制代码
#include <iostream>  
#include <thread>  
#include <vector>  
  
// 简单的任务函数  
void simpleTask(int threadId) {  
    std::cout << "Thread " << threadId << " is running." << std::endl;  
}  
  
int main() {  
    // 获取系统的最大并发线程数  
    unsigned int maxConcurrentThreads = std::thread::hardware_concurrency();  
    std::cout << "Maximum concurrent threads: " << maxConcurrentThreads << std::endl;  
  
    // 创建线程的向量  
    std::vector<std::thread> threads(maxConcurrentThreads);  
  
    // 启动线程并为其分配简单的任务  
    for (int i = 0; i < maxConcurrentThreads; ++i) {  
        threads[i] = std::thread(simpleTask, i);  
    }  
  
    // 等待所有线程完成  
    for (auto& thread : threads) {  
        thread.join();  
    }  
  
    std::cout << "All threads completed." << std::endl;  
    return 0;  
}

五、识别线程

在C++多线程编程中,有2种方式能获取线程。

(1)在与线程关联的std::thread对象上调用成员函数get_id(),就可以获得该线程ID;

(2)当前线程的ID可以通过调用std::this_thread::get_id()方法便可以获得。

举个简单的栗子:

cpp 复制代码
#include <iostream>
#include <thread>
//延时函数
void delay(int milliseconds) {
    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
//方法一:调用成员函数std::this_thread::get_id()返回当前线程的ID
void print_id(){
    std::cout << std::this_thread::get_id() << std::endl;
    delay(100);
}
void hello_world(){
    std::cout << "Hello world!" << std::endl;
}
int main(){
    std::thread t1(print_id);
    std::thread t2(hello_world);
    //方法二:在与线程关联的std::thread对象上调用成员函数get_id()
    std::cout << "t2_id:" << t2.get_id() << std::endl;
    t1.join();
    t2.join();
}

六、总结

书山有路勤为径,学海无涯苦作舟。

七、参考书籍

《C++并发编程实战(第二版)》

相关推荐
hope_wisdom5 分钟前
C++网络编程之SSL/TLS加密通信
网络·c++·ssl·tls·加密通信
erxij10 分钟前
【游戏引擎之路】登神长阶(十四)——OpenGL教程:士别三日,当刮目相看
c++·经验分享·游戏·3d·游戏引擎
Lizhihao_23 分钟前
JAVA-队列
java·开发语言
林开落L40 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色41 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧1 小时前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices1 小时前
C++如何调用Python脚本
开发语言·c++·python
单音GG1 小时前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
wyh要好好学习1 小时前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf