【Linux C | 多线程编程】线程的创建、线程ID、线程属性

news2024/12/27 13:31:39

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-03-22 09:05:41

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、线程的创建 pthread_create
  • 🎄三、线程ID
    • ✨2.1 线程ID相关函数
    • ✨2.2 线程ID复用
  • 🎄四、线程属性
  • 🎄五、总结


在这里插入图片描述

🎄一、概述

Linux线程库接口包括线程的创建、 退出、 取消和分离, 以及连接已经终止的线程, 互斥量, 读写锁,线程的条件等待等。

POSIX 函数函数功能描述
pthread_create创建一个线程
pthread_exit退出线程
pthread_self获取线程ID
pthread_equal检查两个线程ID是否相等
pthread_join等待线程退出
pthread_detach设置线程状态为分离状态
pthread_cancel线程的取消
pthread_cleanup_push、pthread_cleanup_pop线程退出,清理函数注册和执行

在代码里使用到上述接口函数时,使用gcc编程过程中需要加-pthread选项。

本文将介绍线程创建相关的一些知识,从pthread_create开始,然后依次介绍该函数第一个参数相关的线程ID,以及第二个函数相关的线程属性。


在这里插入图片描述

🎄二、线程的创建 pthread_create

程序开始启动的时候, 产生的进程只有一个线程, 我们称之为主线程或初始线程。 对于单线程的进程而言, 只存在主线程一个线程。 如果想在主线程之外, 再创建一个或多个线程, 就需要 pthread_create 函数。

pthread_create 函数原型:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
   					void *(*start_routine) (void *), void *arg);

Compile and link with -pthread.
  • 函数描述:函数 pthread_create 在调用过程中启动一个新线程。新线程通过调用 start_routine 参数指向的函数开始执行;arg参数作为 start_routine 的唯一参数传递。
  • 函数参数:
    • thread:传出参数,thread参数是 pthread_t 类型的指针,线程创建成功的话,会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。
    • attr:传入参数,第二个参数是 pthread_attr_t 类型,通过该参数可以定制线程的属性,比如可以指定新建线程栈的大小、调度策略等。 如果创建线程无特殊的要求, 该值也可以是NULL, 表示采用默认属性。
    • start_routine:传入参数,第三个参数是线程需要执行的函数。创建线程,是为了让线程执行一定的任务。线程创建成功之后,该线程就会执行start_routine函数,该函数之于线程,就如同 main 函数之于主线程。
    • arg:第四个参数是新建线程执行的 start_routine 函数的入参。新建线程如果想要正常工作,则可能需要入参,那么主线程在调用 pthread_create 的时候,就可以将入参的指针放入第四个参数以传递给新建线程。如果多个入参,可以使用结构体指针。
  • 函数返回值:如果成功,则 pthread_create 返回0;如果不成功,则 pthread_create 返回一个非0的错误码。pthread_create函数有点不同, 它会将errno作为返回值, 而不是一个负值。
    • EAGAIN:系统资源不够,或者创建线程的个数超过系统对一个进程中线程总数的限制
    • EINVAL:第二个参数attr值不合法
    • EPERM:没有合适的权限来设置调度策略或参数

通过上面的描述可以看到,pthread_create 是一个"四针"函数,也就是说它四个参数都是指针。下面是这个函数的简单使用示例:

// 02_pthread_create
// 编译:gcc 02_pthread_create.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func(void *arg)
{
	int *parg = arg;
	printf("this thread arg is %d \n", *parg);
	return NULL;
}
int main()
{
	int arg=10;
	pthread_t threadId;
	pthread_create(&threadId, NULL, func, &arg);
	while(1); // 让主线程不退出
	return 0;
}

在这里插入图片描述

🎄三、线程ID

通过 pthread_create 成功创建线程后,第一个参数会返回所创建线程的线程ID,这个线程ID不同于使用系统调用函数syscall(SYS_gettid)获得的线程ID。syscall(SYS_gettid)的ID是进程调度的范畴;而这里返回的线程ID是操作系统调度器用来标识线程的。

pthread_t到底是个什么样的数据结构呢? 因为POSIX标准并没有限制pthread_t的数据类型, 所以该类型取决于具体实现。 对于Linux目前使用的NPTL实现而言, pthread_t类型的线程ID, 本质就是一个进程地址空间上的一个地址。

typedef unsigned long int pthread_t;

pthread_t类型在Linux系统中定义在 <bits/pthreadtypes.h>头文件中,在Ubuntu可以使用命令vi /usr/include/bits/pthreadtypes.h 来查看,其完整定义如上,是unsigned long int类型的。

✨2.1 线程ID相关函数

关于线程ID,线程库NPTL提供了pthread_selfpthread_equal 两个函数来操作线程ID,它们的函数原型如下:

#include <pthread.h>
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);

Compile and link with -pthread.

pthread_self函数用于在线程指向函数中获取自身线程ID,这个函数不会调用失败,返回值就是线程ID;

pthread_equal函数用于比较两个线程ID是否相等,返回值是0的时候, 表示两个线程是同一个线程, 非零值则表示不是同一个线程。注意,比较线程ID只有在同一个进程中才有意义。

🌰举例子:

// 03_pthreadID.c
// gcc 03_pthreadID.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func1(void *arg)
{
	int *parg = arg;
	printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());
	while(1); // 让线程不退出
}
void *func2(void *arg)
{
	pthread_t *parg = arg;
	printf("other threadId is %lx, my threadID is %lx \n", (unsigned long)*parg, (unsigned long)pthread_self());
	while(1); // 让线程不退出
}
int main()
{
	int arg=10;
	pthread_t threadId_1;
	pthread_create(&threadId_1, NULL, func1, &arg);
	pthread_t threadId_2;
	pthread_create(&threadId_2, NULL, func2, &threadId_1);
	if(0 == pthread_equal(threadId_1,threadId_1))
		printf("same threads\n");
	else
		printf("different threads\n");
	while(1); // 让主线程不退出
	return 0;
}

✨2.2 线程ID复用

在满足下列条件时, 线程ID就有可能会被复用:
1) 线程退出。
2) 线程组的其他线程对该线程执行了pthread_join, 或者线程退出前将分离状态设置为已分离。
3) 再次调用pthread_create创建线程。

看例子:

// 04_pthreadID_reuse.c
// gcc 04_pthreadID_reuse.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func(void *arg)
{
	int *parg = arg;
	printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());
	return NULL;
}

int main()
{
	int arg=10;
	pthread_t threadId;
	pthread_create(&threadId, NULL, func, &arg);
	pthread_join(threadId,NULL); // 等待线程退出
	pthread_create(&threadId, NULL, func, &arg);

	while(1); // 让主线程不退出
	return 0;
}

运行结果,可以看到两次的线程ID是一样的:
在这里插入图片描述


在这里插入图片描述

🎄四、线程属性

pthread_create 的第二个参数是线程属性,先看看 pthread_attr_t 结构体的定义:

typedef struct
{
    int						detachstate;		//线程的分离状态
    int						schedpolicy;		//线程调度策略
    struct sched_param		schedparam;			//线程的调度参数
    int						inheritsched;		//线程的继承性
    int						scope;				//线程的作用域(竞争范围)
    size_t					guardsize;			//线程栈末尾的警戒缓冲区大小
    int						stackaddr_set;		//线程的栈设置
    void *                  stackaddr;			//线程栈的位置
    size_t					stacksize;			//线程栈的大小
}pthread_attr_t;


int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);   
功能:初始化/销毁线程的属性结构体
  • detachstate:分离状态

    • PTHREAD_CREATE_JOINABLE(默认值):线程执行完函数后不会自行释放资源;
    • PTHREAD_CANCEL_DEFERRED:线程执行完函数后,会自行终止并释放占用的资源。

    系统提供两个函数获取、设置分离状态。另外,pthread_detach函数也可以设置线程分离。

    int pthread_attr_getdetachstate(const pthread_attr_t * attr,int * detachstate);
    int pthread_attr_setdetachstate(pthread_attr_t *sttr,int detachstate);
    
  • schedpolicy:调度策略

    • SCHED_OTHER(默认值):普通策略(分时调度算法),按照优先级调度
    • SCHED_FIFO:先进先出。一个FIFO会持续执行,直到线程阻塞、结束、有更高优先级的线程就绪
    • SCHED_RR:轮转策略。给每个线程分配执行时间(时间片),当一个线程的时间片耗尽时,下一个线程执行

    其中,SCHED_OTHER 调度算法不支持为线程设置优先级,而另外两种调度算法支持。获取、设置的函数如下:

    int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
    int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
    
  • schedparam:调度参数
    用于设置线程的优先级(默认值为 0),该属性仅当线程的 schedpolicy 属性为 SCHED_FIFO 或者 SCHED_RR 时才能发挥作用。

    int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
    int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
    struct sched_param param; // sched_param 只有一个字段 sched_priority 
    param.sched_priority = 99;
    
  • inheritsched:继承性

    • PTHREAD_INHERIT_SCHED 调度属性(schedpolicy、schedparam)继承自创建者的
    • PTHREAD_EXPLICIT_SCHED 使用attr创建的线程,从attr指定的值中获取其调度属性(schedpolicy、schedparam)。

    获取、设置函数如下:

    int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
    int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);
    
  • scope:作用域(竞争范围)

    • PTHREAD_SCOPE_SYSTEM:在系统范围内竞争资源
    • PTHREAD_SCOPE_PROCESS:在进程范围内竞争资源

    线程执行过程中,可以只和同进程内的其它线程争夺 CPU 资源,也可以和系统中所有的其它线程争夺 CPU 资源,scope 属性用于指定目标线程和哪些线程抢夺 CPU 资源。获取、设置函数如下:

    int pthread_attr_setscope(pthread_attr_t *attr, int scope);
    int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
    
  • guardsize:线程栈末尾的警戒缓冲区大小
    每个线程中,栈内存的后面都紧挨着一块空闲的内存空间,我们通常称这块内存为警戒缓冲区,它的功能是:一旦我们使用的栈空间超出了额定值,警戒缓冲区可以确保线程不会因“栈溢出”立刻执行崩溃。

    int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
    int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
    
  • stackaddr_set:线程的栈设置

    int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize);
    int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize);
    
  • stackaddr:线程栈的位置

    int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
    int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);
    
  • stacksize:线程栈的大小

    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
    

🌰举例子:

// 05_dispaly_attr.c  这是man手册的一个展示线程属性的例子,可以仔细研究以下
// gcc 05_dispaly_attr.c -l pthread
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define handle_error_en(en, msg) \
	   do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

static void
display_pthread_attr(pthread_attr_t *attr, char *prefix)
{
   int s, i;
   size_t v;
   void *stkaddr;
   struct sched_param sp;

   s = pthread_attr_getdetachstate(attr, &i);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getdetachstate");
   printf("%sDetach state        = %s\n", prefix,
		   (i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
		   (i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
		   "???");

   s = pthread_attr_getscope(attr, &i);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getscope");
   printf("%sScope               = %s\n", prefix,
		   (i == PTHREAD_SCOPE_SYSTEM)  ? "PTHREAD_SCOPE_SYSTEM" :
		   (i == PTHREAD_SCOPE_PROCESS) ? "PTHREAD_SCOPE_PROCESS" :
		   "???");

   s = pthread_attr_getinheritsched(attr, &i);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getinheritsched");
   printf("%sInherit scheduler   = %s\n", prefix,
		   (i == PTHREAD_INHERIT_SCHED)  ? "PTHREAD_INHERIT_SCHED" :
		   (i == PTHREAD_EXPLICIT_SCHED) ? "PTHREAD_EXPLICIT_SCHED" :
		   "???");

   s = pthread_attr_getschedpolicy(attr, &i);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getschedpolicy");
   printf("%sScheduling policy   = %s\n", prefix,
		   (i == SCHED_OTHER) ? "SCHED_OTHER" :
		   (i == SCHED_FIFO)  ? "SCHED_FIFO" :
		   (i == SCHED_RR)    ? "SCHED_RR" :
		   "???");

   s = pthread_attr_getschedparam(attr, &sp);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getschedparam");
   printf("%sScheduling priority = %d\n", prefix, sp.sched_priority);

   s = pthread_attr_getguardsize(attr, &v);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getguardsize");
   printf("%sGuard size          = %ld bytes\n", prefix, v);

   s = pthread_attr_getstack(attr, &stkaddr, &v);
   if (s != 0)
	   handle_error_en(s, "pthread_attr_getstack");
   printf("%sStack address       = %p\n", prefix, stkaddr);
   printf("%sStack size          = 0x%lx bytes\n", prefix, v);
}

static void *
thread_start(void *arg)
{
   int s;
   pthread_attr_t gattr;

   /* pthread_getattr_np() is a non-standard GNU extension that
	  retrieves the attributes of the thread specified in its
	  first argument */

   s = pthread_getattr_np(pthread_self(), &gattr);
   if (s != 0)
	   handle_error_en(s, "pthread_getattr_np");

   printf("Thread attributes:\n");
   display_pthread_attr(&gattr, "\t");

   exit(EXIT_SUCCESS);         /* Terminate all threads */
}

int main(int argc, char *argv[])
{
   pthread_t thr;
   pthread_attr_t attr;
   pthread_attr_t *attrp;      /* NULL or &attr */
   int s;

   attrp = NULL;

   /* If a command-line argument was supplied, use it to set the
	  stack-size attribute and set a few other thread attributes,
	  and set attrp pointing to thread attributes object */

   if (argc > 1) {
	   int stack_size;
	   void *sp;

	   attrp = &attr;

	   s = pthread_attr_init(&attr);
	   if (s != 0)
		   handle_error_en(s, "pthread_attr_init");

	   s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	   if (s != 0)
		   handle_error_en(s, "pthread_attr_setdetachstate");

	   s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
	   if (s != 0)
		   handle_error_en(s, "pthread_attr_setinheritsched");

	   stack_size = strtoul(argv[1], NULL, 0);

	   s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size);
	   if (s != 0)
		   handle_error_en(s, "posix_memalign");

	   printf("posix_memalign() allocated at %p\n", sp);

	   s = pthread_attr_setstack(&attr, sp, stack_size);
	   if (s != 0)
		   handle_error_en(s, "pthread_attr_setstack");
   }

   s = pthread_create(&thr, attrp, &thread_start, NULL);
   if (s != 0)
	   handle_error_en(s, "pthread_create");

   if (attrp != NULL) {
	   s = pthread_attr_destroy(attrp);
	   if (s != 0)
		   handle_error_en(s, "pthread_attr_destroy");
   }

   pause();    /* Terminates when other thread calls exit() */
}

运行结果,打印一些默认值:
在这里插入图片描述


在这里插入图片描述

🎄五、总结

本文介绍了线程创建相关的内容,包括pthread_create函数的详细介绍和使用例子,然后依次介绍该函数第一个参数相关的线程ID知识以及第二个参数相关的线程属性知识。读完完整地了解线程的创建。

补充:
进程的地址空间:
1、Linux系统中,/proc/sys/vm/legacy_va_layout文件的值会影响进程地址空间的布局。默认值是0,表示mmap区域的基地址在栈的下面, mmap区域从高地址向低地址扩展;若值为1, 那么mmap的基地址mmap_base变小(约在128T的三分之一处),mmap区域从低地址向高地址扩展。

2、使用命令 pmap PIDcat /proc/PID/maps 可以查看进程的地址空间:
在这里插入图片描述

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考资料:
https://blog.csdn.net/qq_41854911/article/details/118719001
《Linux环境编程:从应用到内核》

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

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

相关文章

LiDAR 点云数据综合指南

LiDAR 是光探测和测距的缩写,它彻底改变了各个领域的数据采集。它生成密集 3D 点云的能力为我们的世界提供了无与伦比的洞察力。 但如此丰富的信息也带来了复杂性,特别是在理解不同类型的激光雷达点云数据时。本指南旨在成为您的一站式资源,阐明其中的细微差别,并使您能够…

python基础——对序列的通用操作【+和*、in、切片操作、separator.join(iterable)】

&#x1f4dd;前言&#xff1a; 我们已经学习了python数据容器中的列表&#xff0c;元组以及字符串。而他们都属于序列 &#xff08;序列是指&#xff1a;内容连续&#xff0c;有序&#xff0c;可以用下标索引访问的数据容器&#xff09; 在之前已经介绍了不少操作方法&#xf…

工作需求,Vue实现登录

加油&#xff0c;新时代打工人&#xff01; vue 2.x Element UI <template><div class"body" :style"{background-image: url(${require(/assets/images/login.png)})}"><el-form :rules"rules" ref"loginForm" :mode…

线性表:关于链表(主要以单链表为例)的相关理解和应用

多清澈这天空 晴雨相拥 同心逐梦&#xff01; 坚守我信心 一路出众&#xff01;&#xff01; 首先&#xff0c;按照惯例&#xff0c;欢迎大家边听歌边观看本博客 ▶ 紫荆花盛开 (163.com)&#xff08;建议复制链接&#xff0c;浏览器打开&#xff0c;csdn打开太慢了&#x…

mysql数据类型和常用函数

目录 1.整型 1.1参数signed和unsigned 1.2参数zerofill 1.3参数auto_increment 2.数字类型 2.1floor()向下取整 2.2随机函数rand() 2.3重复函数repeat() 3.字符串类型 3.1length()查看字节长度&#xff0c;char_length()查看字符长度 3.2字符集 3.2.1查看默认字符…

工程信号的去噪和(分类、回归和时序)预测

&#x1f680;【信号去噪及预测论文代码指导】&#x1f680; 还为小论文没有思路烦恼么&#xff1f;本人专注于最前沿的信号处理与预测技术——基于信号模态分解的去噪算法和深度学习的信号&#xff08;回归、时序和分类&#xff09;预测算法&#xff0c;致力于为您提供最精确、…

MySql实战--深入浅出索引(下)

在开始这篇文章之前&#xff0c;我们先来看一下这个问题&#xff1a; 在下面这个表T中&#xff0c;如果我执行 select * from T where k between 3 and 5&#xff0c;需要执行几次树的搜索操作&#xff0c;会扫描多少行&#xff1f; 下面是这个表的初始化语句。 图1 InnoDB的索…

第 6 章 ROS-xacro练习(自学二刷笔记)

重要参考&#xff1a; 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 6.4.3 Xacro_完整使用流程示例 需求描述: 使用 Xacro 优化 URDF 版的小车底盘模型实现 结果演示: 1.编写 X…

ChatGPT已经掌控了全局:不仅写论文的在用ChatGPT,同行评审也在用ChatGPT!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

分库分表场景下多维查询解决方案(用户+商户)

在采用分库分表设计时&#xff0c;通过一个PartitionKey根据散列策略将数据分散到不同的库表中&#xff0c;从而有效降低海量数据下C端访问数据库的压力。这种方式可以缓解单一数据库的压力&#xff0c;提升了吞吐量&#xff0c;但同时也带来了新的问题。对于B端商户而言&#…

【Python爬虫】网络爬虫:信息获取与合规应用

这里写目录标题 前言网络爬虫的工作原理网络爬虫的应用领域网络爬虫的技术挑战网络爬虫的伦理问题结语福利 前言 网络爬虫&#xff0c;又称网络爬虫、网络蜘蛛、网络机器人等&#xff0c;是一种按照一定的规则自动地获取万维网信息的程序或者脚本。它可以根据一定的策略自动地浏…

linux查看usb是3.0还是2.0

1 作为device cat /sys/devices/platform/10320000.usb30drd/10320000.dwc3/udc/10320000.dwc3/current_speed 如下 high-speed usb2.0 super-speed usb3.0 2 作为host linux下使用以下命令查看 &#xff0c;如果显示 速率为5G, 则为USB 3.0&#xff0c; USB2.0通常显示速率…

Day17|二叉树part04:110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和、543: 二叉树的直径、124: 二叉树的最大路径和

之前的blog链接&#xff1a;https://blog.csdn.net/weixin_43303286/article/details/131982632?spm1001.2014.3001.5501 110.平衡二叉树 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。思路&#xff…

Matlab之已知2点绘制长度可定义的射线

目的&#xff1a;在笛卡尔坐标系中&#xff0c;已知两个点的位置&#xff0c;绘制过这两点的射线。同时射线的长度可以自定义。 一、函数的参数说明 输入参数&#xff1a; PointA&#xff1a;射线的起点&#xff1b; PointB&#xff1a;射线过的零一点&#xff1b; Length&…

AI PPT生成工具 V1.0.0

AI PPT是一款高效快速的PPT生成工具&#xff0c;能够一键生成符合相关主题的PPT文件&#xff0c;大大提高工作效率。生成的PPT内容专业、细致、实用。 软件特点 免费无广告&#xff0c;简单易用&#xff0c;快速高效&#xff0c;提高工作效率 一键生成相关主题的标题、大纲、…

【链表】Leetcode 138. 随机链表的复制【中等】

随机链表的复制 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点…

Linux - 应用层HTTPS、传输层TCP/IP模型中典型协议解析

目录 应用层&#xff1a;自定制协议实例 HTTP协议首行头部空行正文http服务器的搭建 HTTPS协议 传输层UDP协议TCP协议 应用层&#xff1a; 应用层负责应用程序之间的沟通—程序员自己定义数据的组织格式 应用层协议&#xff1a;如何将多个数据对象组织成为一个二进制数据串进行…

代码签名证书被吊销的原因及其后果是什么?

代码签名证书是确保软件代码完整性和可信度的关键工具&#xff0c;然而&#xff0c;在某些情况下&#xff0c;此类证书可能会被撤销。这意味着证书颁发机构&#xff08;CA&#xff09;不再认可该证书的有效性&#xff0c;并宣布其失效。本文将解析导致代码签名证书撤销的原因、…

Bytebase 2.14.1 - 分支 (Branching) 功能支持 Oracle

&#x1f680; 新功能 分支 (Branching) 功能支持 Oracle。为 SQL 编辑器添加了项目选择器。 新增 SQL 审核规范&#xff1a; 禁止混合 DDL、DML 语句。禁止对同一张表进行不同类型的 DML 变更 (UPDATE,INSERT,DELETE)。 &#x1f514; 重大变更 工作空间设置中的「数据访问…

puppeteer使用示例云顶之弈官网

自己从0到1开发的&#xff0c;微信小程序【云顶宝藏】求求点个5星好评吧&#xff01; 需求&#xff1a;拿到所有英雄的信息 思路&#xff1a;点击每个英雄&#xff0c;进入英雄详情页&#xff0c;拿信息&#xff0c;并返回&#xff0c;继续下一个英雄** 最终效果 本地环境 win…