用户态与内核态
CPU分不同的指令级别
linux内核跑在ring 0 级,用户程序跑在ring 3,对于系统的关键访问,需要经过kernel同意,保证系统健壮性
内核执行的操作->200多个系统调用sendfile read write pthread fork
进程 线程 纤程
面试高频题:进程和线程有什么区别?
进程就是一个程序运行起来的状态,线程是一个进程中的不同的执行路径
专业答案:进程是OS分配资源的基本单位,线程是执行调度的基本单位。
分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)
线程在Linux中的实现:
就是一个普通进程,只不过和其他进程共享资源(内存空间 全局数据等。。。)
Linux中起一个进程,就是调用fork()函数。例如进程1,在进程1中想起一个线程,其实也是调用fork()起一个进程,只不过起的这个进程和进程1是共享内存的。
其他系统都有各自的所谓LWP的实现Light Weight Process---轻量级进程
高层面理解:一个进程中的不同的执行路线
fiber---纤程/协程(线程中的线程)
纤程:用户态的线程,线程中的线程,切换和调度不需要经过OS
优势:
1:占有资源很少,OS起一个线程占用1M,纤程占用4K
2:切换比较简单
3:启动很多个10W+
目前支持内置纤程的语言:Kotlin Scala Go Python(lib)... java目前在jdk14是不支持的,但是通过类库是可以实现的(open jdk:loom)
java中对于纤程的支持:没有内置,盼望内置
利用Quaser库(不成熟)
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zy.jvm</groupId>
<artifactId>jvm</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/co.paralleluniverse/quasar-core -->
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.7.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
线程实现:
package com.mashibing.jvm.os;
public class HelloFiber {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Runnable r = new Runnable() {
@Override
public void run() {
calc();
}
};
int size = 100000;
Thread[] threads = new Thread[size];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) result += i;
}
}
}
纤程是实现:
package com.mashibing.jvm.os;
import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.SuspendableRunnable;
public class HelloFiber2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int size = 100000;
Fiber<Void>[] fibers = new Fiber[size];
for (int i = 0; i < fibers.length; i++) {
fibers[i] = new Fiber<Void>(new SuspendableRunnable() {
public void run() throws SuspendExecution, InterruptedException {
calc();
}
});
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].start();
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) result += i;
}
}
}
作业:目前是10000个Fiber -> 1个JVM线程,想办法提高效率,10000Fiber -> 10份 -> 10Threads
纤程的应用场景
纤程 vs 线程池:很短的计算任务,不需要和内核打交道,并发量高!
进程
linux中也称为task,是系统分配资源的基本单位
资源:独立的地址空间 内核数据结构 (进程描述符...) 全局变量 数据段...
进程描述符:PBC(Process Control Block)
进程创建和启动
系统函数fork() exec()
从A中fork B的话,A称为B的父进程
什么是僵尸进程
ps -ef | grep defuct
父进程产生子进程后,会维护子进程的一个PCB结构,子进程退出,由父进程释放,如果父进程没有释放,那么子进程成为一个僵尸进程(大多是情况下僵尸进程是没有什么影响的,因为只是维护一个PCB描述符,子进程的资源都会释放,影响不是很大,但是如果太多僵尸进程,肯定还是有影响的)
zombie.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (0 == pid) {
printf("child id is %d\n", getpid());
printf("parent id is %d\n", getppid());
} else {
while(1) {}
}
}
什么是孤儿进程
子进程结束之前,父进程已经退出 孤儿进程会成为init进程的孩子,由1号进程维护
orphan.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (0 == pid) {
printf("child ppid is %d\n", getppid());
sleep(10);
printf("parent ppid is %d\n", getppid());
} else {
printf("parent id is %d\n", getpid());
sleep(5);
exit(0);
}
}
进程调度
内核进程调度器决定:该哪一个进程运行?何时开始?运行多长时间?
进程调度的算法:
非抢占式(cooperative multitasking)除非进程主动让出cpu(yielding),否则将一直运行
抢占式(preemptive multitasking)由进程调度器强制开始或暂停(抢占)某一进程的执行
进程调度
2.6采用CFS调度策略:Completely Fair Scheduler
按优先级分配时间片的比例,记录每个进程的执行时间,如果有一个进程执行时间不到它应该分配的比例,优先执行。
进程调度基本概念
进程类型:
IO密集型 大部分时间用于等待IO
CPU密集型 大部分时间用于闷头计算
进程优先级
实时进程 > 普通进程
实时进程中又分为100个优先级(0 - 100)
普通进程nice值(-20 ~ 19)
Linux默认的调度策略
对于实时进程(急诊):使用SCHED_FIFO(先进先出)和SCHED_RR(轮询)两种
对于普通进程:使用CFS
其中等级最高的是FIFO,这种进程除非自己让出CPU否则Linux会一直执行它
除非更高级的FIFO和RR抢占它
RR只是这种这种进程中是同级别FIFO中的平均分配 ---- 比如FIFO中有两个都是90优先级的进程,这时候按照RR执行。
只有实时进程主动让出,或者执行完毕后,普通继承才有机会运行。