【Linux】线程概念 | 线程控制
🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
目录
- 👉知识补充👈
- 👉Linux线程概念👈
- 什么是线程
- 线程 VS 进程
- 线程的优点
- 线程的缺点
- 线程异常
- 线程用途
- 👉线程控制👈
- 线程创建
- 线程终止
- 线程等待
- 线程分离
- 👉总结👈
👉知识补充👈



👉Linux线程概念👈
什么是线程


- 线程是在进程内部执行的,也就是说线程是在进程的地址空间内运行的,其是操作系统调度的基本单位。
- 进程等于内核数据结构加上该进程对应的代码和数据,内核数据结构可能不止一个 PCB,进程是承担分配系统资源的基本实体,将资源分配给线程!
- 那如何理解我们之前写的代码呢?其实我们之前学习的是只有一个执行流的进程,而今天学习的是具有多个执行流的进程(task_struct 是进程内部的一个执行流),所以这两者是不冲突的。
- 在运行队列中排队的都是 task_struct,CPU 只能看到 task_struct,CPU 根本不关系当前调度的是进程还是线程,只关心 task_struct。所以,CPU 调度的基本单位是”线程”。
- Linux 下的线程是轻量级进程,没有真正意义上的线程结构,没有为线程专门设计内核数据结构,而是通过 PCB 来模拟实现出线程的。
- Linux 并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口!在用户层实现了一套多进程方案,以库的方式提供给用户进行使用,这个库就是 pthread 线程库(原生线程库)。
知道了什么是线程,我们来学习创建线程的接口,来验证一下上面的结论!
pthread_create 函数的功能是创建一个新的进程。thread 是输出型参数,返回进程的 ID;attr 设置线程的属性,attr 为 nullptr 表示使用默认属性;start_routine 是一个函数地址,即线程启动后要执行的函数;arg 是传给线程启动函数的参数。调用成功是返回 0,错误是返回错误码。
Makefile
mythread:mythread.ccg++ $^ -o $@ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread
注:使用原生线程库时,必须带上 -lpthread,告诉编译器你要链接原生线程库,否则就会产生链接错误。


// 注:一下代码是示例代码,有些许问题
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <string>using namespace std;void* threadRun(void* args)
{string name = (char*)args;while(1){cout << name << " id: " << getpid() << '\n' << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof name, "%s-%d", "thread", i);pthread_create(tid + i, nullptr, threadRun, (void*)name);sleep(3); // 缓解传参的bug}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(3);}return 0;
}
ps -aL | head -1 && ps -aL | grep mythread | grep -v grep #查找线程

将进程 16889 杀掉时,全部执行流都会终止。因为线程用的资源都是进程给的,而杀掉进程就要回收进程的资源,那么线程终止了是理所当然的。
线程是如何看到进程内部的资源的呢?
我们知道,线程的运行依赖于进程的资源,一旦进程退出,线程也会退出。那进程的哪些资源是线程之间共享的,哪些资源又是线程独自占用的呢?
进程的大多数资源都被线程所共享:
- 文件描述符表,如果一个线程打开了一个文件,那么其他的线程也能够看到。
- 每种信号的处理方式(SIG_IGN、SIG_DFL 或者自定义的信号处理函数)
- 当前工作目录
- 用户 ID 和组 ID
- 进程地址空间的代码区、共享区
- 已初始化、未初始化数据区,也就是全局变量
- 堆区一般也是被所有线程共享的,但在使用时,认为线程申请的堆空间是线程私有的,因为只有这个线程拿到这段空间的其实地址
线程独自占用的资源:
- 线程 ID
- 一组寄存器。线程是 CPU 调度的基本单位,一个线程被调度一定会形成自己的上下文,那么这组寄存器必须是私有的,才能保证正常的调度。
- 栈。每个线程都是要通过函数来完成某种任务的,函数中会定义各种临时变量,那么线程就需要有自己私有的栈来保存这些局部变量。
- 错误码 errno、信号屏蔽字、调度优先级
线程 VS 进程
为什么线程的调度切换的成本更低呢?
线程进行切换时,进程地址空间和页表是不用换的。而进程进行切换时,需要将进程的上下文,进程地址空间、页表、PCB 等都要切换。CPU 内部是有 L1 ~ L3 的 Cache,CPU 执行指令时,会更具局部性原理将内存中的代码和数据预读到 CPU 的缓存中。如果是多线程,CPU 预读的代码和数据很大可能就会被所有的线程共享,那么进行线程切换时,下一个线程所需要的代码和数据很有可能已经被预读了,这样线程切换的成本就会更低!而进程具有独立性,进行进程切换时,CPU 的 Cache 缓存的代码和数据就会立即失效,需要将新进程的代码和数据重新加载到 Cache 中,所以进程切换的成本是更高的。
进程和线程的关系如下图:

线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速 I / O 操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I / O 密集型应用,为了提高性能,将 I / O 操作重叠。线程可以同时等待不同的 I / O 操作
注:线程不是创建越多越好,因为线程切换也是有成本的,并不是不需要成本。创建线程太多了,线程切换的成本有可能就是最大的成本了。
线程的缺点
- 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。如:一个线程对全局变量修改了,另外的线程的全局变量也会跟着修改;还有就是如果主线程挂掉了,其他线程也会跟着挂掉。
- 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些操作系统函数会对整个进程造成影响。
- 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程。进程终止,该进程内的所有线程也就随即退出。
线程用途
- 合理的使用多线程,能提高 CPU 密集型程序的执行效率。
- 合理的使用多线程,能提高 I / O 密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)。
👉线程控制👈

clone 函数可以创建线程或者子进程,可以设置回调函数,子进程的栈区,还有各种属性等等。除了 clone 函数,还有一个 vfork 函数。vfork 函数创建出来的子进程是和父进程共享进程地址空间的。
#include <iostream>
#include <string>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int globalVal = 100;int main()
{int id = vfork();// int id = fork();assert(id != -1);if(id == 0){// child processint count = 0;while(1){cout << "child process -> globalVal: " << globalVal << endl;sleep(1);++count;if(count == 5){globalVal = 200;cout << "child process change globalVal!" << endl;exit(1);}}}//waitpid(id, nullptr, 0); // 为了演示现象就不等待子进程了// parent processwhile(1){cout << "parent process -> globalVal: " << globalVal << endl;sleep(1);}return 0;
}

线程创建
线程创建的函数在上面已经提过了,就不在赘述了。我们也已经知道通过 kill 命令来杀掉进程,其余线程也会跟着终止。那么现在,我们就来验证一下线程出现异常导致进程终止。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);int a = 100;a /= 0;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");while(1){cout << "主线程: running ..." << endl;sleep(1); }return 0;
}

结论:线程谁先运行与调度器相关。线程一旦异常都有可能导致整个进程整体退出!
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数 return。这种方法对主线程不适用,从main 函数 return 相当于调用 exit。
- 线程可以调用 pthread_ exit 终止自己。
- 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。
注:在多线程场景下,不要使用 exit 函数,exit 函数是终止整个进程的!
pthread_exit 函数
- pthread_exit 函数的功能是终止线程。
- retval:retval 不要指向一个局部变量。
- 无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 3) break;}cout << (char*)args << " quit" << endl;pthread_exit((void*)10);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

pthread_cancel 函数
pthread_cancel 函数的功能是取消一个执行中的线程。thread 是线程的 ID,调用成功是返回 0,失败是返回错误码。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);}cout << (char*)args << " quit" << endl;pthread_exit((void*)13);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");// pthread_cancel(tid); // 不要一创建线程就取消它int count = 0;while(1){cout << "main线程 running ..." << endl;sleep(2);count++;if(count == 5) break;}pthread_cancel(tid);cout << "pthread cancel tid: " << tid << endl;void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

当一个线程被取消时,线程的退出结果是 -1(PTHREAD_CANCELED)。使用 pthread_cancel 函数的前提是线程已经跑起来了才能够取消,所以不要穿甲一个线程后就立马取消(可能刚创建的线程还没有跑起来)。一般情况下,都是用主线程来取消新线程的。如果使用新线程来取消主线程的话,这样会影响整个进程。
线程 ID 的深入理解
线程 ID 本质是一个地址!!!因为我们目前用的不是 Linux 自带的创建线程的接口,用的是 pthread 库中的接口!用户需要的是线程,而 Linux 系统只提供轻量级进程,无法完全表示线程,所以在用户和操作系统之间加了个软件层 pthread 库。操作系统承担轻量级进程的调度和内核数据结构的管理,而线程库要给用户提供线程相关的属性字段,包括线程 ID、栈的大小等等。

pthread_self 函数可以获取当前线程的 ID,既然能获得当前线程的 ID,那么线程就可以自己取消自己,但是这种方式不推荐!

线程局部存储:用 __thread 修饰全局变量带来的结果就是让每一个线程各自拥有一个全局变量,这就是线程的局部存储。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{while(1){cout << (char*)args << " g_val:" << g_val << " &g_val:" << &g_val << endl;g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << " g_val:" << g_val << " &g_val:" << &g_val << endl;sleep(2);}pthread_join(tid, nullptr);return 0;
}

去掉 __thread 修饰后,所有线程看到的全局变量都是同一个!__thread 所有 pthread 库给 g++ 编译器的一个编译选项!
在多线程的场景下进行进程替换
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{sleep(5);execl("/bin/ls", "ls", "-l", nullptr);while(1){cout << (char*)args << " g_val:" << g_val << " &g_val:" << &g_val << endl;g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << " g_val:" << g_val << " &g_val:" << &g_val << endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}

在多线程的场景下执行进程替换,那么先会将除主线程外的其它线程都终止掉,然后再进行进程替换。
线程等待
线程在创建并执行的时候,线程也是需要被等待的。如果不等待线程的话,会引起类似于进程的僵尸问题,进而导致内存泄漏。已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。
pthread_join 函数的功能是等待线程结束。thread
是要线程的 ID,retval 指向线程所执行的函数的返回值。调用该函数的线程将阻塞等待,直到 ID为 thread 的线程终止。thread 线程以不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下:
- 如果 thread 线程通过 return 返回,retval 所指向的单元里存放的是 thread 线程函数的返回值。
- 如果 thread 线程被别的线程调用 pthread_ cancel 异常终掉,retval 所指向的单元里存放的是常数
PTHREAD_ CANCELED。- 如果 thread 线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。
- 如果对 thread 线程的终止状态不感兴趣,可以传 nullptr 给 retval 参数。
- thread 线程函数的返回值不会考虑异常的情况,如果线程出现了异常,那么整个进程都会崩掉。注:状态寄存器是所有线程共享的。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");pthread_join(tid, nullptr); // 默认会阻塞等待cout << "main thread wait done... main quit too" << endl;return 0;
}

线程执行的函数的返回值是返回给主线程的,主线程通过该返回值来获取线程退出的状态。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return (void*)10;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

线程执行的函数的返回值可以多种多样,比如返回一段堆空间的起始地址。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;int* ret = new int[7];while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);ret[i] = i;if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return (void*)ret;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");int* ret = nullptr;pthread_join(tid, (void**)&ret);for(int i = 0; i < 7; ++i)cout << ret[i] << ' ';cout << endl;cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

线程分离
- 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join 是一种负担。这个时候,我们可以告诉系统:当线程退出时,自动释放线程资源,这就是线程分离。
- 一般主线程时不退出的,当用户有个任务要处理,主线程就可以创建新线程来执行用户的任务,但主线程不关心任务处理的结果,那么就可以将该线程分离出去。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{pthread_detach(pthread_self());while(1){cout << (char*)args << " g_val:" << g_val << " &g_val:" << &g_val << endl;g_val++;break;}pthread_exit((void*)11);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << " g_val:" << g_val << " &g_val:" << &g_val << endl;sleep(1);break;}int n = pthread_join(tid, nullptr);cout << "n: " << n << " error string: " << strerror(n) << endl;return 0;
}

注:joinable 和分离是冲突的,一个线程不能既是joinable 又是分离的。
如果线程被分离,但是该线程出现了异常,这样也会影响到整个进程。线程执行的是进程派发的任务,尽管线程被分离了,线程也离不开进程的资源,所以线程出现了异常也会导致进程终止。

注:C++ 11 的线程库也是调用了原生线程库的,所以在使用 C++ 的线程库时也要指定链接原生线程库。
👉总结👈
本篇博客主要讲解了什么是线程、线程和进程的区别、线程的优缺点、线程异常、线程用途以及线程控制等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️
相关文章:
【Linux】线程概念 | 线程控制
🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉知识补充&…...
pocsuite3安装及使用
pocsuite3安装及使用简介项目地址环境配置及安装环境要求安装(详情可以参考[https://pocsuite.org/](https://pocsuite.org/))使用方法运行模块加载目标参数:Console模式查看有哪些模块使用Telnet 弱密码模块这里以flask模板注入漏洞为例pocs…...
docker从安装到部署一个项目
一.centos安装docker 参考博客:https://blog.csdn.net/m0_47010003/article/details/127775185 1.设置一下下载Docker的镜像源 设置下载的镜像源为国内的阿里云,如果不设置,会默认去Docker的官方下载 yum-config-manager --add-repo http…...
QT编程从入门到精通之十二:“第四章:Qt程序创建基础”之“4.1 创建基础程序”
目录 第四章:Qt程序创建基础 4.1 创建基础程序 4.1.1 新建一个项目...
黑客入门教程【非常详细】从零基础入门到精通,看这一篇就够了!
首先要明白啊,我们现在说的黑客不是那种窃取别人信息、攻击别人系统的黑客,说的是调试和分析计算机安全系统的网络安全工程师。 黑客技术的核心就是渗透攻防技术,是为了证明网络防御按照预期计划正常运行而提供的一种机制。就是通过模拟恶意…...
手机怎么远程控制腾讯云云服务器?
手机怎么远程控制腾讯云云服务器?腾讯云提供的连接:Windows系统。可以用远程桌面连接,你本地电脑点击,开始-运行-输入mstsc,弹出的框里,填IP和账号密码信息。 管理服务器上还会运行一个数据采集程序&#…...
dorcker与vlu靶场搭建
dorcker与vlu靶场搭建 dorcker安装 以kali linux 为例 安装必要的一些系统工具 apt update apt -y install apt-transport-https ca-certificates curl software-properties-common 添加Docker PGP key curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg …...
Unity性能优化 - Overdraw篇
一、什么是Overdraw Unity Overdraw(超绘)是指在渲染过程中绘制了超过一次相同像素的现象。当多个UI元素重叠时,每个像素都需要被多次绘制。这种绘制超出了渲染所需的最小像素数,因此被称为Overdraw。 二、都有哪些元素会导致Ov…...
Tp5操作mysql json函数
Tp5操作mysql json函数 官方文档介绍正常单个json数据更新没什么问题,但是某些情况可能一次修改多个,但是也不想全部替换 怎么处理?注意 :在源码中 项目\thinkphp\library\think\db\builder.php@parseData方法中 修改控制器调用:官方文档介绍 JSON字段 从V5.1.4+版本开始…...
【蓝桥杯嵌入式】PWM的设置,原理图解析与代码实现(第十一届省赛为例)——STM32
🎊【蓝桥杯嵌入式】专题正在持续更新中,原理图解析✨,各模块分析✨以及历年真题讲解✨都在这儿哦,欢迎大家前往订阅本专题,获取更多详细信息哦🎏🎏🎏 🪔本系列专栏 - 蓝…...
Learning C++ No.13【STL No.3】
引言: 北京时间:2023/3/7/15:33,还有27分钟就要去上课啦!刚刚把最近因为考试原因欠的课给还干净了,已经准备好今天晚上接受航哥的毒打了,毒打就毒打,咱不怕,只要不欠钱,…...
推荐收藏!10大程序员必备生产力工具
作为程序员,提高生产力是我们一直追求的目标。随着技术的发展,越来越多的工具和应用程序被开发出来,帮助程序员们更好地完成工作。在本文中,我将介绍一些程序员必备的生产力工具。 一、IDE(集成开发环境) …...
【项目总结】基于SSM+SpringBoot+Redis的个人博客系统项目总结
文章目录项目介绍(开发背景)数据库设计主要使用到的技术点前端后端自定义统一返回对象自定义拦截器加盐加密操作分页功能session持久化自定义头像的存储和获取项目编写过程中遇到的困难点困难点一(小)困难点二(小&…...
从入门到精通MongoDB数据库系列之一:MongoDB简介
从入门到精通MongoDB数据库系列之一:MongoDB简介 一、易于使用二、易于扩展三、功能丰富四、性能卓越五、设计理念MongoDB是功能强大、灵活且易于扩展的通用型数据库。融合了二级索引、范围查询、排序、聚合以及地理空间索引等诸多特性。 一、易于使用 MongoDB是面向文档的数…...
大数据系列——什么是hdfs?hdfs用来干什么的?
一、什么是HDFSHDFS全称是Hadoop Distributed File System是一种分布式文件系统(HDFS使用多台计算机存储文件,对外提供统一操作文件的接口)Hodoop使用HDFS(Hadoop Distributed File System)作为存储系统。二、hdfs用来干什么的用于大规模数据的分布式读写࿰…...
云端地球2月更新了这些功能,你都用过了吗?
时光飞逝、转眼已到2023年的第三个月,武汉的天气也逐渐转好,温度步步高升。云端地球产研团队的脚步也越走越快,虽然春节仿佛还是昨天的事,但云端地球已经完成了四次迭代,为广大建模爱好者带来了更多实用功能࿰…...
基于gin-vue-admin[gin+gorm]手动实现crud(全)
使用Gin-Vue- Admin框架手动实现crud 在gva框架下自己手动实现一个CRUD的操作,该操作将会结合gen进行探讨学习,具体实现可以看下面代码的实现,项目目录层级分为api层,service层,model层,common层ÿ…...
彻底关闭Windows10更新!!
以下四个步骤都需要执行。 一、禁用Windows Update服务 1、同时按下键盘 Win R,然后输入 services.msc ,点击确定。 2、找到 Windows Update 这一项,并双击打开。 3、双击打开它,点击 停止,把启动类型选为 禁用&…...
跨时钟域CDC
https://www.cnblogs.com/icparadigm/p/12794483.html https://www.cnblogs.com/icparadigm/p/12794422.html 亚稳态 是什么 时序逻辑在跳变时,由于异步信号、跨时钟域等原因,不满足setup或hold条件,输出在0和1之间产生振荡。 原因 D触发…...
JavaEE简单示例——Spring的控制反转
简单介绍: 在之前的入门程序中,我们简单的介绍了关于Spring框架中的控制反转的概念,这次我们就来详细的介绍和体验一下Spring中的控制反转的理论和实操。 使用方法: 控制反转(IoC)是面向对象编程中的一个…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...






