Linux线程(1)--线程的概念 | 线程控制

目录

前置知识

线程的概念

Linux中对线程的理解

 重新定义进程与线程

重谈地址空间

线程的优缺点

线程的优点

线程的缺点

线程异常

线程的用途

Linux线程 VS 进程

线程控制

创建线程

线程等待

线程终止

线程ID的深入理解


前置知识

我们知道一个进程有属于自己的PCB(task_struct),地址空间,页表;OS会为进程在物理内存中申请资源(空间),通过页表与地址空间产生映射关系。在执行进程时,会通过地址空间使用OS为进程申请的资源。

因此我们可以说:

地址空间时进程的资源窗口

在创建子进程时,也会为子进程创建属于它的PCB,地址空间,页表,与物理内存的资源。子进程会将父进程的部分属性拷贝下来。


线程的概念

在一些教材中对于线程是这样定义的:

线程:是进程内的一个执行分支。线程的执行粒度,要比进程要细。

 这里对线程的定义是通过线程的特点定义的,并不能很好的解释什么是线程。

这里可以这样解释:

Linux中对线程的理解


我们知道在之前我们创建的进程都是一个PCB(task_struct)的,这里我们再创建多个特殊的 "进程" :

这个 "进程" 有属于自己的PCB,但是与它的 "父进程",共享同一个地址空间,同一个页表,同一块物理内存。(即:将只属于一个PCB的栈,堆等资源,划分一部分给另一个新创建的PCB).  

由于父进程与新创建的“子进程”,他们共享一个地址空间,可能会导致他们可以使用同一个资源。

这里我们可以说这些"进程"的执行粒度要比,原来进程(一个task_struct)的执行粒度要小。

原因:原来进程由自己一个就可以执行完全代码,使用空间。而现在却需要多个"进程"去执行代码,使用空间。这些"进程"是原来进程的一个执行分支(执行流)。因此我们说现在这些 "进程"的执行粒度小于原来进程的。

这里为了和原来的进程进行区分,我们把新创建的"进程"叫做:线程。


Linux中实现线程的方案:

1.在Linux中,线程在进程"内部"执行,即:线程在进程的地址空间内运行。

  • 任何执行流要执行,都要有资源,在上面我们说过,地址空间是进程的资源窗口。这里线程采用了共用同一个地址空间(将地址空间分成若干份,分配给线程)。所以说新城在地址空间中运行。

2.在Linux中,线程的执行粒度要比进程要细。

  • 线程执行了进程的一部分代码,共享同一份资源。

注:不同的操作系统对线程的实现方案可能是不一样的,但实现原理都是一样的。


同时我们知道操作系统用PCB对进程描述与管理,会通过进程的PCB进行调度,但是对于多线程来说,一个进程中会有多个PCB,那么该如何调度的呢?

这里我们要知道在OS中是通过CPU进行调度的,对于CPU来说,它并没有进程与线程的概念,它只有调度执行流的概念,即:task_struct(只要有代码与数据让CPU去执行就可以了)。

 重新定义进程与线程

什么叫做线程?

  • 我们认为线程是操作系统调度的基本单位。

什么是进程?

  • 在内核角度:进程是操作系统分配资源的基本实体,执行流也是资源,所以线程是进程内部的执行流资源!

        对于以前的进程可以这样理解:操作系统是以进程为基本单位进行资源分配,在进程内部就有一个执行流。


Linux中对线程的描述与管理

 这里我们知道,在操作系统里一个进程可能有1个或多个线程,所以进程和线程的关系是1:N的。在操作系统用PCB(task_struct)来描述和管理进程,所以线程也可以采用同样的方式来进行管理,在有些操作系统中(Windows)用struct tcb(thread control block)来进行描述与管理。但是我们知道对于进程的管理就已经很复杂了,如果再进一步细分struct tcb会更加的复杂。所以在Linux中,并没有使用struct tcb来对线程进行描述与管理,由于线程是进程的一个执行流,在大体上并没有太大的区别,于是采用了复用进程的PCB(task_struct)来对线程进行描述与管理。

因此可以在进程中只有一个PCB时,把它当作进程的,多个PCB时,当作线程的。甚至可以不区分这些,直接把他们当作执行流去看待。

由于Linux是采取复用进程的结构体去管理线程的,所以Linux没有真正意义上的线程,而是用"进程的内核数据结构 "模拟线程的。

这里以CPU的角度去看:

线程<=执行流<=进程

所以在Linux中执行流,也叫做轻量级进程。


重谈地址空间

这里我们知道进程是OS分配资源的基本单位,线程是调度的基本单位。在进程里多线程会共享同一块地址空间,它们会把 地址空间里的资源划分给每一个线程,那么是如何划分的呢?这里我们就不得不再谈地址空间了。

 

虚拟地址是如何转换到物理地址???(以32位虚拟地址为例)

在Linux中,将32位的虚拟地址,分为了三部分:32=10+10+12。同时页表并不是直接记录在一张表里的。因为:地址空间一共有4GB个,一个按照10字节算,页表最大为40GB,明显太大,不能存储下来,更别说其他的了。页表是这样的:分为两级:第一级页表有1024个,存放第二级页表地址;第二级页表:有1024个,存放了物理内存中页框的起始地址。

在Linux中这样转换的:

这里我们知道一个变量的地址是变量的起始地址,那么从虚拟地址转化成物理地址,我们只能找到一个物理地址,但是如果是int,那么它是用4个字节存储的,那么他是如何读取的呢?

这里我们知道每一个变量都有一个类型,在读取数据是,就识别出了它的类型,在它找到物理地址时,会加上类型大小对应的偏移量进行读取。


在了解地址空间后,我们知道线程的资源分配,全部都是由地址空间来的,而所有的代码于数据都是,地址空间通过页表的映射在物理内存中找到的。

所以线程分配资源的本质就是分配地址空间。

线程的优缺点

线程比进程要更轻量化:

创建和释放更加轻量化

  • 创建线程只需要创建一个PCB,而进程不但要创建PCB,还要创建地址空间,页表,将地址空间通过页表于物理地址进行映射等。

切换更加轻量化

  •  在线程切换时,只需要改变CPU寄存器中对应线程的的上下文内容于数据,但是效率主要来自于一个存储常用数据cache,切换线程时并不会改变该cache,而进程会将其清空,从头慢慢开始。

我们知道进程有时间片,线程也有时间片。

在OS中并不会为创建的新线程重新赋予时间片,而是瓜分同一进程的时间片(时间片也是资源,会分配给每个线程)。

那OS是如何区分是进程切换还是线程进行切换的呢?

task_struct是可以标识,这里在进程中的线程是有主次之分的,在进程刚开始的时候的task_struct时主线程,其他创建的是副线程。在主线程的task_struct里记录着进程的时间片于给线程的时间片,在其他副线程中记录着线程的时间片,当线程的时间片结束,主线程中记录进程的时间片会减去线程的时间,这是如果为0,则进行进程的切换,否则进行线程的切换

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现(不是线程越多越合适,一般有多少个CPU就创建多少个线程)
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

性能损失

  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低
  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多

线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该 进程内的所有线程也就随即退出

线程的用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是 多线程运行的一种表现)

Linux线程 VS 进程

进程和线程
进程是资源分配的基本单位
线程是调度的基本单位
线程共享进程数据,但也拥有自己的一部分数据:
  • 线程ID
  • 一组寄存器 (线程的上下文)
  • errno
  • 信号屏蔽字
  • 调度优先级
进程的多个线程共享 同一地址空间,因此Text SegmentData Segment都是共享的,如果定义一个函数,在各线程 中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
  • 文件描述符表 (即:一个线程打开一个文件,所有线程都打开了)
  • 每种信号的处理方式(SIG_ IGNSIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

线程控制

创建线程

在Linux中复用了进程的PCB来对线程进行描述于管理,因此在Linux中没有明确的线程的概念,只有轻量级进程的概念。因此在Linux中不会直接提供线程的系统调用,只会给我们提供轻量级的系统调用!

但由于我们用户需要线程的接口去创建线程。

因此一些大佬们专门创建了一个应用层的pthread线程库(对轻量级进程接口进行封装),为用户提供直接创建线程的接口。(pthread线程库是一个第三方库,几乎所以的Linux平台,都默认自带这个库)

  • 功能:创建一个新的线程
参数
  • thread:输出型参数,创建线程成功返回线程ID。

  • attr:设置线程的属性,attr为nullptr表示使用默认属性
  • start_routine:是个函数指针(返回值为void*  参数也为void*),线程启动后要执行的函数里面的内容
  • arg:传给线程启动函数的参数,为start_routine指向函数的参数。不需要参数为nullptr

返回值:成功返回0;失败返回错误码

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{while(true){cout<<"new thread:"<<endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);while(true){cout<<"main thread"<<endl;sleep(1);}return 0;
}

编译:

g++ -o thread thread.cc -std=c++11

这里我们发现,在编译时报错。

这里是因为pthread_create是第三方库的接口,不是系统调用。g++在编译时,只会链接自己的C/C++库,这里因为Linux已经将pthread库加载到了指定路径下,因此我们只需要告诉g++链接那个库就可以了。

我们要这样编译:

g++ -o thread thread.cc -l pthread -std=c++11

这里我们还可以通过指令查看执行流:
 

ps -aL

  • LWP(Light Weight Process)就是轻量级进程的ID(用来标识轻量级进程),可以看到两个轻量级进程的PID相等,所以他们属于同一个进程。这里有一个线程的LWP和PID相等,这里我们把这个线程叫做主线程,另一个就是新创建的线程。
  • 之前说的OS调度一个进程,可以认为单进程单执行流,它调度的基本单位看的是pid也可以是LWP,因为它两相等。现在我们认为线程调度时OS看的是LWP。
  • LWP和PID的关系?   PID是对进程标识,相同进程具有相同的PID。LWP标识轻量级进程,它们的标识不同。OS真正调用时使用的时LWP

注:在同一进程里所有线程的PID都相等。

验证线程共享全局变量:

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;int val=100;void* thread(void* args)
{while(true){printf("new thread:  getpid:%d  val=%d    &val=%p\n",getpid(),val,&val);sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);while(true){printf("main thread:  getpid:%d  val=%d    &val=%p\n",getpid(),val,&val);val++;sleep(1);}return 0;
}

 验证一个线程出异常,整个进程崩溃:

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;int val=100;void* thread(void* args)
{while(true){sleep(5);int a=1;a/=0;}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);while(true){printf("main thread:  getpid:%d  val=%d    &val=%p\n",getpid(),val,&val);val++;sleep(1);}return 0;
}


获取线程ID

常见的获取线程ID的方式有两种:

  • 通过创建线程pthread_create函数的第一个输出型参数获得。
  • 通过调用pthread_self函数获得。

这里pthread_self函数那个线程调用,就获取那个线程的ID,类似于调用getpid()函数。

代码:主线程调用pthread_create函数,通过输出型参数获取并打印;在创建的新线程中调用pthread_self函数获得线程ID。

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{while(true){printf("new thread:           new thread tid=%p\n",pthread_self());//将线程ID以16进制打印出来sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);while(true){printf("main thread:   create new thread tid=%p\n",tid);//将线程ID以16进制打印出来sleep(1);}return 0;
}

用这里我们用pthread_self函数获取的线程ID于内核的LWP的值不同,pthread_self函数获得的是用户级原生线程库的线程ID,而LWP是内核的轻量级进程ID,他们之间是1:1。

线程等待

  • 这里创建完线程,我们并不知道是那个线程先进行。但是我们可以确定的是主线程最后退出,因为新线程是在主线程中创建的,主线程要对创建的新线程进行管理,所以最后退出。

  • 因此,一个线程被创建出来,这个线程如进程一般,需要主线程进行等待,如果主线先退出会出现类似于进程中的僵尸进程的情况,造成内存泄漏。(这里线程退出,类似于进程的退出,它的空间并没有被完全的释放。)

功能:

  • 指定主线程等待那个线程

参数:

  • thread:等待线程ID(用户级别的)
  • retval:接收线程退出时的返回值,若不使用可以为nullptr。

返回值:

  • 成功,返回0;失败返回错误码。
#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{int cnt=5;while(cnt--){cout<<"new thread: "<<cnt<<endl;sleep(1);}return (void*)101;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);void* retval;pthread_join(tid,&retval);主线程等待新线程退出;通过retval获取线程的返回值cout<<"main thread quit... retval:"<<(long long int)retval<<endl;return 0;
}

注:主线程等待的时候,默认是阻塞等待。

线程终止

1.return返回

在线程中使用return代表当前进程退出,但是在main函数中使用,代表整个进程退出,也就是说只要主线程退出,那么整个资源就会被释放,而其他线程会因为没有资源,自然而然的也退出了。

2.pthread_exit函数

功能:终止调用该函数的线程。

参数:retval:线程退出时的退出码信息。

说明:

  • 该函数无返回值,跟进程一样,线程结束时无法返回它的调用者(自身)。
  • pthread_exit或者return返回指针所指向的内存单元必须在全部变量或是用malloc分配的,不能在线程函数的只能上分配,因为当其他线程得到这个返回指针时,指针指向的空间已经被释放了。
  • exit函数的作用时终止整个进程,任何一个线程调用exit函数就代表着整个进程终止。
#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{int cnt=5;while(cnt--){cout<<"new thread: "<<cnt<<endl;pthread_exit((void*)101);//终止线程。这里的参数可以理解为线程的退出码,可以被pthread_join的第二个参数接收sleep(1);}return (void*)101;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);void* retval;pthread_join(tid,&retval);主线程等待新线程退出;通过retval获取线程的返回值cout<<"main thread quit... retval:"<<(long long int)retval<<endl;return 0;
}

3.pthread_cancel

功能:取消指定线程,类似于kill。被取消的线程的错误码(返回值)为-1。

参数:取消线程的ID。

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{int cnt=5;while(cnt--){cout<<"new thread: "<<cnt<<endl;sleep(1);}return (void*)101;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);sleep(1);//保证线程已创建成功pthread_cancel(tid);//取消线程void* retval;pthread_join(tid,&retval);主线程等待新线程退出;通过retval获取线程的返回值cout<<"main thread quit... retval:"<<(long long int)retval<<endl;return 0;
}

线程ID的深入理解

  • pthread_create函数会产生一个线程,这里和进程一样,也需要一个属性来标识线程,这里我们叫做线程的ID,存放在第一个参数指向的地址中,这里线程的ID于内核中的LWP不是一回事的。
  • 内核中LWP属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小单位。
  • Linux不提供真正的线程,只提供轻量级进程,也就意味着操作系统只需要对内核执行流LWP进行管理,而供用户使用的线程接口等其他数据,应该由线程库自己管理。因此管理线程时的“先描述,再组织”就应该在线程库里进行。
  • 这里线程库是一个动态库,加载到地址空间的共享区中
  • 每个线程都有自己私有的栈,其中主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是在共享区中开辟的。除此之外,每个线程都有自己的struct pthread,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。
  • 每一个新线程在共享区都有这样一块区域对其进行描述,因此我们要找到一个用户级线程只需要找到该线程内存块的起始地址,然后就可以获取到该线程的各种信息。

  • 上面我们所用的各种线程函数,本质都是在库内部对线程属性进行的各种操作,最后将要执行的代码交给对应的内核级LWP去执行就行了,也就是说线程数据的管理本质是在共享区的。
  • pthread_t到底是什么类型取决于实现,但是对于Linux目前实现的NPTL线程库来说,线程ID本质就是进程地址空间共享区上的一个虚拟地址,同一个进程中所有的虚拟地址都是不同的,因此可以用它来唯一区分每一个线程。
  • 所谓线程ID可以理解为每个新线程在库当中的内存位置的起始地址,线程控制块的起始地址。
#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;void* thread(void* args)
{int cnt=5;while(cnt--){printf("new thread  tid:%p\n",pthread_self());sleep(1);}return (void*)101;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,thread,nullptr);while(1){printf("main thread tid:%p\n",pthread_self());sleep(1);}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/687149.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker (六)-进阶篇-数据持久化最佳实践MySQL部署

容器的数据挂载通常指的是将宿主机&#xff08;虚拟机或物理机&#xff09;上的目录或文件挂载到容器内部 MySQL单节点安装 详情参考docker官网文档 1 创建对应的数据目录、日志目录、配置文件目录(参考二进制安装&#xff0c;需自己建立数据存储目录) mkdir -p /data/mysq…

计算机毕业设计springboot_vue房屋租赁系统_ku668

1.掌握Html&#xff0c;Css&#xff0c;JavaScript等基础编程语言。 2.掌握Vue框架&#xff0c;node环境&#xff0c;数据库等知识。 3.掌握开发系统的基本流程。 …

你了解API测试吗?如何充分的测试一个API?

什么是API&#xff1f; API代表应用程序接口。API是软件系统中的中间层&#xff0c;负责数据源与用户看到的图形用户界面&#xff08;GUI&#xff09;之间的数据通信。换句话说&#xff0c;API是软件的业务层&#xff0c;它在表示层和数据层之间创建连接。 API测试侧重于所谓的…

2.17C语言学习

P1678 烦恼的高考志愿 写完后发现题解里面用的是优先队列或者二分什么的&#xff0c;其实这个题可以贪心&#xff0c;我们把学校的分数线和学生的成绩分别进行排序&#xff0c;然后从前往后遍历&#xff0c;每次比较当前学校的分数与学生成绩的差距和下一个学校的分数与学生成…

re-captioning技术是什么

参考https://zhuanlan.zhihu.com/p/664192860 模型对图片进行caption操作时&#xff0c;输出的标题一般描述图片中的主体&#xff0c;而忽视了背景、常识关系等更为细节的描述。 图片比较重要的细节的描述应当包括&#xff1a; 物体存在的场景。如&#xff1a;在厨房的水槽&am…

Qt之条件变量QWaitCondition详解(从使用到原理分析全)

QWaitCondition内部实现结构图&#xff1a; 相关系列文章 C之Pimpl惯用法 目录 1.简介 2.示例 2.1.全局配置 2.2.生产者Producer 2.3.消费者Consumer 2.4.测试例子 3.原理分析 3.1.辅助函数CreateEvent 3.2.辅助函数WaitForSingleObject 3.3.QWaitConditionEvent …

阿里云服务器服务费怎么计算的?详细报价解析

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

001 - Hugo, 创建一个网站

001 - Hugo, 创建一个网站安装hugoWindows系统Macos Hugo博客搭建初始化博客主题安装配置博客各个页面开始创作创建 GitHub Page 仓库本地调试和预览发布内容 教程及鸣谢文字教程视频教程 001 - Hugo, 创建一个网站 这篇文章假设你已经&#xff1a; 了解基本的终端命令行知识&…

黑马鸿蒙教程学习1:Helloworld

今年打算粗略学习下鸿蒙开发&#xff0c;当作兴趣爱好&#xff0c;通过下华为那个鸿蒙开发认证&#xff0c; 发现黑马的课程不错&#xff0c;有视频和完整的代码和课件下载&#xff0c;装个devstudio就行了&#xff0c;建议32G内存。 今年的确是鸿蒙大爆发的一年呀&#xff0c;…

Win32汇编数组学习2

之前学习过win32汇编数组&#xff1b;还不熟悉&#xff1b;继续熟悉&#xff1b; 先做几个基本的对话框&#xff0c;有一个静态文本框&#xff1b; 定义数组之后&#xff0c;用 wsprintf 函数格式化&#xff0c;然后调用 SetDlgItemText 赋值给静态文本框&#xff1b; arr1 …

【力扣hot100】刷题笔记Day5

前言 回学校了&#xff0c;荒废了半天之后打算奋发图强猛猛刷题&#xff0c;找实习&#xff01;赚钱&#xff01;&#xff01; 560. 和为 K 的子数组 - 力扣&#xff08;LeetCode&#xff09; 前缀法 哈希表 这个题解解释比官方清晰&#xff0c;截个图方便看&#xff0c;另一…

OpenCV-42 直方图均匀化

目录 一、直方图均匀化原理 二、直方图均匀化在OpenCV中的运用 一、直方图均匀化原理 直方图均匀化是通过拉伸像素强度的分布范围&#xff0c;使得在0~255灰阶上的分布更加均匀&#xff0c;提高图像的对比度。达到改善图像主管视觉效果的目的。对比度较低的图像适合使用直方…

由于找不到MSVCP140.dll无法运行软件游戏,多种解决方法分享

电脑系统在运行过程中&#xff0c;当出现“由于找不到MSVCP140.dll”这一提示时&#xff0c;可能会引发一系列潜在的问题与影响。当电脑无法找到这个特定的dll文件时&#xff0c;意味着相关应用可能无法顺利加载并执行必要的组件&#xff0c;进而导致程序无法启动或运行过程中频…

【PCB】Allegro PCB 的模块复用操作

【PCB】Allegro PCB 的模块复用操作

51_蓝桥杯_led流水灯

一 原理图分析 二 三八译码器工作原理 三八译码器&#xff1a;3个输入控制8路互斥的低电平有效输出。 C B A 输出 0 0 0 Y0 0 0 1 Y1 0 1 0 Y2 0 1 1 Y3 1 0 0 Y4 1 0 1 Y5 1 1 0 Y6 1 1 1 Y7 三 锁存器工作原理 锁存器&#xff1a;当使…

Open CASCADE学习|布尔运算

目录 1、加法&#xff1a;BRepAlgoAPI_Fuse 2、减法&#xff1a;BRepAlgoAPI_Cut 3、交集&#xff1a;BRepAlgoAPI_Common 4、交线&#xff1a;BRepAlgoAPI_Section 1、加法&#xff1a;BRepAlgoAPI_Fuse #include <gp_Pnt.hxx>#include <BRepPrimAPI_MakeBox.hxx…

“我觉得我今年可能要随便找个人嫁了,下半辈子应该都不会再快乐了”

2月15日&#xff0c;“张颂文情商”的话题登上热搜&#xff0c;引发网友热议。 许多人对张颂文老师的情商表达了高度的赞扬和敬意&#xff0c;纷纷感叹&#xff1a;“张颂文老师真的是一个非常会安慰人的人&#xff01;” 在2月13日的一条微博中&#xff0c;张颂文分享了家里三…

【JavaEE】_HTTP请求首行

目录 1. URL 2. 方法 2.1 GET方法 2.2 POST方法 2.3 GET与POST的区别 2.4 低频使用方法 1. URL 在mysql JDBC中已经提到过URL的相关概念&#xff1a; 如需查看有关JDBC更多内容&#xff0c;原文链接如下&#xff1a; 【MySQL】_JDBC编程-CSDN博客 URL用于描述某个资源…

K8s进阶之路-安装部署K8s

参考&#xff1a;&#xff08;部署过程参考的下面红色字体文档链接就可以&#xff0c;步骤很详细&#xff0c;重点部分在下面做了标注&#xff09; 安装部署K8S集群文档&#xff1a; 使用kubeadm方式搭建K8S集群 GitBook 本机&#xff1a; master&#xff1a;10.0.0.13 maste…

[Angular 基础] - 视图封装 局部引用 父子组件中内容传递

[Angular 基础] - 视图封装 & 局部引用 & 父子组件中内容传递 之前的笔记&#xff1a; [Angular 基础] - Angular 渲染过程 & 组件的创建 [Angular 基础] - 数据绑定(databinding) [Angular 基础] - 指令(directives) 以上为静态页面&#xff0c;即不涉及到跨组…