Linux-线程知识点

目录

  • 线程与进程区别
  • pthread库接口介绍
  • pthread_create
  • pthread_self和syscall(SYS_gettid);
  • pthread_equal
  • 测试主线程的栈空间大概是多大
  • pthread_setname_np
  • pthread_exit
  • pthread_join
    • 为什么要连接退出的线程
  • pthread_detach

线程与进程区别

进程是一个动态的实体,有自己的生命周期。线程是操作系统进程调度器可以调度的最小执行单位。

一个进程可能包含多个线程。

进程之间,彼此的地址空间是独立的,但同一个进程的多个线程共享内存地址空间,即全局内存区域,包括初始化数据段、未初始化数据段和动态分配的内存段。

这种共享给线程带来了更多的优势:

  • 创建线程花费的时间要比创建进程花费的时间少
  • 终止线程花费的时间要比终止进程花费的时间少
  • 线程之间上下文切换的开销要比进程之间上下文切换的开销小
  • 线程之间共享数据要比进程之间共享数据简单

线程共享地址空间的设计,让多个线程之间的通信变得非常简单。一个进程内的多个线程,就像一个软件研发小组内部的不同员工,共享代码、服务器、打印机、资料,彼此之间有分工协作,沟通协作成本比较低。进程之间的通信代价则要高很多。进程之间不得不采用一些进程间通信的手段(如管道、共享内存及信号量等)来协作。

需要强调的一点是,线程和进程不一样,进程有父进程的概念,但在线程组里,所有的线程都是对等的关系:

  • 并不是只有主线程才能创建线程,被创建出来的线程同样可以创建线程。
  • 不存在类似于fork函数那样的父子关系,大家都归属于同一个线程组,进程ID都相等,而且各有各的线程ID。
  • 并非只有主线程才能调用pthread_join连接其他线程,同一线程组内的任意线程都可以对某线程执行pthread_join函数。
  • 并非只有主线程才能调用pthread_detach函数,其实任意线程都可以对同一线程组内的线程执行分离操作。

pthread库接口介绍

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

pthread_create

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

函数接口如下:

#include <pthread.h>
int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void*),void *restrict arg);
  • pthread_create函数的第一个参数是pthread_t类型的指针,线程创建成功的话,会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。
  • 第二个参数是pthread_attr_t类型,通过该参数可以定制线程的属性,比如可以指定新建线程栈的大小、调度策略等。如果创建线程无特殊的要求,该值也可以是NULL,表示采用默认属性。
  • 第三个参数是线程需要执行的函数。创建线程,是为了让线程执行一定的任务。线程创建成功之后,该线程就会执行start_routine函数,该函数之于线程,就如同main函数之于主线程。
  • 第四个参数是新建线程执行的start_routine函数的入参。

返回值:如果成功,则pthread_create返回0;如果不成功,则pthread_create返回一个非0的错误码。常见的错误码如表:

返回值描述
EAGAIN系统资源不够,或者创建线程的个数超过系统对一个进程中线程总数的限制
EINVAL第二个参数attr不合法
EPERM没有合适的权限来设置调度策略或参数
// pthread_create示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>void* ThreadFunc(void* _threadId)
{std::cout << "线程ID: " << pthread_self() << " hello world" << std::endl;pthread_exit(nullptr);
}int main()
{// 创建五个线程pthread_t tid[5];for (int i = 0;i < 5;i++){int rc = pthread_create(&tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}

pthread_self和syscall(SYS_gettid);

这两个函数都是获取自身的线程ID

 #include <pthread.h>pthread_t pthread_self();// --------------------------------- //int TID = syscall(SYS_gettid);

区别是:

  • syscall(SYS_gettid),属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程。
  • pthread_self(),属于NPTL线程库的范畴,线程库的后续操作,就是根据该线程ID来操作线程的。
// 示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>void* ThreadFunc(void* _threadId)
{int TID = syscall(SYS_gettid);std::cout << "TID: " << TID << std::endl;std::cout << "pthread_self: " << pthread_self() << std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;int rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}// 等待线程退出int res = pthread_join(tid, nullptr);if (res != 0){std::cout << strerror(res) << std::endl;return -1;}std::cout << "线程已经退出" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_detach -lpthread
[root@Zhn 线程]# ./pthread_detach 
TID: 8718
pthread_self: 140005981333248
线程已经退出
[root@Zhn 线程]# 

pthread_equal

在同一个线程组内,线程库提供了接口,可以判断两个线程ID是否对应着同一个线程:

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

返回值是0的时候,表示两个线程是同一个线程,非零值则表示不是同一个线程。

pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

测试主线程的栈空间大概是多大

// 查看一个线程的栈最大占用多少内存空间#include <iostream>
#include <pthread.h>
#include <unistd.h>int i = 0;void func()
{// int类型是4个字节,256个int类型是4 * 256 = 1024,也就是1kB// 每执行一次func函数就会占用1kB的栈空间,看看可以执行多少次这个函数int buffer[256];std::cout << "i = " << i << std::endl;i++;func();
}int main()
{func();sleep(100);
}

在这里插入图片描述

可以执行8053次,每次是1kB,也就是8053 / 1024,大概是8MB。

pthread_setname_np

给线程设置名称

#include <pthread.h>int pthread_setname_np(pthread_t thread, const char *name);
// pthread_setname_np示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>void* ThreadFunc(void* _threadId)
{// 设置线程名称pthread_setname_np(pthread_self(), std::to_string(pthread_self()).c_str());while (1){std::cout << "线程ID: " << pthread_self() << " hello world" << std::endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid[5];for (int i = 0;i < 5;i++){int rc = pthread_create(&tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}

创建了五个线程,给每个线程设置名称为自己线程ID。

在这里插入图片描述

CMD字段就是设置的线程名称。

pthread_exit

有生就有灭,线程执行完任务,也需要终止。

下面的三种方法中,线程会终止,但是进程不会终止(如果线程不是进程组里的最后一个线程的话):

  • 创建线程时的start_routine(线程执行函数)函数执行了return,并且返回指定值。
  • 线程调用pthread_exit。
  • 其他线程调用了pthread_cancel函数取消了该线程。如果线程组中的任何一个线程调用了exit函数,或者主线程在main函数中执行了return语句,那么整个线程组内的所有线程都会终止。

pthread_exit函数的定义:

#include <pthread.h>void pthread_exit(void *value_ptr);

value_ptr是一个指针,存放线程的“临终遗言”。线程组内的其他线程可以通过调用pthread_join函数接收这个地址,从而获取到退出线程的临终遗言。如果线程退出时没有什么遗言,则可以直接传递NULL指针,如下所示:

pthread_exit(NULL);

但是这里有一个问题,就是不能将遗言存放到线程的局部变量里,因为如果用户写的线程函数退出了,线程函数栈上的局部变量可能就不复存在了,线程的临终遗言也就无法被接收者读到。

那我们应该如何正确地传递返回值呢?

  • 如果是int型的变量,则可以使用“pthread_exit((int*)ret);”。
  • 使用全局变量返回。
  • 将返回值填入到用malloc在堆上分配的空间里。·使用字符串常量,如pthread_exit(“hello,world”)。

线程退出有一种比较有意思的场景,即线程组的其他线程仍在执行的情况下,主线程却调用pthread_exit函数退出了。这会发生什么事情?首先要说明的是这不是常规的做法,但是如果真的这样做了,那么主线程将进入僵尸状态,而其他线程则不受影响,会继续执行。

pthread_join

线程库提供了pthread_join函数,用来等待某线程的退出并接收它的返回值。这种操作被称为连接(joining)。

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);

第一个参数表示要等待的线程ID,第二个参数代表线程退出的返回值。

根据等待的线程是否退出,可得到如下两种情况:

  • 等待的线程尚未退出,那么pthread_join的调用线程就会陷入阻塞。
  • 等待的线程已经退出,那么pthread_join函数会将线程的退出值(void*类型)存放到retval指针指向的位置。

线程的连接(join)操作有点类似于进程等待子进程退出的等待(wait)操作,但还是有不同之处:

  • 第一点不同之处是进程之间的等待只能是父进程等待子进程,而线程则不然。线程组内的成员是对等的关系,只要是在一个线程组内,就可以对另外一个线程执行连接(join)操作。
  • 第一点不同之处是进程之间的等待只能是父进程等待子进程,而线程则不然。线程组内的成员是对等的关系,只要是在一个线程组内,就可以对另外一个线程执行连接(join)操作。

返回值:

返回值说明
ESRCH传入的线程ID不存在,查无此线程
EINVAL线程不是一个可连接的线程或者已经有其他线程连接
EDEADLK死锁

pthread_join不能连接线程组内任意线程的做法,并不是NPTL线程库设计上的瑕疵,而是有意为之的。如果听任线程连接线程组内的任意线程,那么所谓的任意线程就会包括其他库函数私自创建的线程,当库函数尝试连接(join)私自创建的线程时,发现已经被连接过了,就会返回EINVAL错误。如果库函数需要根据返回值来确定接下来的流程,这就会引发严重的问题。正确的做法是,连接已知线程ID的那些线程,就像pthread_join函数那样。

pthread_join函数之所以能够判断是否死锁和连接操作是否被其他线程捷足先登,是因为目标线程的控制结构体struct pthread中,存在如下成员变量,记录了该线程的连接者:

struct pthread *joinid;

该指针存在三种可能,如下。

  • NULL:线程是可连接的,但是尚没有其他线程调用pthread_join来连接它。
  • 指向线程自身的struct pthread:表示该线程属于自我了断型,执行过分离操作,或者创建线程时,设置的分离属性为PTHREAD_CREATE_DETACHED,一旦退出,则自动释放所有资源,无需其他线程来连接。
  • 指向线程组内其他线程的struct pthread:表示joinid对应的线程会负责连接。

为什么要连接退出的线程

如果不连接退出的线程,会导致资源无法释放

如果不执行连接操作,线程的资源就不能被释放,也不能被复用,这就造成了资源的泄漏。

值得一提的是,纵然调用了pthread_join,也并没有立即调用munmap来释放掉退出线程的栈,它们是被后建的线程复用了,这是NPTL线程库的设计。释放线程资源的时候,NPTL认为进程可能再次创建线程,而频繁地munmap和mmap会影响性能,所以NTPL将该栈缓存起来,放到一个链表之中,如果有新的创建线程的请求,NPTL会首先在栈缓存链表中寻找空间合适的栈,有的话,直接将该栈分配给新创建的线程。

始终不将线程栈归还给系统也不合适,所有缓存的栈大小有上限,默认是40MB,如果缓存起来的线程栈的空间总和大于40MB,NPTL就会扫描链表中的线程栈,调用munmap将一部分空间归还给系统。

// pthread_join示例#include <iostream>
#include <pthread.h>
#include <unistd.h>#define     Threads     5void* ThreadFunc(void* _args)
{int thread_num = *((int*)_args);printf("Thread %d: Hello, World!\n", thread_num);int* retval = new int;*retval = thread_num * 2;pthread_exit(reinterpret_cast<void*>(retval));
}int main()
{pthread_t tid[Threads];for (int i = 0;i < Threads;i++){int* p = new int;*p = i;int rc = pthread_create(&tid[i], nullptr, ThreadFunc, reinterpret_cast<void*>(p));if (rc != 0){std::cout << "线程" << i << "创建失败" << std::endl;return -1;}}// 等待线程结束并接收返回值for (int i = 0;i < Threads; i++){void* retval;pthread_join(tid[i], &retval);std::cout << "线程" << i << "退出, 返回值是" << *reinterpret_cast<int*>(retval) << std::endl;}std::cout << "线程全部退出" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_join.cpp -o pthread_join -lpthread
[root@Zhn 线程]# ./pthread_join 
Thread 0: Hello, World!
Thread 1: Hello, World!
Thread 3: Hello, World!
Thread 2: Hello, World!
Thread 4: Hello, World!
线程0退出, 返回值是0
线程1退出, 返回值是2
线程2退出, 返回值是4
线程3退出, 返回值是6
线程4退出, 返回值是8
线程全部退出
[root@Zhn 线程]# 

可以看到,程序会阻塞在pthread_join,直到所有线程全部退出。

pthread_detach

默认情况下,新创建的线程处于可连接(Joinable)的状态,可连接状态的线程退出后,需要对其执行连接操作,否则线程资源无法释放,从而造成资源泄漏。

如果其他线程并不关心线程的返回值,那么连接操作就会变成一种负担:你不需要它,但是你不去执行连接操作又会造成资源泄漏。这时候你需要的东西只是:线程退出时,系统自动将线程相关的资源释放掉,无须等待连接。NPTL提供了pthread_detach函数来将线程设置成已分离(detached)的状态,如果线程处于已分离的状态,那么线程退出时,系统将负责回收线程的资源,如下:

#include <pthread.h>int pthread_detach(pthread_t thread);

参数就是要分离的线程ID,可以是线程组内其他线程对目标线程进行分离,也可以是线程自己执行pthread_detach函数,将自身设置成已分离的状态,如下:

pthread_detach(pthread_self())

线程的状态之中,可连接状态和已分离状态是冲突的,一个线程不能既是可连接的,又是已分离的。因此,如果线程处于已分离的状态,其他线程尝试连接线程时,会返回EINVAL错误。

返回值:

返回值说明
ESRCH传入的线程ID不存在,查无此线程
EINVAL线程不是一个可连接的线程,已经处于分离状态
// pthread_detach示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>void* ThreadFunc(void* _threadId)
{int TID = syscall(SYS_gettid);std::cout << "TID: " << TID << std::endl;std::cout << "pthread_self: " << pthread_self() << std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;#ifdef USE_ATTRpthread_attr_t attr;// 将线程状态设置为分离状态pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);int rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}#elseint rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}int res = pthread_detach(tid);if (res != 0){std::cout << strerror(res) << std::endl;return -1;}#endifstd::cout << "已设置线程分离" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_use_attr_detach -DUSE_ATTR -lpthread
[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_no_attr_detach -lpthread
[root@Zhn 线程]# ./pthread_no_attr_detach 
已设置线程分离TID: 
8428
pthread_self: pthread_self: 139697266493184 hello world
[root@Zhn 线程]# ./pthread_no_attr_detach 
已设置线程分离TID: 8485
pthread_self: 140716723963648 hello world[root@Zhn 线程]# ./pthread_use_attr_detach 
已设置线程分离
[root@Zhn 线程]# 

我们使用两种方式设置线程分离,一种是通过属性设置,还有一种是调用pthread_detach设置分离,执行了三次程序,可以发现第三次线程没有输出,这是为什么?因为我们设置线程分离,那么主线程先抢到CPU执行之后就退出了,还没轮到子线程执行

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

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

相关文章

一维前缀和与差分数组

目录 前缀和 差分数组 性质 例题&#xff1a; 前缀和 前缀和主要适用场景是原始数组不会被修改的情况下&#xff0c;频繁查询某个区间的累加和。 差分数组 性质 当我们需要更新区间[l,r]时候&#xff08;仅指加减运算&#xff09;&#xff0c;我们仅仅可以只更新d[l]x,d[r1…

通信分类3G,4G,5G,通信专用名词

Generation: 2G: GSM全名为&#xff1a;Global System for Mobile Communications&#xff0c;中文为全球移动通信系统&#xff0c;俗称"全球通"&#xff0c;是一种起源于欧洲的移动通信技术标准&#xff0c;是第二代移动通信技术 3G&#xff1a;WCDMA 4G&#xff1a…

VMware vSphere Hypervisor,ESXi的介绍,下载与安装

1.介绍 看这篇文章就好了 Vmware ESXi 是免费吗&#xff1f;一文弄懂vSphere功能特性及ESXi与vSphere到底有什么区别和联系。 - 知乎 (zhihu.com) 2.下载 这里面有7.0各个版本的下载镜像文件和校验信息 VMware-Esxi7.0各个版本镜像文件iso下载链接_esxi7.0镜像-CSDN博客 3.…

计算机网络-TCP基础、三次挥手、四次握手过程

TCP基础 定义&#xff1a;TCP是面向连接的、可靠的、基于字节流的传输层通信协议。这意味着在发送数据之前&#xff0c;TCP需要建立连接&#xff0c;并且它能确保数据的可靠传输。此外&#xff0c;TCP将数据视为无结构的连续字节流。面向连接&#xff1a;TCP只能一对一进行连接…

RAG文本加载和分块调研

文本加载和分块 一、文本加载 文本加载是RAG文本增强检索重要环节。文件有不同类型&#xff08;excel、word、ppt、pdf、png、html、eps、gif、mp4、zip等&#xff09;&#xff0c;衍生出了很多第三方库。使用python处理文件是各种python开发岗位都需要的操作。主要涉及到的标准…

【智能算法】随机油漆优化算法(SPO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2022年&#xff0c;A Kaveh等人受到绘画艺术启发&#xff0c;提出了减法平均优化器&#xff08;Stochastic Paint Optimizer&#xff0c;SPO&#xff09;。 2.算法原理 2.1算法思想 SPO将搜索空间…

von Mises-Fisher Distribution (Appendix)

2. Relation to Normal Distribution 疑问&#xff1a;有没有不各向同性的 vMF&#xff1f; 答&#xff1a;应该是没有的&#xff0c;如果想让各方向偏离中心的速度不一致&#xff0c;则协方差矩阵不为 I \bm{I} I 的倍数. 正态分布的概率密度函数为&#xff1a; f ( x ) 1 …

时序预测 | Matlab实现SSA-ESN基于麻雀搜索算法(SSA)优化回声状态网络(ESN)的时间序列预测

时序预测 | Matlab实现SSA-ESN基于麻雀搜索算法(SSA)优化回声状态网络(ESN)的时间序列预测 目录 时序预测 | Matlab实现SSA-ESN基于麻雀搜索算法(SSA)优化回声状态网络(ESN)的时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SSA-ESN基于麻雀搜索…

RuntimeError: Error(s) in loading state_dict for ZoeDepth解决方案

本文收录于《AI绘画从入门到精通》专栏,订阅后可阅读专栏内所有文章,专栏总目录:点这里。 大家好,我是水滴~~ 本文主要介绍在 Stable Diffusion WebUI 中使用 ControlNet 的 depth_zoe 预处理器时,出现的 RuntimeError: Error(s) in loading state_dict for ZoeDepth 异常…

故障诊断 | Matlab实现基于小波包结合鹈鹕算法优化卷积神经网络DWT-POA-CNN实现电缆故障诊断算法

故障诊断 | Matlab实现基于小波包结合鹈鹕算法优化卷积神经网络DWT-POA-CNN实现电缆故障诊断算法 目录 故障诊断 | Matlab实现基于小波包结合鹈鹕算法优化卷积神经网络DWT-POA-CNN实现电缆故障诊断算法分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现基于小波…

代码随想录学习Day 26

332.重新安排行程 题目链接 from collections import defaultdictclass Solution:def findItinerary(self, tickets):targets defaultdict(list) # 创建默认字典&#xff0c;用于存储机场映射关系for ticket in tickets:targets[ticket[0]].append(ticket[1]) # 将机票输入…

win11网络驱动怎么安装,windows11怎么安装驱动

win11网络驱动怎么安装呢?驱动程序是系统中非常重要的部分,当安装新硬件时,相应的硬件没有驱动程序,那么在计算中就无法工作。而有了驱动后,计算机就可以与设备进行通信。例如,电脑如果缺少了网络驱动,那么就会无法上网,需要安装上网的驱动程序。由于win11系统变化太大…

Echarts柱状图多样式实现

样式一 样式二 在这里插入代码片

数据库索引详解

目录 第一章、快速了解索引1.1&#xff09;索引是什么1.2&#xff09;为什么使用索引1.3&#xff09;操作索引示例 第二章、索引分类2.1&#xff09;按数据结构分类2.1.1&#xff09;树型数据结构索引二叉树B树B 树 2.1.2&#xff09;Hash数据结构索引2.1.3&#xff09; 其他数…

【JavaWeb】Day34.MySQL概述——数据库设计-DDL(一)

项目开发流程 需求文档&#xff1a; 在我们开发一个项目或者项目当中的某个模块之前&#xff0c;会先会拿到产品经理给我们提供的页面原型及需求文档。 设计&#xff1a; 拿到产品原型和需求文档之后&#xff0c;我们首先要做的不是编码&#xff0c;而是要先进行项目的设计&am…

docker搭建EFK

目录 elasticsearch1.创建网络2.拉取镜像3.创建容器如果出现启动失败&#xff0c;提示目录挂载失败&#xff0c;可以考虑如下措施 开放防火墙端口4.验证安装成功重置es密码关闭https连接创建kibana用户创建新账户给账户授权 kibana1.创建容器2.验证安装成功3.es为kibana创建用户…

java对象是怎么在jvm中new出来的,在内存中查看java对象成员变量字段属性值

java对象是怎么在jvm中new出来的 查看java对象字段属性在内存中的值 java 对象 创建 流程 附上java源码 public class MiDept {private int innerFiled999;public MiDept() {System.out.println("new MiDept--------------");}public String show(int data) {Sy…

电脑怎么才能用动态ip上网?步骤详解与优势分析

在数字化时代&#xff0c;互联网已成为我们生活与工作中不可或缺的一部分。为了保障网络安全、提升网络效率或满足特定应用场景的需求&#xff0c;有时我们需要为电脑配置动态IP地址上网。那么&#xff0c;如何为电脑设置动态IP地址呢&#xff1f;本文将为您详细介绍动态IP的配…

为什么AI模型需要合乎道德的数据

道德问题简介 “合乎道德的数据”和“负责任的数据”这两个词的含义可能并不明确。在科技界和AI数据界&#xff0c;道德原则是指负责任地采集和使用数据用以训练模型&#xff0c;并确保这些模型不带偏见地与人类交互。不仅为训练模型负责任地采集和使用数据很重要&#xff0c;…

Java的jmap命令使用详解

jmap命令简介 jmap&#xff08;Java Virtual Machine Memory Map&#xff09;是JDK提供的一个可以生成Java虚拟机的堆转储快照dump文件的命令行工具。 以外&#xff0c;jmap命令还可以查看finalize执行队列、Java堆和方法区的详细信息&#xff0c;比如空间使用率、当前使用的…