文章目录
- 一、线程的基本管控
- 1.1发起线程
- 1.2等待线程完成
- 1.3出现异常情况下等待
- 1.4后台运行线程
- 二、向线程函数传递参数
- 三、转移线程归属权
- 四、运行时选择线程数量
- 五、识别线程
- 六、总结
- 七、参考书籍
一、线程的基本管控
1.1发起线程
线程通过构建std::thread对象而启动,该对象指明线程要运行的任务。
举一个简单的栗子:
#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()。
举一个简单的栗子:
#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出现异常情况下等待
当然程序在运行时,可能会出一些意外情况。为了防止因抛出异常而导致的应用程序终结,我们需要决定如何处理这种情况。
举一个简单的栗子:
#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 异常。
举个简单的栗子:
#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的构造函数添加更多参数即可。
举个简单的栗子:
#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函数来实现;
举个简单的栗子:
#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() 来确定基准线程数。然后,可以逐渐增加或减少线程数量,以观察性能的变化。
举个简单的栗子:
#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()方法便可以获得。
举个简单的栗子:
#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++并发编程实战(第二版)》