前言
这个系列是我发现自己编程能力比较不足,所以想写blog来促进自己复习
希望能帮助到大家(先少误导)有问题可以直接在评论区指出
本文环境是macOS和自带的clang, C++11,cmd内编译使用:
clang++ -std=c++11 -o my_program your_code.cpp
线程基础概念
首先要明白四个基本概念:
1. 进程:进程是操作系统分配资源的基本单位,每个进程都有独立的内存空间。
2. 线程:线程是进程内的一个执行单元,多个线程共享进程的资源。
3. 并发:并发是指多个任务在同一时间段内交替执行,但不一定是同时进行。
4. 并行:并行是指多个任务在同一时间点同时执行,通常依赖于多核处理器。
然后衍生一下:
进程&线程查询: windows的话Ctrl + Shift + Esc/cmd里用tasklist命令,mac的话只要在终端里输入top即可,也可以用ps命令(和linux一样)。可以发现一个进程往往是对应多个线程的,然后每个运行的程序往往对应一个进程,当然这里之后的篇章会再说,毕竟光看还是比较杂的。
进程&线程关系理解:
一个进程至少有一个线程,即使它只有一个主线程。
一个进程中的多个线程共享进程的资源,例如内存空间,但每个线程有自己的执行栈和程序计数器。
线程之间可以通过共享进程的资源进行通信,而进程之间的通信需要额外的机制
处理器:也就是CPU,可以打开网购平台看一下,一般指出的最明显的就是处理器了,游戏本还会指出一个显卡(或许以后我复习到了会再仔细介绍一下处理器相关)。处理器分单核核多核。处理器的“核”指的是处理器内部的独立计算单元。每个核都可以独立地执行指令。从处理器的角度可以进一步理解并发与并行:
并发(Concurrency):指多个任务在同一时间段内进行,可能是交替执行的,不一定是同时进行的,但因为CPU时钟频率很高,在高速交替进行下模拟出来了同时进行的效果,因此,单核处理器也可以并发。关键点:可以“交替进行”
并行(Parallelism):指多个任务在同一时刻由多个核心同时执行,在多核处理器上,任务可以真正地并行处理。关键点:“同时”
Cpp:Thread库
C++11 开始,<thread> 库就成为了一个很重要的工具了。
最基础地,创建一个进程:
#include <iostream>
#include <thread>
void func(int x) {
std::cout << x << "GET\n";
}
int main() {
std::cout << "hello\n";
std::thread t(func, 8);
t.join();
//创建了一个线程 t,并将 func 函数和参数 8 传递给它,即新线程会执行 func(8)。t.join() 阻塞当前线程,直到新线程完成执行。
std::cout << "end thread\n";
return 0;
}
以上thread 类和join 函数两行
展现了基础的“创建并启动进程”和“等待线程完成”这样一个过程
join在后文会继续讲
并且可以看出,thread是可以传递参数的,这里参数包括函数本身(func)+函数参数(8)
在这里特别提一点:这里函数参数传递有时可以考虑:std::ref (引用包装器,避免拷贝)
例子:
#include <iostream>
#include <thread>
void threadFunction(int n, const std::string& message) {
for (int i = 0; i < n; ++i) {
std::cout << message << std::endl;
}
}
int main() {
std::string msg = "仓鼠复习";
std::thread t(threadFunction, 5, std::ref(msg));
t.join();
return 0;
}
lambda表达式
lambda表达式也能结合thread用
lambda表达式因为我不算掌握,之后我会单独开一篇进行复习(大概可以认为是一个更浓缩的函数表达方法),在这里就举一个基本例子:
#include <iostream>
#include <thread>
int main() {
std::thread t([]() {
std::cout << "hamster is coming" << std::endl;
});
t.join();
return 0;
}
get_ID
可以用get_id方法去获取
#include <iostream>
#include <thread>
void getThreadID() {
std::cout << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t(getThreadID);
t.join();
return 0;
}
输出会是一个十六位进制且唯一的数,如:(但我并不确定换了编译器是否会改变,大家可以自己试试)
0x16db8b000
注意,这个ID不表示和内存地址有关的东西。
join 和 detach和joinable
join():等待线程完成-----当前线程将等待,直到目标线程执行完毕。
🌟使用场景:当主线程或其他线程依赖某个线程的执行结果时,我们需要使用 join() 来确保该线程执行完毕。
detach():分离线程,使其独立运行-----将线程分离,使其在后台独立运行,当前线程继续执行
🌟使用场景:当我们不关心线程的执行结果,且希望线程在后台继续运行时使用 detach()。
joinable() 是 std::thread 类中的一个成员函数,用于判断一个线程对象是否可以被 join(),即该线程是否仍然处于活动状态并且未被 detach() 或已经完成执行。
joinable() :顾命思意:join+able?
🌟 判断线程是否可 join:线程在被创建后,只有在没有被 join() 或 detach() 之后,它才是 “joinable” 的。
🌟避免调用无效的 join():如果线程已经被 join() 或 detach(),则调用 join() 或 detach() 会导致崩溃(报错见下图)。因此,在调用 join() 之前,应该首先检查线程是否是 “joinable”。
libc++abi: terminating due to uncaught exception of type std::__1::system_error: thread::join failed: Invalid argument
zsh: abort ./my_program
再这里更详细解释线程从创建起的逻辑:(还是AI会总结)
1. 当一个 std::thread 对象被创建时,线程就会启动并开始执行。此时线程是活动的,并且处于 “joinable” 状态,即线程可以被 join() 或 detach()
2. 当调用 join() 方法时,主线程会阻塞,直到该线程执行完毕。一旦调用 join(),该线程对象就进入了不可 joinable状态。也就是说,线程已经被“加入”了主线程,不能再对同一线程再次调用 join() 或 detach()。
3. 调用 detach() 方法会将线程与主线程分离,使其在后台独立运行。分离的线程会继续执行,但主线程不再等待它完成。一旦调用 detach(),线程也不再是 “joinable” 的,因为它已经从主线程中独立出来,无法再被 join() 或 detach() 操作。
逻辑上举一个例子理解
比如说,你现在有10个线程,分别在对一个向量的10个部分进行操作
现在你希望把它们的结果加起来
你必须等子线程确认算出答案了,主线程才可以去取用那个答案
因此,这些会要被关心算没算完的东西就是join 你需要知道这个小任务结束了没
否则就是detach
综合代码
注意在这里,我们用chrono里的sleep_for函数模拟复杂问题中一些线程需要一定的时间(凸显多线程必要性)。
#include <iostream>
#include <thread>
#include <chrono>
void worker(int id) {
std::cout << "Thread " << id << " is working...\n";
if(id==2){
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作延迟,2延迟更长
}
std::cout << "Thread " << id << " finished work.\n";
}
int main() {
std::thread t1(worker, 1); // 创建线程 t1
std::thread t2(worker, 2); // 创建线程 t2
// 使用 joinable 检查线程是否可以连接
if (t1.joinable()) {
std::cout << "Thread 1 is joinable, joining now...\n";
t1.join(); // 等待 t1 完成
}
// 使用 detach 来分离线程
if (t2.joinable()) {
std::cout << "Thread 2 is joinable, detaching now...\n";
t2.detach(); // 让 t2 独立执行,主线程不等待 t2
}
// 由于 t2 已被分离,主线程无需等待它)
std::cout << "Main thread finishes.\n";
return 0;
}
输出(注意被detach的2线程是没有finish,主线程直接结束了)
Thread Thread 1 is joinable, joining now...
Thread 21 is working...
Thread 1 finished work.
is working...
Thread 2 is joinable, detaching now...
Main thread finishes.
后文:
之后会随着自己复习进度先出续篇2,补充讲一下thread相关的东西以及线程同步,欢迎关注~