移动端网站开发尺寸,上品设计,logo创意设计,同一个服务器做两个网站目录
一、进程
1.1、进程状态
1.2、进程的控制结构
1.3、进程的控制
1.4、进程的上下文切换
二、线程
2.1.线程是什么
2.2、线程与进程的比较
2.3、线程的上下文切换
2.4、线程的实现
2.5、轻量级线程
三、进程间的通信方式
3.1、管道
3.2、消息队列
3.3、共享内…目录
一、进程
1.1、进程状态
1.2、进程的控制结构
1.3、进程的控制
1.4、进程的上下文切换
二、线程
2.1.线程是什么
2.2、线程与进程的比较
2.3、线程的上下文切换
2.4、线程的实现
2.5、轻量级线程
三、进程间的通信方式
3.1、管道
3.2、消息队列
3.3、共享内存
3.4、信号量
3.5、信号
3.6、Socket
四、多线程冲突
五、如何避免死锁
六、锁
6.1、互斥锁与自旋锁
6.2、读写锁
6.3、乐观锁与悲观锁
6.4、一个进程最多可以创建多少个线程呢 一、进程
1.1、进程状态
我们编写代码只是在一个存储在硬盘的静态文件通过编译之后就会生成二进制可执行文件当我们运行这个可执行文件后它会被装载到内存之中接着 CPU 会执行程序中的每一条指令那么这个运行中的程序就被称作进程。
一个进程的活动期间至少具备三种基本状态运行状态、就绪状态、阻塞状态 运行状态Running该时刻进程占用 CPU就绪状态Ready可运行由于其他进程处于运行态而暂时停止运行阻塞状态Blocked该进程正在等待某一事件发生而暂时停止运行此时即使给它 CPU 控制权它也无法进行
此外还有两种基本状态
创建状态new进程正在被创建时的状态结束状态Exit进程正在从系统中消失时的状态
与是一个完整的进程状态变迁如下 进程状态变迁
NULL - 创建状态一个新进程被创建时的第一个状态创建状态 - 就绪状态当进程被创建完成并初始化后一切就绪准备运行时变为就绪状态这个过程是很快的就绪态 - 运行状态处于就绪状态的进程被操作系统的进程调度器选中后就分配 CPU 正式运行该进程运行状态 - 结束状态当进程已经运行完成或出错时会被操作系统做结束状态处理运行状态 - 就绪状态处于运行状态的进程在运行过程中由于分配给它的运行时间片用完操作系统就会把该进程变为就绪态接着从就绪态选中另外一个进程运行运行状态 - 阻塞状态当进程请求某个时间且必须等待时例如请求 IO 事件阻塞状态 - 就绪状态当进程要等待的事件完成时它从阻塞状态变到就绪状态
在虚拟内存管理的操作系统中通常会把阻塞状态的进程的物理内存空间换出到硬盘等需要再次运行的时候再从硬盘换到物理内存进程没有占用实际的物理内存空间的情况这个状态就是【挂起状态】。
另外挂起状态可分为两种
阻塞挂起状态进程在外存硬盘并等待某个事件的出现就绪挂起状态进程在外村硬盘但只要进入内存立刻运行 1.2、进程的控制结构
PCB 是进程存在的唯一标识这意味着一个进程的存在必然会有一个 PCB如果进程消失了那么 PCB 也会随之消失。
PCB 包含什么信息呢
①进程描述信息
进程标识符标识各个进程每个进程都有一个并且唯一的标识符用户标识符进程归属的用户用户标识符主要为共享和保护
②进程控制和管理信息
进程当前状态如 newreadyrunningwaiting 或 blocked 等进程优先级进程抢占 CPU 时的优先级
③资源分配清单
有关内存地址空间或虚拟地址空间的信息所打开文件的列表和所使用的 IO 设备信息
④CPU 相关信息
CPU 中各个寄存器的值 当进程被切换时CPU 的状态信息都会被保存在相应的 PCB 中以便重新执行时能从断点处继续执行
PCB 是如何组织的呢
通常是通过链表的方式进行组织把具有相同状态的进程链在一块组成各种队列如
将所有处于就绪状态的进程链在一块称为【就绪队列】
把所有因等待某事件而处于等待状态的进程链在一起就组成了各种【阻塞队列】
另外对于运行队列在单核 CPU 系统中则只有一个运行指针了因为单核 CPU 在某个事件只能运行一个程序 1.3、进程的控制
①创建进程
操作系统允许一个进程创建另一个进程而且允许子进程继承父进程所拥有的资源
创建进程的过程如下
申请一个空白的 PCB 并向 PCB 中填写一些控制和管理进程的信息比如进程的唯一标识符为该进程分配运行时所需的资源比如内存资源将 PCB 插入到就绪队列等待被调度运行
②终止进程
进程可以有三种终止方式正常结束异常结束以及外界干预信号 kill 掉
当子进程被终止时在父进程处继承的资源应当归还给父进程。而当父进程被终止时该父进程的子进程就变成孤儿进程会被 1 号进程收养并由 1 号进程对它们完成状态收集工作
终止进程过程如下
查找需要终止进程的 PCB 如果出于执行状态则立刻终止该进程的执行然后将 CPU 资源分配给其他进程如果其还有子进程则应将该进程的子进程交由 1 号进程接管将该进程所拥有的全部资源都归还给操作系统将其从 PCB 所在队列中删除
③阻塞进程
当进程需要等待某一事件完成时它可以调用阻塞语句把自己阻塞等待。而一旦被阻塞等待它只能由另一个进程唤醒。过程如下
找到将要被阻塞进程标识号对应的 PCB如果改进程为运行状态则保护现场将其状态转为阻塞状态停止运行将该 PCB 插入到阻塞队列中
④唤醒进程
进程由【运行】转变为【阻塞】状态是由于进程必须等待某一事件的完成所以处于阻塞状态的进程是绝对不可能叫醒自己的。唤醒过程如下
在该事件的阻塞队列中找到相应进程的 PCB将其从阻塞队列中移出并置状态为就绪状态把该 PCB 插入到就绪队列中等待程序调度
1.4、进程的上下文切换
一个进程切换到另一个进程运行称为进程的上下文切换进程是由内核管理和调度的所以进程的切换只能发生在内核态所以进程的上下文切换不仅包含了虚拟内存栈全局变量等用户空间的资源还包括了内核堆栈寄存器等内核空间的资源。举个 发生进程切换上下文的场景
为了保证所有进程可以得到公平调度CPU 时间被划分为一段段的时间片这些时间片再被轮流分配给各个进程。这样当某个进程的时间片耗尽了进程就从运行状态变为就绪状态系统从就绪队列选择另外一个进程运行进程在系统资源不足比如内存不足时要等到资源满足后才可以运行这个时候进程也会被挂起并由系统调度其他进程运行当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时自然也会重新调度当有优先级更高的进程运行时为了保证高优先级进程的运行当前进程会被挂起由高优先级进程来运行发生硬件中断时CPU 上的进程会被中断挂起转而执行内核中的中断服务程序
二、线程
2.1.线程是什么
线程是进程当中的一条执行流程。
同一个线程内多个线程之间可以共享代码段数据段打开的文件等资源但每个线程都有各自一套独立的寄存器和栈这样就可以确保线程的控制流是相对独立的。
优点
一个进程可以同时存在多个线程各个线程之间可以并发执行各个线程之间可以共享地址空间和文件等资源
缺点
当进程中的一个线程崩溃时会导致其所属进程的所有线程崩溃
2.2、线程与进程的比较
线程与进程的比较如下
进程是资源分配的单位线程是 CPU 调度的单位进程拥有一个完整的资源平台而线程只独享必不可少的资源如寄存器与栈线程同样具有就绪阻塞执行三种基本状态同样具有状态之间的转换关系线程能减少并发执行的时间和空间开销
线程相比进程能减少开销体现在
线程的创建时间比进程快因为进程在创建的过程中还需要资源管理信息比如内存管理信息文件管理信息而线程在创建的过程中不会涉及这些资源管理信息而是共享它们线程终止时间比进程快因为线程释放的资源相比进程少很多同一个进程内的线程切换比进程切换快因为线程具有相同的地址空间这意味着同一个进程的线程都具有同一个页表那么在切换时不需要切换页表。而对于进程之间的切换切换的时候要把页表给切换掉而页表的切换过程开销是比较大的由于同一进程的各线程之间共享内存和文件资源那么在线程之间数据传递的时候就不需要经过内核了这就使得线程之间的数据交互效率更高了
2.3、线程的上下文切换
线程与进程最大的区别是线程是调度的基本单位而进程则是资源拥有的基本单位
所以操作系统的任务调度实际上的调度对象是线程而进程只是给了线程提供了虚拟内存全局变量等资源。所以我们可以这样理解
当进程只有一个线程时可以认为进程就等于线程当进程拥有多个线程时这些线程会共享相同的虚拟内存和全局变量等资源这些资源在上下文切换的时候是不需要更改的
另外线程也有自己的私有数据比如栈和寄存器等这些在上下文切换时也需要保存
线程上下文切换的是什么
当两个线程不是属于同一个进程则切换的过程就像进程上下文切换一样当两个线程属于同一个进程时因为虚拟内存是共享的所以在切换时虚拟内存这些资源就保持不懂只需要切换线程的私有数据寄存器这些不共享的数据
2.4、线程的实现
主要有三种线程的实现方式
用户线程User Thread在用户空间实现的线程不是由内核管理的线程是由用户态的线程库来完成线程的管理内核线程Kernel Thread在内核中实现的线程是由内核管理的线程轻量级线程LightWeight Process在内核中来支持用户线程
此时我们需要考虑用户线程和内核线程的对应关系
多对一的关系也就是多个用户进程对应同一个内核线程一对一的关系也就是一个用户线程对应一个内核线程多对多的关系也就是多个用户线程对应到的多个内核线程
用户线程
用户线程是基于用户态的线程管理库来实现的那么线程控制块Thread Control BlockTCB也是在库里面实现的对于操作系统而言是看不到这个 TCB 的它只能看到整个进程的 PCB
所以用户进程的整个线程管理和调度操作系统是不直接参与的而是由用户级线程库函数来完成线程的管理包括线程的创建、终止、同步和调度等
用户线程优点
每个进程都需要有它私有的线程控制块TCB列表用来跟踪记录它各个线程状态信息TCB 由用户级线程库来完成的无需用户态与内核态的切换所以速度特别快用户线程的切换也是由线程库函数来完成的无需用户态与内核态的切换所以速度特别快
缺点
由于操作系统不参与线程的调度如果一个线程发起了系统调用而阻塞那进程所包含的用户线程都不能执行了当一个线程开始运行后除非它主动的交出 CPU 的使用权否则它在的进程当中的其他进程无法运行因为用户态的线程没法打断当前运行中的线程它没有这个特权只有操作系统有但是用户线程不是由操作系统管理的由于时间片分配给进程故与其他进程比在多线程执行时每个线程得到的时间片少执行会比较慢
内核线程
内核线程是由操作系统管理的线程对应的 TCB 自然是放在操作系统中的这样线程的创建、终止和管理都是由操作系统负责
优点
在一个进程中如果某个内核线程发起系统调用而被阻塞并不会影响其他内核线程的运行分配给线程多线程的进程获得更多的 CPU 运行时间
缺点
在支持内核线程的操作系统中由内核来维护进程和线程的上下文信息如 PCB 和 TCB线程的创建、终止和切换都是通过系统调用的方式来进行因此对于系统来说系统开销比较大
2.5、轻量级线程
轻量级线程Light-weight processLWP是内核支持的用户线程一个进程可有一个或多个 LWP每一个 LWP 是跟内核线程一对一映射的也就是 LWP 都是由一个内核线程支持而且 LWP 是由内核管理并像普通进程一样被调度。
在大多数的系统下LWP 与普通进程的却别也就在于它有一个最小的执行上下文和调度程序所需的统计信息。
在 LWP 之上也是可以使用用户线程的那么 LWP 与用户线程的对应关系也是三种一对一多对一多对多。
1 : 1 模式
一个线程对应到一个 LWP 再对应到一个内核线程
优点实现并行当一个 LWP 阻塞不会影响其他 LWP缺点每一个用户线程就产生一个内核线程创建线程开销较大
N : 1 模式
多个用户线程对应一个 LWP 再对应一个内核线程
优点用户线程要开几个都没问题且上下文切换发生用户空间切换效率高缺点一个用户进程阻塞了则整个进程都会被阻塞另外在多核 CPU 中是没办法充分利用 CPU 的
M : N 模式
根据前面两种模式混搭在一起就形成了 M : N 模式
优点综合了前面两种的优点大部分的线程上下文切换发生在用户空间且多个线程又可以充分利用 CPU 资源。
三、进程间的通信方式
每个进程的用户空间都是独立的一般而言是不能相互访问的但内核空间是每个进程都共享的所以进程之间通信必须通过内核 3.1、管道
管道分为匿名管道和有名管道其中匿名管道适用于具有亲缘关系进程之间的通信。
管道这种通信方式效率低不适合进程间频繁地交换数据。当然它的好处是简单同时我们很容易得知管道里的数据被另一个进程读取。
匿名管道创建通过
int pipe(int fd[2])
表示创建一个匿名管道并返回两个描述符一个是管道的读取端描述符 fd[0]另一个是写入端描述符 fd[1] 。注意这个匿名管道是特殊的文件只存在于内存中不存在于文件系统中。
其实所谓的管道就是内核中的一段缓存。从管道的一段写入的数据实际上是缓存在内核中的另一端读取也就是从内核中读取这段数据。另外管道传输的数据是无格式的流且受大小限制。
我们在使用 fork 创建的子进程会复制父进程的文件描述符这样就做到了两个进程各有两个【fd[0]fd[1]】两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信。
管道只能一端读另一端写因为父子进程都可以同时写与读这样很容易就造成混乱通常的做法就是父进程关闭读取的 fd[0] 子进程关闭写入的 fd[1] 所以说需要双向通信就应该创建两个管道。
对于命名管道它可以在不相关的进程间也能相互通信。因为命名管道提前创建了一个类型为管道的设备文件进程只要使用这个设备文件就可以相互通信。
不管是匿名管道还是命名管道进程写入的数据都是缓存在内核中另一个进程读取数据时候自然也是从内核中获取同时通信数据都遵循先进先出原则。
3.2、消息队列
前面写到管道的通信方式是效率低的因此管道不适合进程间频繁地交换数据
对于这个问题消息队列的通信模式就可以解决
消息队列是保存在内核中的消息链表在发送数据时会被拆分成一个个独立的单元也就是消息体消息体是用户自定义的数据类型消息的发送方和接收方要约定好消息体的数据类型所以每个消息体都是固定大小的存储块不像是无格式的字节流数据。如果进程从消息队列中读取了消息体内核就会把这个消息体删除。
消息队列生命周期随内核如果没有释放消息队列或者没有关闭操作系统消息队列会一直存在而前面提到的匿名管道的生命周期是随进程的创建而建立随进程的结束而销毁
缺点
消息队列不适合比较大数据的传输因为在内核中每个消息体都会有一个最大长度的限制同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中会有两个宏定义 MSGMAX 和 MSGMNB 它们以字节为单位分别定义了一条消息的最大长度和一个队列的最大长度。消息队列通信过程中存在用户态与内核态之间的数据拷贝开销因为进程写入数据到内核中的消息队列时会发生从用户态拷贝数据到内核态的过程同理另一进程读取内核中的消息数据时会发生从内核态拷贝数据到用户态的过程。
3.3、共享内存
消息队列的读取和写入的过程都会有发生用户与内核态之间的消息拷贝过程。那共享内存的方式就很好的解决了这个问题。
共享内存机制就是拿出一块虚拟地址空间来映射到相同的物理内存中这样两个进程写入的东西另外一个进程马上就能看到了都不需要拷贝来拷贝去大大提高了进程间通信的速度。 3.4、信号量
共享内存通信机制带来了一个新问题那就是如果多个进程同时修改一个共享内存很有可能就冲突了。例如两个进程同时写一个地址那先写的那个地址会发现内容被别人覆盖了
为了防止多进程竞争共享资源而造成的数据错乱所以需要保护机制使得共享的资源在任意时刻只能被一个进程访问。信号量就正好提供了这一保护机制。
信号量其实就是一个整型的计数器主要用于实现进程间的互斥与同步而不是用于缓存进程间通信的数据。
信号量代表资源的数量控制信号量的方式有两种原子操作
P 操作这个操作会把信号量减 1 相减后如果信号量 0则表明资源已被占用进程需阻塞等待。相减后如果信号量 0则表明资源还可使用进程可正常继续执行。V 操作这个操作会把信号量加 1 相加后如果信号量 0则表明当前有阻塞中的进程于是会将该进程唤醒运行相加后如果信号量 0 则表明当前没有阻塞中的进程。
P 操作是用在进入共享资源前V 操作是用在离开共享资源之后这两个操作是必须成对出现的。
当信号初始化为 1 就代表着是互斥信号量它可以保证共享内存在任何时刻只有一个进程在访问这就很好的保护了共享内存。
当信号量初始化为 0 就代表着是同步信号量它可以保证进程之间的同步。
3.5、信号
上面说的进程间的通信都是常规状态下的工作状态。对于异常情况下的工作模式就需要使用【信号】的方式来通知进程。
信号是进程间通信机制中唯一的异步通信机制因为可以在任何时候发送信号给某一进程一旦有信号产生我们就有下面的几种用户进程对信号的处理方式。
执行默认操作。Linux 对每种信号都规定了默认操作例如 SIGINT 信号就是终止该进程的意思
捕捉信号。可以为信号定义一个信号处理函数。当信号发生时我们就可以执行相应的信号处理函数
忽略信号。当我们不希望处理某些信号的时候就可以忽略该信号不做任何处理。有两个信号是进程无法捕捉和忽略的即 SIGKILL 和 SEGSTOP 它们用在任何时刻中断或结束某一进程。
3.6、Socket
上述的方式都是在同一主机进行进程进程间的通信那么想要跨网络与不同主机上的进程之间通信就需要 Socket 通信了。实际上它不仅可以跨网络与不同主机的进程间通信还可以在同主机进程间通信。
进行本地进程通信时在 bind 的时候不像 TCP 和 UDP 要绑定 IP 地址和端口而是绑定一个本地文件这也就是他们之间的最大区别。
四、多线程冲突
竞争条件当多线程相互竞争操作共享变量时在执行过程中发生了上下文切换。不确定性每次运行都可能得到不同的结果临界区访问共享资源的代码片段一定不能给多线程同时使用互斥保证一个线程在临界区执行时其他线程应该被阻止进入临界区同步就是并发进程/线程在一些关键节点上可能需要互相等待与互通消息这种相互制约的等待与互通消息称为进程/线程同步。
哲学家问题 五、如何避免死锁
造成死锁的条件
互斥条件多个线程不能同时使用同一个资源持有并等待条件线程在等待某一资源的时候不会释放已持有的资源 不可剥夺条件在自己使用完之前不能被其他线程获取环路等待条件两个线程获取资源的顺序构成了环形链
避免死锁的方法是破坏上述其中的任一条件即可
六、锁
6.1、互斥锁与自旋锁
互斥锁加锁失败后线程会释放 CPU 给其他线程自旋锁加锁失败后线程会忙等待直到它拿锁
互斥锁是一种【独占锁】比如线程 A 加锁成功后此时互斥锁已经被线程 A 独占了只要线程 A 没有释放手中的锁线程 B 加锁就会失败于是就会释放 CPU 让给其他线程既然线程 B 释放掉了 CPU 自然线程 B 加锁的代码就被阻塞。对于互斥锁加锁失败而阻塞的现象是由操作系统内核实现的。当加锁失败时内核会将线程置为【睡眠】状态等到锁被释放后内核会在合适的实际唤醒线程当这个线程获取到锁后于是就可以继续执行。
互斥锁在加锁失败时会从用户态陷入到内核态让内核帮我们切换线程虽然简化了使用锁的难度但是存在一定的性能开销成本而这个开销的成本就是会有两次线程上下文切换的成本
当线程加锁失败时内核会把线程的状态从【运行】状态设置为【睡眠】状态然后把 CPU 切换给其他线程运行接着当锁被释放时之前【睡眠】状态的线程就会变为【就绪】状态然后内核会在合适的时间把 CPU 切换给该线程运行
线程的上下文切换是什么当两个线程属于同一个线程因为虚拟内存是共享的所以在切换时虚拟内存这些资源就保持不动只需要切换线程的私有数据寄存器这些等不共享的数据。
如果你能确定被锁住的代码执行时间很短就不应该用互斥锁而应该用自旋锁否则使用互斥锁。
自旋锁是一种比较简单的锁一直自旋利用 CPU 周期直到锁可用。需要注意的是在单核 CPU 上需要抢占式的调度器即不断通过时钟中断一个线程运行其他线程。否则自旋锁在单核 CPU 上无法使用因为一个自旋锁的线程永远不会放弃 CPU。
当加锁失败时互斥锁用【线程切换】来应对自旋锁则是【忙等待】来应对。
6.2、读写锁
读写锁适用于能明确区分读操作和写操作的场景
读写锁的工作原理
当【写锁】没有被线程持有时多个线程能够并发地持有读锁这大大提高了共享资源的访问效率因为【读锁】是用于读取共享资源的场景所以多个线程同时持有读锁也不会破坏共享资源的数据。但是一旦【写锁】被线程持有后读线程的获取读锁的操作会被阻塞。而且其他写线程的获取写锁的操作也会被阻塞。
所以说写锁是独占锁因为任何时刻只能有一个线程持有写锁类似互斥锁和自旋锁而读锁是共享锁因为读锁可以被多个线程同时持有。
知道了读写锁的工作原理后我们可以发现读写锁在都多写少的场景都能发挥出优势。
根据实现的不同读写锁还分为【读优先锁】和【写优先锁】
读优先锁它期望的是读锁能够被更多的线程持有以便提高读线程的并发性它的工作方式是当读线程 A 先持有了读锁写线程 B 在获取写锁的时候会被阻塞并且在阻塞过程中后续来的读线程 C 仍然可以成功的获取读锁最后直到读线程 A 和 C 释放读锁后写线程 B 才可以成功获得写锁。写优先锁是优先服务于写线程其工作方式是当读线程 A 先持有了读锁写线程 B 在获取写锁的时候会被阻塞并且在阻塞过程中后续来的读线程 C 获取读锁时会失败于是读线程 C 将会被阻塞在获取读锁的操作这样只要读线程 A 释放读锁后写线程 B 就可以成功获得写锁。
公平读写锁比较简单的一张方式是用队列把获取锁的线程排队不管是写线程还是读线程都按照先进先出的原则加锁即可这样读线程仍然可以并发也不会出现【饥饿】的现象。
6.3、乐观锁与悲观锁
上述的互斥锁、自旋锁、读写锁都是属于悲观锁。
悲观锁做事比较悲观它认为多线程同时修改共享资源的概率比较高于是很容易出现冲突所以访问共享资源前要先上锁。
相反的如果多线程同时修改共享资源的概率比较低就可以采用乐观锁。
乐观锁比较乐观它假定冲突的概率比较低它的工作方式是先修改完共享资源再验证这一段时间内有没有发生冲突如果没有其他线程在修改资源那么操作完成如果发现有其他线程已经修改过这个资源就放弃本次操作。可见乐观锁是真的很乐观不管三七二十一想改了资源再说。另外乐观锁全程并没有加锁所以也叫它无锁编程。
6.4、一个进程最多可以创建多少个线程呢
这个问题和两个东西有关
进程的虚拟内存空间上限因为创建一个线程操作系统需要为其分配一个栈空间如果线程数量越多所需的栈空间就要越大那么虚拟内存就会越占用的越多。
系统参数限制虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数但是有系统级别的参数来控制整个系统的最大线程个数。
在 32 位 Linux 系统里一个进程的虚拟空间是 4 G 内核分走了 1 G留给用户的只有 3 G。
假设创建一个线程需要占用 10M 的虚拟内存总共只有 3 G 虚拟内存可以使用于是我们可以算出最多可以创建差不多 300 个左右的线程。如果想使得进程创建上千个线程那么我们可以调整创建线程时分配的栈空间大小比如调整为 512k 【ulimit -s 512】
在 64 位系统里意味着用户空间的虚拟内存最大值是 128 T这个数值很大如果创建一个线程需占用 10M 栈空间的情况来算那么理论上可以创建 128T / 10M 个线程也就是 1000多W 个线程但是实际上创建不了这么多除了虚拟内存的限制还有系统的限制
/proc/sys/kernel/threads-max表示系统支持的最大线程数默认是 14553/proc/sys/kernel/pid_max表示系统全局的 PID 号数值的限制每一个进程或线程都有 IDID 的值超过这个数进程或者线程就会创建失败默认是 32768/proc/sys/vm/max_map_count表示限制一个进程可以拥有的 VMA虚拟内存区域的数量如果这个值很小也会导致创建线程的失败默认值是 65530