【Linux/C++多线程篇(二) 】给线程装上“红绿灯”:通俗易懂的同步互斥机制讲解 & C++ 11下的多线程

【Linux/C++多线程篇(二) 】给线程装上“红绿灯”:通俗易懂的同步互斥机制讲解 & C++ 11下的多线程

⭐️在这个怀疑的年代,我们依然需要信仰

个人主页:YYYing.

⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】

系列上期内容:【Linux/C++多线程篇(一) 】多线程编程入门

系列下期内容:【Linux/C++网络篇(一) 】网络编程入门


目录

前言:当多线程遇上“交通混乱”

线程的同步互斥机制

一、为什么需要同步互斥?

二、线程互斥之互斥锁

2.1、互斥锁的相关API函数接口

 📖 创建一个互斥锁

 📖 初始化互斥锁

📖 获取锁资源

📖 释放锁资源

📖 销毁互斥锁

2.2、互斥锁的小练习

三、线程同步之无名信号量

3.1、无名信号量的相关API函数接口

 📖 创建无名信号量

 📖 初始化无名信号量

 📖 申请无名信号量的资源(P操作)

 📖 释放无名信号量的资源(V操作)

 📖 销毁无名信号量

3.2、互斥锁的小练习

四、线程同步之条件变量

4.1、条件变量的API函数接口

 📖 创建一个条件变量

 📖 初始化条件变量

 📖 消费者线程进入等待队列

 📖 生产者线程唤醒休眠队列中的任务

 📖 销毁条件变量

3.2、条件变量的小练习

C++11中的多线程

一、线程相关常用操作

1.1、线程的创建

1.2、线程体函数种类

1.3、线程号获取

1.4、线程号回收

二、互斥锁的使用

2.1、常用函数

2.2、lock_guard的使用

2.3、代码演示

三、条件变量

2.1、常用函数

2.2、代码演示

结语

---⭐️封面自取⭐️---



前言:当多线程遇上“交通混乱”

        想象一下,你是一个繁忙路口的交警,需要同时指挥四面八方的车辆。如果没有红绿灯和交警,所有车辆都凭感觉开,结果必然是撞车、拥堵、混乱。在计算机世界里,多个线程同时访问共享数据时,也会出现类似的“交通事故”——数据错乱、程序崩溃、结果不可预测

        为了让线程们有序地工作,我们需要给它们装上“红绿灯”,也就是同步互斥机制。本文将从生活比喻出发,带你轻松理解这些看似复杂的并发工具。

线程的同步互斥机制

一、为什么需要同步互斥?

假设有一个火车票售票系统,剩余票数为 1。两个线程同时执行以下操作

if (tickets > 0) { tickets--; cout << "购票成功"; }

        如果两个线程同时检查 tickets > 0,发现都是 1,于是都执行 tickets--,结果票数变成 -1,但两人都以为自己买到了票。这就是典型的竞态条件——程序的结果依赖于线程执行的偶然顺序。

        这种问题源于线程的交错执行tickets-- 不是原子操作(也就是指不会被线程调度机制打断的操作。),它实际上分为三步:读取、减一、写回。如果两个线程的步骤交错,就会出错。

        要解决这个问题,我们需要确保同一时刻只有一个线程能操作票数,这就是互斥。同时,可能还需要让一个线程等待另一个线程完成某件事(比如等票补足再卖),这就是同步

        总的来说——由于同一个进程的多个线程会共享进程的资源,这些被共享的资源称为临界资源,多个线程对公共资源的抢占问题,访问临界资源的代码段称为临界区,多个线程抢占进程资源的现象称为竞态,为了解决竞态,我们引入了同步互斥机制

下面,我们就来看看系统为我们提供的几种“红绿灯”。


二、线程互斥之互斥锁

        互斥锁的本质是一个特殊的临界资源,当该临界资源被某个线程所拥有后,其他线程就不能拥有该资源,直到,拥有该资源的线程释放掉互斥锁后,其他线程才能进行抢占(同一时刻,一个互斥锁只 能被一个线程所拥有),相当于一把获取资源的钥匙。

2.1、互斥锁的相关API函数接口

 📖 创建一个互斥锁

        只需定义一个pthread_mutex_t 类型的变量即创建了一个互斥锁

pthread_mutex_t mutex;

 📖 初始化互斥锁
函数原型

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;       //静态初始化

头文件iostream
功能初始化互斥锁变量
参数说明

参数1:互斥锁变量的地址,属于地址传递

参数2:互斥锁属性,一般填NULL,让系统自动设置互斥锁属性

返回值成功返回0,失败返回错误码

📖 获取锁资源
函数原型int pthread_mutex_lock(pthread_mutex_t *mutex);
头文件iostream
功能获取锁资源,如果要获取的互斥锁已经被其他线程锁定,那么该函数会阻塞,直到能够获取锁资源
参数说明互斥锁地址,属于地址传递
返回值成功返回0,失败返回错误码

📖 释放锁资源
函数原型int pthread_mutex_unlock(pthread_mutex_t *mutex);
头文件iostream
功能释放对互斥锁资源的拥有权
参数说明互斥锁变量的地址
返回值成功返回0,失败返回错误码

📖 销毁互斥锁
函数原型int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件iostream
功能销毁互斥锁
参数说明互斥锁变量的地址
返回值成功返回0,失败返回错误码

2.2、互斥锁的小练习

        可以看到我们线程1和线程2都是先抢占我们锁资源然后进行释放,这其中的机制依旧是时间片轮询上下文切换。

#include<iostream> #include<cstdio> #include<cstring> #include <unistd.h> using namespace std; //11、创建一个互斥锁 pthread_mutex_t mutex; //定义一个全局资源 int num = 520; //定义分支线程1 void *task1(void *arg){ while(1){ sleep(1); //临界资源 //33、获取锁资源 pthread_mutex_lock(&mutex); num -= 10; //线程1将临界资源减少10 printf("张三取了10,剩余%d\n", num); //44、释放锁资源 pthread_mutex_unlock(&mutex); } } //定义分支线程2 void *task2(void *arg){ while(1){ sleep(1); //33、获取锁资源 pthread_mutex_lock(&mutex); num -= 20; //线程1将临界资源减少10 printf("李四取了20,剩余%d\n", num); //44、释放锁资源 pthread_mutex_unlock(&mutex); } } /*****************************主线程****************************/ int main() { //22、初始化互斥锁,参数NULL表示让系统自动分配互斥锁属性 pthread_mutex_init(&mutex, NULL); //1、创建两个分支线程 pthread_t tid1,tid2; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); //55、释放锁资源 pthread_mutex_destroy(&mutex); std::cout << "Hello, World!" << std::endl; return 0; }

三、线程同步之无名信号量

        线程同步:就是多个线程之间有先后顺序得执行,这样在访问临界资源时,就不会产生抢占现象了

        同步机制常用于生产者消费者模型:消费者任务要想执行,必须先执行生产者线程,多个任务有顺序执行

        无名信号量:本质上也是一个特殊的临界资源,内部维护了一个value值,当某个进行想要执行之前,先申请该无名信号量的value资源,如果value值大于0,则申请资源函数接触阻塞,继续执行后续操作。如果value值为0,则当前申请资源函数会处于阻塞状态,直到其他线程将该value值增加到大于0

3.1、无名信号量的相关API函数接口

 📖 创建无名信号量

        只需定义一个sem_t 类型的变量即可

sem_t sem;

 📖 初始化无名信号量
函数原型

int sem_init(sem_t *sem, int pshared, unsigned int value);

头文件semaphore.h
功能初始化无名信号量,最主要是初始化value值
参数说明

参数1:无名信号量的地址

参数2:判断进程还是线程的同步

               0:表示线程间同步

               非0:表示进程间同步,需要创建在共享内存段中

参数3:无名信号量的初始值

返回值成功返回0,失败返回-1并置位错误码

 📖 申请无名信号量的资源(P操作)
函数原型int sem_wait(sem_t *sem);
头文件semaphore.h
功能阻塞申请无名信号量中的资源,成功申请后,会将无名信号量的value进行减1操作,如果当前无名信号量的value为0,则阻塞
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

 📖 释放无名信号量的资源(V操作)
函数原型int sem_post(sem_t *sem);
头文件semaphore.h
功能将无名信号量的value值增加1操作
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

 📖 销毁无名信号量
函数原型int sem_destroy(sem_t *sem);
头文件semaphore.h
功能销毁无名信号量
参数说明无名信号量的地址
返回值成功返回0,失败返回-1并置位错误码

3.2、互斥锁的小练习

        我们现在再来看看同步在生产者消费者模型中的应用,当 进程2 想要执行之前,先申请无名信号量的value资源,如果value值大于0,则申请资源函数解除阻塞,并继续执行后续操作。如果value值为0,则当前申请资源函数会处于阻塞状态,直到 线程1 将该value值增加到大于0

#include<iostream> #include<cstdio> #include<cstring> #include<unistd.h> #include<semaphore.h> #include<pthread.h> sem_t sem; //创建生产者线程 void *task1(void *arg){ int num = 5; while(num--){ sleep(1); printf("我生产了一辆特斯拉\n"); //44、释放无名信号量资源 sem_post(&sem); } //退出线程 pthread_exit(NULL); } //创建消费者线程 void *task2(void *arg){ int num = 5; while(num--){ //33、申请无名信号量的资源 sem_wait(&sem); printf("我消费了一辆特斯拉,很开心\n"); } //退出线程 pthread_exit(NULL); } /*******************************主程序*************************/ int main() { //22、初始化无名信号量,第一个0表示用于线程间通信,第二个0表示初始值为0 sem_init(&sem, 0, 0); //1、创建两个分支线程 pthread_t tid1,tid2; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x\n", tid1, tid2); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); //55、销毁无名信号量 sem_destroy(&sem); return 0; }

四、线程同步之条件变量

        我们不难发现,如果我们只用互斥锁,那么我们的消费者和生产者只能是一对一的关系,但我们要想让一个生产者对应多个消费者用互斥锁就不行了,所以我们就要用到我们的条件变量了。

        条件变量本质上也是一个临界资源,他维护了一个队列,当消费者线程想要执行时,先进入队列中等待生产者的唤醒。执行完生产者,再由生产者唤醒在队列中的消费者,这样就完成了生产者和消费者之间的同步关系。

        但是,多个消费者在进入休眠队列的过程是互斥的,所以,在消费者准备进入休眠队列时,我们需要使用互斥锁来进行互斥操作。

4.1、条件变量的API函数接口

 📖 创建一个条件变量

        只需定义一个pthread_cond_t类型的全局变量即可

pthread_cond_t cond;

 📖 初始化条件变量
函数原型

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;     //静态初始化

头文件iostream
功能初始化条件变量
参数说明

参数1:条件变量的起始地址

参数2:条件变量的属性,一般填NULL

返回值

成功返回0,失败返回一个错误码


 📖 消费者线程进入等待队列
函数原型

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

头文件iostream
功能将线程放入休眠等待队列,等待其他线程的唤醒
参数说明

参数1:条件变量的地址

参数2:互斥锁,由于多个消费者线程进入等待队列时会产生竞态,为了解决竞态,需要使用一个互斥锁

返回值

成功返回0,失败返回错误码


 📖 生产者线程唤醒休眠队列中的任务
函数原型int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);
头文件iostreamiostream
功能唤醒条件变量维护的队列中的所有消费者线程唤醒条件变量维护的队列中的第一个进入队列的消费者线程
参数说明条件变量的地址条件变量的地址
返回值

成功返回0,失败返回错误码

成功返回0,失败返回错误码

 📖 销毁条件变量
函数原型int pthread_cond_destroy(pthread_cond_t *cond);
头文件iostream
功能销毁一个条件变量
参数说明条件变量的地址
返回值

成功返回0,失败返回错误码


3.2、条件变量的小练习

        我们再以生产者消费者模型来看看我们的用法,我们既可以一个一个唤醒,也可以直接一次性将消费者全部唤醒。

#include<iostream> #include<unistd.h> #include<pthread.h> //11、定义一个条件变量 pthread_cond_t cond; //111、定义一个互斥锁 pthread_mutex_t mutex; //创建生产者线程 void *task1(void *arg) { /* int num = 3; while(num--) { sleep(1); printf("%#x:生产了一辆特斯拉\n", pthread_self()); //44、唤醒一个消费者进行消费 pthread_cond_signal(&cond); } */ sleep(3); printf("我生产了3辆特斯拉\n"); //44、唤醒所有消费者线程 pthread_cond_broadcast(&cond); //退出线程 pthread_exit(NULL); } //创建消费者线程 void *task2(void *arg) { //333、获取锁资源 pthread_mutex_lock(&mutex); //33、进入休眠队列,等待生产者的唤醒 pthread_cond_wait(&cond, &mutex); printf("%#x:消费了一辆特斯拉,很开心\n", pthread_self()); //444、释放锁资源 pthread_mutex_unlock(&mutex); //退出线程 pthread_exit(NULL); } int main() { //22、初始化条件变量 pthread_cond_init(&cond, NULL); //222、初始化互斥锁 pthread_mutex_init(&mutex, NULL); //1、创建两个分支线程 pthread_t tid1,tid2,tid3,tid4; if(pthread_create(&tid1, NULL, task1, NULL) != 0){ printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0){ printf("tid2 create error\n"); return -1; } if(pthread_create(&tid3, NULL, task2, NULL) != 0){ printf("tid3 create error\n"); return -1; } if(pthread_create(&tid4, NULL, task2, NULL) != 0){ printf("tid4 create error\n"); return -1; } printf("主线程:tid1 = %#x, tid2 = %#x, tid3 = %#x, tid4 = %#x\n", tid1, tid2, tid3, tid4); //2、阻塞等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_join(tid4, NULL); //55、销毁条件变量 pthread_cond_destroy(&cond); ///555、销毁互斥锁 pthread_mutex_destroy(&mutex); return 0; }

C++11中的多线程

        C++11之后就支持线程支持库了,也支持线程创建、互斥锁、条件变量,线程支持库需要引入头文件 #include<thread>

一、线程相关常用操作

1.1、线程的创建

        C++线程支持库,本质是是面向对象的操作,可以使用构造函数完成。


1.2、线程体函数种类

  • 可以是任意类型的函数,不必要是 void * 类型参数也是void *类型
  • 可以是全局函数,也可以是类中成员函数当做线程体
  • 可以是仿函数当作线程体函数
  • 也可以是Lambda表达式当作线程体函数

1.3、线程号获取

this_thread::get_id()

        下述代码为我们展示了4种不同的线程体函数。

#include<iostream> #include <thread> using namespace std; /*****************第一个测试线程体*******************/ void ThreadFun_1(){ cout<<"ThreadFun_1 tid = "<< this_thread::get_id()<<endl; cout << "ThreadFun_1 test"<<endl; } /*****************第二个线程体测试**********************/ void ThreadFun_2(int num, string str){ //有参无返回值函数 cout<<"ThreadFun_2 tid = "<< this_thread::get_id()<<endl; cout<<"num = " << num << " str = " << str <<endl; } /*******************第三个线程体测试*********************/ class ThreadClass{ public: string name; int age; void ThreadClassFun(){ //类中成员函数作为线程体函数 cout<<"ThreadFun_3 tid = "<< this_thread::get_id()<<endl; cout << "name = "<<this->name<<" age = "<<age<<endl; } }; /**********************主程序**********************/ int main(int argc, const char *argv[]){ //主程序就是主线程 //创建第一个分支线程,使用无参函数完成线程体为无参无返回值函数 thread th1(ThreadFun_1); //创建第二个分支线程,并向线程体中传递数据 string name = "zpp"; thread th2(ThreadFun_2, 520, name); //此时就创建了一个分支线程,线程体函数 //可以直接向线程体传递参数,有多少可以传多少,无需使用结构体完成 //创建第三个分支线程,向线程体中传递一个类的成员函数 ThreadClass test; test.name = "zhangsan"; test.age = 18; thread th3(&ThreadClass::ThreadClassFun, &test); /************lambda表达式当作线程体:开发过程中用的比较多的************/ //创建第四个分支线程,将lambda表达式当作线程体函数 thread th4([](int key){ cout<<"ThreadFun_4 tid = "<< this_thread::get_id()<<endl; cout<<"key = "<<key<<endl; }, 999); //阻塞回收分支线程 th1.join(); th2.join(); th3.join(); th4.join(); return 0; }

1.4、线程号回收

  • 阻塞方式回收线程
th1.join();
  • 非阻塞方式回收线程
th1.detach();

        此处的阻塞非阻塞回收与我们上一篇讲的并无二异,说到底,这些类的实现本质也是在用我们上节课c语言的那些函数所做出来的。

#include<iostream> #include<thread> using namespace std; /*****************第一个测试线程体*******************/ void ThreadFun_1(){ // 无参无返回值 for (int i = 0; i < 10; i++){ cout << "ThreadFun_1 tid = " << this_thread::get_id() << endl; cout << "ThreadFun_1 test" << endl; //延时函数 this_thread::sleep_for(1s); //等待1秒时间 } } /**********************主程序**********************/ int main(int argc, const char *argv[]){ // 阻塞回收分支线程 //th1.join(); // 主程序就是主线程 // 创建第一个分支线程,使用无参函数完成线程体 thread th1(ThreadFun_1); // 此时就创建了一个分支线程,线程体函数为无参无返回值函数 th1.detach(); //将线程设置成分离态:主线程可以继续做自己其他事情 this_thread::sleep_for(20s); return 0; }

二、互斥锁的使用

        互斥锁本质上是完成将多个线程使用临界资源时,防止竞态

        我们需要在c++编程中需要引入头文件 #include<mutex>

2.1、常用函数

1、构造函数:创建一个互斥锁对象 2、 lock():上锁 3、 unlock():释放锁资源

2.2、lock_guard的使用

        但在这其中,我们的mutex互斥锁经常会与lock_guard进行一起使用,用于在其构造时自动获取锁,在析构时自动释放锁。使用 std::lock_guard 的好处是,当 std::lock_guard 对象离开其作用域时,会自动调用析构函数,该析构函数会释放锁。这确保了在任何情况下(包括由于异常等原因导致的提前退出),锁都会被正确释放,从而避免了忘记手动释放锁而导致的死锁问题

std::mutex myMutex; std::lock_guard<std::mutex> lock(myMutex);

2.3、代码演示

#include<iostream> #include<thread> #include<mutex> using namespace std; mutex mux; //实例化一个互斥锁 /*****************第一个测试线程体*******************/ void ThreadFun_1(){ // 无参无返回值 mux.lock(); //获取锁资源 cout<<"======================================"<<endl; cout<<"tid = "<<this_thread::get_id()<<endl; this_thread::sleep_for(1s); cout<<"**************************************"<<endl; mux.unlock(); //释放锁资源 } /**********************主程序**********************/ int main(int argc, const char *argv[]){ for(int i=0; i<10; i++){ thread th(ThreadFun_1); th.detach(); } //线程分离 this_thread::sleep_for(20s); std::cout << "Hello, World!" << std::endl; this_thread::sleep_for(20s); return 0; }

三、条件变量

        实现一个生产者对应多个消费者问题,需要引入头文件:#include<condition_variable>

2.1、常用函数

1、 构造函数:创建并初始化一个条件变量 2、 wait():将消费者线程放入等待队列中 3、 唤醒线程: cv.notify_one(); 唤醒一个线程 cv.notify_all(); 唤醒所有线程

2.2、代码演示

        不难看出与我们刚才所讲的同步机制中的条件变量逻辑是非常相似的。

#include<iostream> #include<thread> #include<mutex> #include<condition_variable> using namespace std; //定义一个条件变量 condition_variable cv; mutex mux; //条件变量头文件 //线程支持库头文件 //互斥锁 //用于防止竞态的互斥锁 //定义生产者线程 void ThreadWrite(){ for(int i=0; i<5; i++){ this_thread::sleep_for(2s); cout<<"我生产了一辆特斯拉"<<endl; cv.notify_one(); //通知一个线程可以消费了 //通知所有线程 //cv.notify_all(); } } //定义消费者线程 void ThreadRead(){ //提前先进入消费者队列 unique_lock<mutex> lock(mux); cv.wait(lock); cout<<"我消费了一辆特斯拉"<<endl; lock.unlock(); // 解锁 } int main(int argc, const char *argv[]) { //创建生产者线程 thread th1(ThreadWrite); //每隔两秒时间生产一辆特斯拉 //创建多个消费之 for(int i=0; i<5; i++){ thread th2(ThreadRead); th2.detach(); } th1.join(); //阻塞回收线程 return 0; }

结语

        当你掌握了“红绿灯”和“独木桥”的原理,你就已经跨过了多线程编程最危险的那道门槛。接下来,就是去享受多核 CPU 带来的速度激情吧!

我是YYYing,后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步,我们下次再见!


---⭐️封面自取⭐️---

Read more

【C++:C++11】C++11新特性深度解析:从可变参数模板到Lambda表达式

【C++:C++11】C++11新特性深度解析:从可变参数模板到Lambda表达式

🎬 个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》 《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬 艾莉丝的简介: 🎬 艾莉丝的C++专栏简介: 文章目录 * C++学习阶段的三个参考文档 * 4 ~> 可变参数模版 * 4.5 emplace系列接口 * 4.5.1 不同容器emplace系列接口展示 * 4.5.2 浅谈emplace系列接口概念 * 4.5.3 emplace系列接口在list.h文件中的使用 * 4.5.4 emplace系列接口在Test.cpp文件中的使用 * 4.

By Ne0inhk
C++性能优化:提升代码执行效率的艺术

C++性能优化:提升代码执行效率的艺术

C++性能优化:提升代码执行效率的艺术 一、学习目标与重点 本章将深入探讨C++性能优化的核心知识,帮助你掌握提升代码执行效率的艺术。通过学习,你将能够: 1. 理解性能优化的基本概念,掌握性能分析的方法 2. 学会优化内存管理,减少内存泄漏和内存碎片 3. 理解CPU优化技巧,提高代码的执行速度 4. 学会优化I/O操作,提升文件和网络读写的效率 5. 培养性能优化思维,设计高效的代码 二、性能优化的基本概念 2.1 性能优化的原则 性能优化应该遵循以下原则: * 先测量后优化:在优化之前,必须先测量代码的性能,找出瓶颈所在 * 优化瓶颈:只优化对性能影响最大的部分 * 保持代码的可维护性:优化后的代码应该易于理解和维护 * 测试优化结果:优化后必须测试代码的正确性和性能提升效果 2.2 性能分析工具 常用的性能分析工具包括: * GProf:GNU的性能分析工具 * Valgrind:内存调试和性能分析工具

By Ne0inhk
C++ 二叉搜索树(BST)完全指南:从概念原理、核心操作到底层实现

C++ 二叉搜索树(BST)完全指南:从概念原理、核心操作到底层实现

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 二叉搜索树的核心概念:什么是 BST? * 二. 二叉搜索树的性能分析:理想与最差情况 * 三. 二叉搜索树的实战实现:基于 BinarySearchTree.h * 3.1 节点结构定义:BSTreeNode * 3.2 BST 类核心操作:Insert、Find、Erase * 3.2.1 插入操作(Insert) * 3.2.2 查找操作(Find) * 3.2.3 删除操作(

By Ne0inhk
【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

⭐️在这个怀疑的年代,我们依然需要信仰。 个人主页:YYYing. ⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】 系列上期内容:【Linux/C++多进程篇(一) 】C/C++ 程序中神奇的“分身术” 系列下期内容:【Linux/C++多线程篇(一) 】多线程编程入门 目录 前言: 进程间通信(IPC) 一、进程间通信的基础概念 二、内核提供的通信方式 2.1、无名管道  📖 无名管道的API  📖 代码案例 2.2、有名管道  📖 有名管道的API  📖 代码案例 2.3、管道特点 2.4、信号  📖 信号相关概念

By Ne0inhk