【Linux】第十二章 多线程(线程概念+线程控制)

news2024/9/21 22:50:31

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙


【Linux】第一章环境搭建和配置

【Linux】第二章常见指令和权限理解

【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)

【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)

【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)

【Linux】第十章 进程间通信(管道+system V共享内存)

【Linux】第十一章 进程信号(概念+产生信号+阻塞信号+捕捉信号)


文章目录

  • 💙系列文章💙
  • 💎一、线程概念
    • 🏆1.线程定义
    • 🏆2.线程的优点和缺点
    • 🏆3.线程异常
    • 🏆4.线程用途
    • 🏆5.二级页表
  • 💎二、线程和进程
  • 💎三、线程控制
    • 🏆1.POSIX线程库
    • 🏆2.线程创建
      • pthread_create
    • 🏆3.进程ID和线程ID
      • pthread_self
    • 🏆4.线程ID及进程地址空间布局
    • 🏆5.线程终止
      • pthread_exit
      • pthread_cancel
    • 🏆6.线程等待
      • pthread_join
    • 🏆7.线程分离


💎一、线程概念

🏆1.线程定义

  • 线程是OS能够进行运算调度的基本单位。更准确的定义是:线程是“一个进程内部的控制序列”。
  • 一切进程至少都有一个执行线程。
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更轻量化。
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
  • Linux下没有真正意义上的线程,线程是通过进程来模拟实现的

在这里插入图片描述

这样可以看出一个线程就是一个执行流,每一个线程有一个task_struct的结构体,和进程一样,这些task_struct都是由OS进行调度,线程在进程内部运行,本质就是线程在进程地址空间内运行,也就是说曾经这个进程申请的所有资源,几乎都是被所有线程共享的。。

红色框框是进程,除了task_struct之外,一个进程还要有进程地址空间、文件、信号等等,合起来称之为一个进程。承担分配系统资源的基本实体

🏆2.线程的优点和缺点

优点:

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

缺点:

  • 性能损失

    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  • 健壮性降低

    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制

    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高

    编写与调试一个多线程程序比单线程程序困难得多。

🏆3.线程异常

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

🏆4.线程用途

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

🏆5.二级页表

以32位平台为例,在32位平台下一共有232个地址,也就意味着有232个地址需要被映射。

  1. 选择虚拟地址的前10个比特位在页目录当中进行查找,找到对应的页表。
  2. 再选择虚拟地址的10个比特位在对应的页表当中进行查找,找到物理内存中对应页框的起始地址。
  3. 最后将虚拟地址中剩下的12个比特位作为偏移量从对应页框的起始地址处向后进行偏移,找到物理内存中某一个对应的字节数据。

在这里插入图片描述

  • 物理内存实际是被划分成一个个4KB大小的页框的,4KB实际上就是212个字节,也就是说一个页框中有212个字节,而访问内存的基本大小是1字节,因此一个页框中就有212个地址
  • 页目录项是一级页表,页表项是二级页表
  • 每一个表项还是按10字节计算,页目录和页表的表项都是210个,因此一个表的大小就是210 * 10个字节,也就是10KB
  • 而页目录有210个表项也就意味着页表有210个,也就是说一级页表有1张,二级页表有210张,总共算下来大概就是10MB

修改常量字符串会报错:修改一个字符串常量时,虚拟地址必须经过页表映射找到对应的物理内存,而在查表过程中发现其权限是只读的就会报错

在这里插入图片描述

💎二、线程和进程

进程是承担分配系统资源的基本实体,线程是调度的基本单位,进程具有独立性,但线程之间可以互相影响。

线程共享一部分进程数据,也有自己独有的一部分数据:

  • 线程ID
  • 一组寄存器(记录上下文信息,任务状态段)
  • 栈(线程私有栈),没有堆
  • errno(错误码)
  • 信号屏蔽字
  • 调度优先级

进程的多个线程共享同一地址空间:

  • 如果定义一个函数,在各线程中都可以调用。
  • 如果定义一个全局变量,在各线程中都可以访问到。
  • 文件描述符表。(进程打开一个文件后,其他线程也能够看到)
  • 每种信号的处理方式。(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
  • 当前工作目录。(cwd)
  • 用户ID和组ID。

关系图:

在这里插入图片描述

💎三、线程控制

🏆1.POSIX线程库

  • 要使用这些函数库,要通过引入头文件<pthreaad.h>。
  • 链接这些线程函数库时,要使用编译器命令的“-lpthread”选项。

错误检查:pthreads函数出错时不会设置全局变量errno,而是将错误代码通过返回值返回

🏆2.线程创建

pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 

功能: 创建一个线程

参数:

  • thread:输出型参数,获取线程ID
  • attr:设置线程的属性,attr为NULL代表默认属性
  • start_routine:函数指针,传一个函数地址,这个函数作为线程的启动后执行的函数
  • arg:传给启动函数的参数

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

实例:让主线程调用pthread_create函数创建一个新线程,此后新线程就会跑去执行自己的新例程,而主线程则继续执行后续代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d\n", msg,getpid());
		sleep(1);
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, pthreadrun, (void*)"thread 1");
	while (1){
		printf("I am main thread!, PID:%d\n",getpid());
		sleep(2);
	}
	return 0;
}

结果:新线程每隔一秒执行一次打印操作,而主线程每隔两秒执行一次打印操作

[root@VM-20-8-centos:/home/Jungle/lesson32]# ./main
I am main thread!, PID:30083
I am thread 1, PID:30083
I am thread 1, PID:30083
I am main thread!, PID:30083
I am thread 1, PID:30083
I am thread 1, PID:30083

使用ps -aL命令,可以显示当前的轻量级进程,发现这两个线程都属于同一个进程

[Jungle@VM-20-8-centos:~]$ ps -aL
  PID   LWP TTY          TIME CMD
21028 21028 pts/6    00:00:00 sudo
21029 21029 pts/6    00:00:00 scl
21030 21030 pts/6    00:00:00 bash
21035 21035 pts/6    00:00:00 bash
30083 30083 pts/6    00:00:00 main
30083 30084 pts/6    00:00:00 main
30163 30163 pts/8    00:00:00 ps

🏆3.进程ID和线程ID

  • 引入线程概念之后,一个用户进程下管理多个用户态线程,每个线程作为一个独立的调度实体,在内核中都有自己的进程描述符。进程和线程关系变成了1:N的关系

  • 多线程的进程,又被称为线程组。线程组内的每一个线程在内核中都有一个进程描述符与之对应。进程描述符结构体表面上看是进程的pid,其实它对应的是线程ID;进程描述符中的tpid,含义是线程组ID,该值对应的是用户层面的进程ID

  • 进程是资源的分配单位,所以线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配

struct task_struct {
	...
	pid_t pid;// 对应的是线程ID,就是我们看到的lwp
	pid_t tgid;// 线程组ID,该值对应的是用户层面的进程ID
	...
	struct task_struct *group_leader;
	...
	struct list_head thread_group;
	...
};

具体关系:

用户态系统调用内核进程描述符中对应的结构
线程IDpid_t gettid(void)pid_t pid
进程IDpid_d getpid(void)pid_t tgid

注意:这里的线程ID是属于内核中的LWP属于进程调度的范畴,用来标识轻量级进程

观察以下代码,LWP是内核的轻量级进程ID,第一个线程的ID和进程ID是一样的,这个线程就是主线程

注意: 线程和进程不一样,进程有父进程的概念,但是在线程组中,所有的线程都是对等关系。

[Jungle@VM-20-8-centos:~/lesson33]$ ps -aL
  PID   LWP TTY          TIME CMD
20966 20966 pts/11   00:00:00 main
20966 20967 pts/11   00:00:00 main

pthread_self

 pthread_t pthread_self(void);

功能: 获取线程自身ID

返回值: 线程自身ID

实例:下面代码中在每一个新线程被创建后,主线程都将通过输出型参数获取到的线程ID进行打印,此后主线程和新线程又通过调用pthread_self函数获取到自身的线程ID进行打印

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d TID:0x%x\n", msg,getpid(), pthread_self());
		sleep(1);
	}
}
int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1, NULL, pthreadrun, (void*)"thread 1");
    printf("thread 1 tid is 0x%x\n", tid1);
	pthread_create(&tid2, NULL, pthreadrun, (void*)"thread 2");
    printf("thread 2 tid is 0x%x\n", tid2);
	while (1){
		printf("I am main thread!, PID:%d TID:0x%x\n",getpid(), pthread_self());
		sleep(2);
	}
	return 0;
}

结果:这两种方式获取到的线程的ID是一样的

[Jungle@VM-20-8-centos:~/lesson33]$ ./main
thread 1 tid is 0x356c1700
thread 2 tid is 0x34ec0700
I am main thread!, PID:17699 TID:0x36754740
I am thread 2, PID:17699 TID:0x34ec0700
I am thread 1, PID:17699 TID:0x356c1700
I am thread 2, PID:17699 TID:0x34ec0700
I am thread 1, PID:17699 TID:0x356c1700

🏆4.线程ID及进程地址空间布局

  • 对于刚刚说的pthread_create函数产生的线程ID和LWP中的内核轻量级线程ID是不一样的
  • 内核中的LWP属于进程调度的范畴,用来标识轻量级进程
  • pthread_create函数第一个参数是一个地址,指向一个虚拟内存单元,这个地址就是线程的ID,这个ID属于NPTL线程库的范畴,线程库的后续操作就是根据该线程ID来操作线程的。
  • 线程库NPTL提供的pthread_self函数,获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。
  • pthread_t的类型是线程ID,本质是进程地址空间的一个地址
  • 线程没有独立的虚拟地址空间,一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的栈 (stack),堆 (heap)是共用的

在这里插入图片描述

  • 进程运行时动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时该进程内的所有线程都是能看到这个动态库的
  • 线程ID都代表的是每一个线程控制块的起始地址,每个线程都有自己的struct pthread,所有的线程可以看成是一个大的数组,被描述组织起来
  • 每一个线程都有自己的栈,主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是在共享区中开辟的
  • 上面所用的各种线程函数,本质都是在库内部对线程属性进行的各种操作,线程数据的管理本质是在共享区的

🏆5.线程终止

想要终止某个线程而不是整个进程有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit

 void pthread_exit(void *retval); 

功能: 线程终止

参数:

  • retval:不能指向局部变量
  • 该函数无返回值,跟进程一样,线程结束的时候无法返回它的调用者(自身)。
  • pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。

实例:三秒后,使用pthread_exit函数终止线程

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
    int count = 0;
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d TID:0x%x\n", msg,getpid(), pthread_self());
		sleep(1);
        if (++count == 3){
        pthread_exit(NULL);
    }
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, pthreadrun, (void*)"thread 1");
	while (1){
		printf("I am main thread!, PID:%d TID:0x%x\n",getpid(), pthread_self());
		sleep(1);
	}
	return 0;
}

结果:线程被终止退出了

[Jungle@VM-20-8-centos:~/lesson33]$ ./main
I am main thread!, PID:20971 TID:0x69c48740
I am thread 1, PID:20971 TID:0x68bb5700
I am thread 1, PID:20971 TID:0x68bb5700
I am main thread!, PID:20971 TID:0x69c48740
I am thread 1, PID:20971 TID:0x68bb5700
I am main thread!, PID:20971 TID:0x69c48740
I am main thread!, PID:20971 TID:0x69c48740
I am main thread!, PID:20971 TID:0x69c48740

pthread_cancel

 int pthread_cancel(pthread_t thread);

功能: 取消一个线程

参数:

  • thread:线程ID

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

实例:三秒后,使用pthread_cancel函数终止线程

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d TID:0x%x\n", msg,getpid(), pthread_self());
		sleep(1);
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, pthreadrun, (void*)"thread 1");
    int count = 0;
	while (1){
		printf("I am main thread!, PID:%d TID:0x%x\n",getpid(), pthread_self());
		sleep(1);
        if (++count == 3){
            pthread_cancel(tid);
            printf("new thread is canceled...\n");
        }
	}
	return 0;
}

结果:线程被终止退出了

[Jungle@VM-20-8-centos:~/lesson33]$ ./main
I am main thread!, PID:22472 TID:0xb4f87740
I am thread 1, PID:22472 TID:0xb3ef4700
I am main thread!, PID:22472 TID:0xb4f87740
I am thread 1, PID:22472 TID:0xb3ef4700
I am main thread!, PID:22472 TID:0xb4f87740
I am thread 1, PID:22472 TID:0xb3ef4700
new thread is canceled...
I am main thread!, PID:22472 TID:0xb4f87740
I am main thread!, PID:22472 TID:0xb4f87740

🏆6.线程等待

等待原因:

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

pthread_join

 int pthread_join(pthread_t thread, void **retval);

功能: 等待一个线程结束

参数:

  • thread:线程ID
  • retval:输出型参数,指向线程退出的返回值

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

实例:主函数使用pthread_join函数等待另一个线程,三秒后,线程终止并返回6666

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
    int count = 0;
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d TID:0x%x\n", msg,getpid(), pthread_self());
		sleep(1);
        if (++count == 3){
            pthread_exit((void*)6666);
        }
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, pthreadrun, (void*)"thread 1");
	printf("main thread is waiting new thread\n");
    void* ret = NULL;
    pthread_join(tid, &ret);
    printf("new thread has exited, exit code is %ld\n", (long)ret);
	return 0;
}

结果:线程终止并返回退出码

[Jungle@VM-20-8-centos:~/lesson33]$ ./main
main thread is waiting new thread
I am thread 1, PID:23260 TID:0x7e815700
I am thread 1, PID:23260 TID:0x7e815700
I am thread 1, PID:23260 TID:0x7e815700
new thread has exited, exit code is 6666

注意: pthread_join函数默认是以阻塞的方式进行线程等待的,只能获取到线程正常退出时的退出码,用于判断线程的运行结果是否正确。

总结:

  • 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  • 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED(-1)。
  • 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  • 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

🏆7.线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
 int pthread_detach(pthread_t thread);

功能: 对一个线程进行分离

参数:

  • thread:线程ID

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

实例:创建一个个新线程后让这个新线程将自己进行分离,那么此后主线程就不需要在对这这个新线程进行join了

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthreadrun(void* arg)
{
    int count = 0;
	pthread_detach(pthread_self());
	char* msg = (char*)arg;
	while (1){
		printf("I am %s, PID:%d TID:0x%x\n", msg,getpid(), pthread_self());
		sleep(1);
        if (++count == 3){
            pthread_exit(NULL);
        }
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid, NULL, pthreadrun, (void*)"thread 1");
	sleep(1);
    void* ret = NULL;
    pthread_join(tid, &ret);
	if(pthread_join(tid, &ret) == 0){
		printf("success new thread has exited, exit code is %ld\n", (long)ret);
	}else{
		printf("failed\n");
	}
	return 0;
}

结果:这个新线程在退出时,系统会自动回收对应线程的资源,不需要主线程进行join

[Jungle@VM-20-8-centos:~/lesson33]$ ./main
I am thread 1, PID:24725 TID:0xce554700
failed

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

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

相关文章

UNet - 训练数据train

目录 1. train 训练数据 2. Loss 值 3. 完整代码 1. train 训练数据 训练的代码只是在之前图像分类的基础上做了一些更改&#xff0c;具体的可以看下面的文章 pytorch 搭建 LeNet 网络对 CIFAR-10 图片分类https://blog.csdn.net/qq_44886601/article/details/127498256 …

EventBridge 生态实践:融合 SLS 构建一体化日志服务

作者&#xff1a; 昶风 引言 阿里云日志服务 SLS 是一款优秀的日志服务产品&#xff0c;提供一站式地数据采集、加工、查询与分析、可视化、告警、消费与投递等服务。对于使用 SLS 的用户业务而言&#xff0c;SLS 上存储的日志信息反映着业务的运行状态&#xff0c;通过适当地…

2021年认证杯SPSSPRO杯数学建模D题(第一阶段)停车的策略全过程文档及程序

2021年认证杯SPSSPRO杯数学建模 D题 停车的策略 原题再现&#xff1a; 开车前往人流集中的目的地时&#xff0c;决定在何处停车经常是一个难题。是停在距离目的地较远的地方&#xff0c;因为那里的空余车位可能较多&#xff0c;然后再走很远的路&#xff1f;或者是否应该乐观…

【C语言】程序的翻译环境和执行环境

&#x1f6a9;write in front&#x1f6a9; &#x1f50e;大家好&#xff0c;我是謓泽&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f3c5;2021年度博客之星物联网与嵌入式开发TOP5&#xff5…

智慧博物馆解决方案-最新全套文件

智慧博物馆解决方案-最新全套文件一、建设背景二、思路架构三、解决方案建成5个方面1、集约化2、物联网接入3、大数据可视化分析4、室内室外地图集成5、可视化信息多元交互四、获取 - 智慧博物馆全套最新解决方案合集一、建设背景 博物馆是征集、典藏、陈列和研究代表自然和人…

【FME实战教程】002:FME完美实现CAD数据转shp案例教程(以三调土地利用现状数据为例)

FME完美实现CAD数据转shp案例教程&#xff08;以三调土地利用数据为例&#xff09; 文章目录1. cad数据预览2. 转换过程3. shp数据预览1. cad数据预览 2. 转换过程 &#xff08;1&#xff09;打开FME Desktop2020中文软件&#xff0c;点击【新建】。 &#xff08;2&#xff09…

【Spring】——2、使用@ComponentScan自动扫描组件并指定扫描规则

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

微信小程序开发(九):使用扩展组件库

前端开发中离不开各种组件库&#xff0c;我最先接触的组件库还是Bootstrap&#xff0c;后来工作中又陆续使用了inoic、ng-zorro等各种不同的库。 在微信小程序开发中也有多种组件库&#xff0c;这里记录其中几种不同组件库的使用方法。 WeUI 这是微信官方推出的一款和微信原…

使用Python,Open3D对点云散点投影到面上并可视化,使用3种方法计算面的法向量及与平均法向量的夹角

使用Python&#xff0c;Open3D对点云散点投影到面上并可视化&#xff0c;使用3种方法计算面的法向量及与平均法向量的夹角 写这篇博客源于博友的提问&#xff0c;他坚定了我继续坚持学习的心&#xff0c;带给了我充实与快乐。 将介绍以下5部分&#xff1a; 随机生成点云点投影…

LaTeX学习笔记

LaTeX学习笔记 文章目录LaTeX学习笔记1. 开始的尝试2.文档类与宏包3.标题与章节4.标注5.列表6.对齐7.插入代码块8.绘制表格9.插入图片10.数学公式10.1.基础公式10.2.复杂公式10.3 常用符号11.参考文献冲鸭&#xff01;&#xff01;&#xff01; 1. 开始的尝试 先开始试一下一个…

MySQL数据库索引和事务详解

目录 前言&#xff1a; 索引 查看索引 创建索引 删除索引 索引使用 底层数据结构分析 事务 事务引出 MySQL设计事务 事务四大特性 小结&#xff1a; 前言&#xff1a; 数据库索引和事务的存在&#xff0c;对于数据库的一些性能有了显著提升。我们需掌握其底层的实现…

NUMA那些事儿

NUMA——Non Uniform Memory Access&#xff0c;中文为非统一内存访问&#xff0c;在NUMA出现之前&#xff0c;内存的控制器是包含在北桥芯片中的&#xff0c;所有内存由北桥统一管理&#xff0c;因此可以保证访问内存的一致性。随着CPU架构的不断迭代和演进&#xff0c;核数越…

Elasticsearch与Kibana安装

现有环境 windows docker ubuntu Elasticsearch安装 安装包下载 ES不同平台、版本下载路径&#xff1a;Download Elasticsearch | Elastic 本文演示用linux # 启动ubuntu环境&#xff0c;开放端口9200、9300、5601 docker run -name es -p 9200:9200 -p 9300:9300 -p 5…

指夹式血氧饱和检测仪方案分析

指夹式心率血氧饱和度方案的测量原理是根据血红蛋白(Hb)和氧合血红蛋白 (HbO2)在红光和近红光区域的吸收光谱特性为依据&#xff0c;运用Lambert Beer定律建立数据处理经验公式&#xff0c;采用光电血氧检测技术结合光电容积脉搏波描记&#xff08;PPG&#xff09;技术&#xf…

化工制造行业数字化升级案例—基于HK-Domo商业智能分析工具

案例背景导读 世伟洛克&#xff08;Swagelok&#xff09;是全球领先的流体系统解决方案的开发商和制造商&#xff0c;为包括科研、仪表、制药、油气、电力、石化、代用燃料和半导体等在内的各个行业提供产品、组装和服务。世伟洛克通过独立的销售和服务中心网站进行运营&#x…

使用 Typescript 封装 Axios

对 axios 二次封装,更加的可配置化、扩展性更加强大灵活 通过 class 类实现&#xff0c;class 具备更强封装性(封装、继承、多态)&#xff0c;通过实例化类传入自定义的配置 创建 class 严格要求实例化时传入的配置&#xff0c;拥有更好的代码提示 /*** param {AxiosInstance…

C语言习题练习8--二进制操作符

IO型--从main函数开始写&#xff0c;要写输入、计算、输出 接口型--不需要写主函数&#xff0c;默认主函数是存在的&#xff0c;你只需要完成函数就行 一、二进制中1的个数 (12条消息) C语言丨关键字signed和unsigned 的使用与区别详解_Emily-C的博客-CSDN博客_signed unsi…

【笔记】samba shell 脚本 离线安装 - Ubuntu 20.04

前言 按照官网调试代码、网上各种步骤来走&#xff08;还收费&#xff09;都不行 结果发现是防火墙问题 公司服务器安装的ufw使用失效&#xff0c;导致端口号放行添加失败 换用firewall-cmd成功 现在免费放下代码&#xff0c;气死他们收费的 目录 ├── home│ ├── k…

linux备份mysql8.0数据库脚本

文章目录环境要求步骤1、创建一个.sh文件编写shell脚本2、添加定时任务环境要求 linux系统&#xff0c;安装了mysql8.0 步骤 1、创建一个.sh文件编写shell脚本 创建文件的命令&#xff1a; vim ***.shshell文件文件参考自文章 链接 export LANGen_US.UTF-8 #注意&#xf…

测试开发技术:Python测试框架Pytest的基础入门

Pytest简介 Pytest is a mature full-featured Python testing tool that helps you write better programs.The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. 通过官方网站介绍…