JDK21虚拟线程

目录

虚拟线程

话题

什么是平台线程?

什么是虚拟线程?

为什么要使用虚拟线程?

创建和运行虚拟线程

使用线程类和线程创建虚拟线程。生成器界面

使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程

调度虚拟线程和固定虚拟线程

调试虚拟线程

JDK虚拟线程的飞行记录器事件


官方文档的翻译版本

官方文档:Virtual Threads

虚拟线程

虚拟线程是轻量级线程,可以减少编写、维护和调试高吞吐量并发应用程序的工作量。

有关虚拟线程的背景信息,请参阅JEP444。

线程是可以调度的最小处理单元。它与其他此类单元同时运行,并且在很大程度上独立于其他此类单元。它是java.lang.Thread的一个实例。有两种线程,平台线程和虚拟线程。

话题

  • 什么是平台线程?
  • 什么是虚拟线程?
  • 为什么要使用虚拟线程?
  • 创建和运行虚拟线程
  • 调度虚拟线程和固定虚拟线程
  • 调试虚拟线程
  • 虚拟线程:采用指南

什么是平台线程?

平台线程被实现为操作系统(OS)线程周围的瘦包装器。平台线程在其底层操作系统线程上运行Java代码,平台线程在平台线程的整个生命周期中捕获其操作系统线程。因此,可用平台线程的数量被限制为OS线程的数量。

平台线程通常具有由操作系统维护的大型线程堆栈和其他资源。它们适用于运行所有类型的任务,但可能是有限的资源。

什么是虚拟线程?

与平台线程一样,虚拟线程也是java.lang.thread的一个实例。然而,虚拟线程并没有绑定到特定的操作系统线程。虚拟线程仍然在操作系统线程上运行代码。但是,当虚拟线程中运行的代码调用阻塞I/O操作时,Java运行时会挂起虚拟线程,直到可以恢复为止。与挂起的虚拟线程相关联的OS线程现在可以自由地执行其他虚拟线程的操作。

虚拟线程的实现方式与虚拟内存类似。为了模拟大量内存,操作系统将大量虚拟地址空间映射到有限的RAM。同样,为了模拟大量线程,Java运行时将大量虚拟线程映射到少量操作系统线程。

与平台线程不同,虚拟线程通常有一个浅调用堆栈,只执行一个HTTP客户端调用或一个JDBC查询。尽管虚拟线程支持线程本地变量和可继承的线程本地变量,但您应该仔细考虑使用它们,因为单个JVM可能支持数百万个虚拟线程。

虚拟线程适用于运行大部分时间被阻塞的任务,这些任务通常等待I/O操作完成。然而,它们并不适用于长时间运行的CPU密集型操作。

为什么要使用虚拟线程?

在高吞吐量并发应用程序中使用虚拟线程,尤其是那些由大量并发任务组成、花费大量时间等待的应用程序。服务器应用程序是高吞吐量应用程序的示例,因为它们通常处理许多执行阻塞I/O操作(如获取资源)的客户端请求。

虚拟线程不是更快的线程;它们运行代码的速度并不比平台线程快。它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。

创建和运行虚拟线程

线程和线程。生成器API提供了创建平台线程和虚拟线程的方法。java.util.concurrent。Executors类还定义了创建ExecutorService的方法,该方法为每个任务启动一个新的虚拟线程。

话题

  • 使用线程类和线程创建虚拟线程。生成器界面
  • 使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程
  • 多线程客户端服务器示例

使用线程类和线程创建虚拟线程。生成器界面

调用Thread.ofVirtual()方法来创建Thread的实例。用于创建虚拟线程的生成器。

以下示例创建并启动一个打印消息的虚拟线程。它调用联接方法以等待虚拟线程终止。(这使您能够在主线程终止之前看到打印的消息。)

Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();

生成器界面允许您创建具有通用线程属性(如线程名称)的线程。线程。建设者OfPlatform子接口创建平台线程,而Thread。建设者OfVirtual创建虚拟线程。

以下示例使用thread创建一个名为MyThread的虚拟线程。生成器界面:

Thread.Builder builder = Thread.ofVirtual().name("MyThread");
Runnable task = () -> {System.out.println("Running thread");
};
Thread t = builder.start(task);
System.out.println("Thread t name: " + t.getName());
t.join();

以下示例使用Thread创建并启动两个 Thread.Builder

Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
Runnable task = () -> {System.out.println("Thread ID: " + Thread.currentThread().threadId());
};// name "worker-0"
Thread t1 = builder.start(task);   
t1.join();
System.out.println(t1.getName() + " terminated");// name "worker-1"
Thread t2 = builder.start(task);   
t2.join();  
System.out.println(t2.getName() + " terminated");

此示例打印类似于以下内容的输出:

Thread ID: 21
worker-0 terminated
Thread ID: 24
worker-1 terminated

使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程

执行器允许您将线程管理和创建与应用程序的其余部分分开。

以下示例使用Executors.newVirtualThreadPerTaskExecutor()方法创建ExecutorService。每当调用ExecutorService.submit(Runnable)时,都会创建并启动一个新的虚拟线程来运行任务。此方法返回Future的一个实例。请注意,方法Future.get()等待线程的任务完成。因此,此示例在虚拟线程的任务完成后打印一条消息。

try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));future.get();System.out.println("Task completed");// ...

多线程客户端服务器示例

以下示例由两个类组成。EchoServer是一个服务器程序,它侦听端口并为每个连接启动一个新的虚拟线程。EchoClient是一个连接到服务器并发送在命令行上输入的消息的客户端程序。

EchoClient创建一个套接字,从而连接到EchoServer。它在标准输入流上读取用户的输入,然后通过将文本写入套接字将文本转发到EchoServer。EchoServer通过插座将输入回波至EchoClient。EchoClient读取并显示从服务器传回的数据。EchoServer可以通过虚拟线程同时为多个客户端提供服务,每个客户端连接一个线程

public class EchoServer {public static void main(String[] args) throws IOException {if (args.length != 1) {System.err.println("Usage: java EchoServer <port>");System.exit(1);}int portNumber = Integer.parseInt(args[0]);try (ServerSocket serverSocket =new ServerSocket(Integer.parseInt(args[0]));) {                while (true) {Socket clientSocket = serverSocket.accept();// Accept incoming connections// Start a service threadThread.ofVirtual().start(() -> {try (PrintWriter out =new PrintWriter(clientSocket.getOutputStream(), true);BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {String inputLine;while ((inputLine = in.readLine()) != null) {System.out.println(inputLine);out.println(inputLine);}} catch (IOException e) { e.printStackTrace();}});}} catch (IOException e) {System.out.println("Exception caught when trying to listen on port "+ portNumber + " or listening for a connection");System.out.println(e.getMessage());}}
}
public class EchoClient {public static void main(String[] args) throws IOException {if (args.length != 2) {System.err.println("Usage: java EchoClient <hostname> <port>");System.exit(1);}String hostName = args[0];int portNumber = Integer.parseInt(args[1]);try (Socket echoSocket = new Socket(hostName, portNumber);PrintWriter out =new PrintWriter(echoSocket.getOutputStream(), true);BufferedReader in =new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));) {BufferedReader stdIn =new BufferedReader(new InputStreamReader(System.in));String userInput;while ((userInput = stdIn.readLine()) != null) {out.println(userInput);System.out.println("echo: " + in.readLine());if (userInput.equals("bye")) break;}} catch (UnknownHostException e) {System.err.println("Don't know about host " + hostName);System.exit(1);} catch (IOException e) {System.err.println("Couldn't get I/O for the connection to " +hostName);System.exit(1);} }
}

调度虚拟线程和固定虚拟线程

操作系统安排平台线程何时运行。但是,Java运行时会安排虚拟线程何时运行。当Java运行时调度虚拟线程时,它将虚拟线程分配或装载到平台线程上,然后操作系统照常调度该平台线程。这个平台线程被称为载体。在运行一些代码后,虚拟线程可以从其承载器中卸载。这种情况通常发生在虚拟线程执行阻塞I/O操作时。虚拟线程从其载体上卸载后,载体是空闲的,这意味着Java运行时调度程序可以在其上装载不同的虚拟线程。

当虚拟线程固定在其承载器上时,在阻塞操作期间无法卸载它。虚拟线程在以下情况下被固定:

  • 虚拟线程在同步块或方法内运行代码
  • 虚拟线程运行本机方法或外部函数(请参阅外部函数和内存API)

固定不会使应用程序出错,但可能会阻碍其可扩展性。通过修改频繁运行的同步块或方法,并使用java.util.concurrent.locks保护潜在的长I/O操作,尝试避免频繁和长期的固定。重新输入锁定。

调试虚拟线程

虚拟线程是静态线程;调试器可以像平台线程一样逐步完成它们。JDK Flight Recorder和jcmd工具具有其他功能,可以帮助您观察应用程序中的虚拟线程。

话题

  • JDK虚拟线程的飞行记录器事件
  • 查看jcmd线程转储中的虚拟线程

JDK虚拟线程的飞行记录器事件

JDK飞行记录器(JFR)可以发出以下与虚拟线程相关的事件:

  • jdk.VirtualThreadStart和jdk。VirtualThreadEnd指示虚拟线程何时开始和结束。默认情况下,这些事件处于禁用状态。
  • dk.VirtualThreadPinned 表示虚拟线程被固定(其承载线程未被释放)的时间超过阈值持续时间。此事件默认启用,阈值为20ms。
  • jdk.VirtualThreadSubmitFailed表示启动或取消标记虚拟线程失败,可能是由于资源问题。停放虚拟线程会释放底层承载线程来执行其他工作,而取消标记虚拟线程则会调度它继续执行。默认情况下会启用此事件。

使用按请求线程样式的阻塞I/O API编写简单的同步代码

虚拟线程可以显著提高以每请求线程方式编写的服务器的吞吐量,而不是延迟。在这种风格中,服务器将一个线程专门用于处理每个传入请求的整个持续时间。它至少专用一个线程,因为在处理单个请求时,您可能希望使用更多的线程来同时执行一些任务。

阻塞一个平台线程是昂贵的,因为它保留了线程——一种相对稀缺的资源——而它没有做太多有意义的工作。因为虚拟线程可能很多,所以阻塞它们是廉价的,也是受鼓励的。因此,您应该以直接的同步风格编写代码,并使用阻塞I/O API。

例如,以下代码以非阻塞异步风格编写,不会从虚拟线程中获得太多好处。

CompletableFuture.supplyAsync(info::getUrl, pool).thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofString())).thenApply(info::findImage).thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofByteArray())).thenApply(info::setImageData).thenAccept(this::process).exceptionally(t -> { t.printStackTrace(); return null; });

另一方面,以下代码以同步风格编写,并使用简单的阻塞IO,将受益匪浅:

try {String page = getBody(info.getUrl(), HttpResponse.BodyHandlers.ofString());String imageUrl = info.findImage(page);byte[] data = getBody(imageUrl, HttpResponse.BodyHandlers.ofByteArray());   info.setImageData(data);process(info);
} catch (Exception ex) {t.printStackTrace();
}

这样的代码也更容易在调试器中调试、在探查器中配置文件或使用线程转储进行观察。要观察虚拟线程,请使用jcmd命令创建一个线程转储:

jcmd <pid> Thread.dump_to_file -format=json <file>

以这种风格编写的堆栈越多,虚拟线程的性能和可观察性就越好。以其他风格编写的程序或框架,如果不为每个任务指定一个线程,就不应该期望从虚拟线程中获得显著的好处。避免将同步、阻塞代码与异步框架混合使用。

将每个并发任务表示为一个虚拟线程;从不池化虚拟线程

关于虚拟线程,最难内化的是,尽管它们与平台线程具有相同的行为,但它们不应该表示相同的程序概念。

平台线程是稀缺的,因此是一种宝贵的资源。需要管理宝贵的资源,而管理平台线程的最常见方式是使用线程池。然后您需要回答的一个问题是,池中应该有多少个线程?

但虚拟线程是丰富的,因此每个线程都不应该代表一些共享的、池化的资源,而是一个任务。线程从托管资源变成应用程序域对象。我们应该有多少虚拟线程的问题变得显而易见,就像我们应该使用多少字符串在内存中存储一组用户名的问题一样:虚拟线程的数量总是等于应用程序中并发任务的数量。

将n个平台线程转换为n个虚拟线程几乎没有好处;相反,需要转换的是任务。

要将每个应用程序任务表示为线程,请不要像下面的示例那样使用共享线程池执行器:

Future<ResultA> f1 = sharedThreadPoolExecutor.submit(task1);
Future<ResultB> f2 = sharedThreadPoolExecutor.submit(task2);
// ... use futures

相反,使用虚拟线程执行器,如以下示例所示:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {Future<ResultA> f1 = executor.submit(task1);Future<ResultB> f2 = executor.submit(task2);// ... use futures
}

代码仍然使用ExecutorService,但从Executors.newVirtualThreadPerTaskExecutor()返回的代码没有使用线程池。相反,它为每个提交的任务创建一个新的虚拟线程。

此外,ExecutorService本身是轻量级的,我们可以创建一个新的,就像处理任何简单的对象一样。这使我们能够依赖于新添加的ExecutorService.close()方法和try-with-resources构造。在try块结束时隐式调用的close方法将自动等待提交给ExecutorService的所有任务(即ExecutorServices派生的所有虚拟线程)终止。

对于输出场景,这是一个特别有用的模式,在输出场景中,您希望同时执行对不同服务的多个传出调用,如以下示例所示:

void handle(Request request, Response response) {var url1 = ...var url2 = ...try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {var future1 = executor.submit(() -> fetchURL(url1));var future2 = executor.submit(() -> fetchURL(url2));response.send(future1.get() + future2.get());} catch (ExecutionException | InterruptedException e) {response.fail(e);}
}String fetchURL(URL url) throws IOException {try (var in = url.openStream()) {return new String(in.readAllBytes(), StandardCharsets.UTF_8);}
}

您应该创建一个新的虚拟线程,如上所示,用于即使是小的、短暂的并发任务。

为了获得更多关于编写输出模式和其他具有更好可观察性的常见并发模式的帮助,请使用结构化并发。

根据经验,如果您的应用程序从来没有10000个或更多的虚拟线程,那么它就不太可能从虚拟线程中受益。要么它的负载太轻,需要更好的吞吐量,要么您没有向虚拟线程表示足够多的任务。

使用信号量限制并发

有时需要限制某个操作的并发性。例如,某些外部服务可能无法处理超过十个并发请求。由于平台线程是一种宝贵的资源,通常在池中进行管理,线程池变得如此普遍,以至于它们被用于限制并发性,如以下示例所示:

ExecutorService es = Executors.newFixedThreadPool(10);
...
Result foo() {try {var fut = es.submit(() -> callLimitedService());return f.get();} catch (...) { ... }
}

此示例确保对有限服务最多有十个并发请求。

但限制并发只是线程池操作的副作用。池是为了共享稀缺资源而设计的,虚拟线程并不稀缺,因此永远不应该被池化!

在使用虚拟线程时,如果您想限制访问某些服务的并发性,则应该使用专门为此目的设计的构造:Semaphore类。以下示例演示了此类:

Semaphore sem = new Semaphore(10);
...
Result foo() {sem.acquire();try {return callLimitedService();} finally {sem.release();}
}

碰巧调用foo的线程将被抑制,也就是说,被阻塞,这样一次只有十个线程可以取得进展,而其他线程将不受阻碍地进行业务。

简单地用信号量阻塞一些虚拟线程似乎与将任务提交到固定线程池有很大不同,但事实并非如此。将任务提交给线程池会使它们排队等待稍后执行,但信号量在内部(或任何其他阻塞同步结构)创建一个被阻塞的线程队列,该队列反映了等待池线程执行它们的任务队列。由于虚拟线程是任务,因此生成的结构是等效的:

尽管您可以将平台线程池视为处理从队列中提取的任务的工作线程,将虚拟线程视为任务本身,直到它们可以继续,但计算机中的底层表示实际上是相同的。识别排队任务和阻塞线程之间的等效性将有助于充分利用虚拟线程。

数据库连接池本身就是一个信号灯。限制为十个连接的连接池将阻止第十一个线程尝试获取连接。不需要在连接池的顶部添加额外的信号量。

不要在线程本地变量中缓存昂贵的可重用对象

虚拟线程和平台线程一样支持线程本地变量。有关详细信息,请参阅线程本地变量(thread local variables)。通常,线程局部变量用于将一些特定于上下文的信息与当前运行的代码相关联,例如当前事务和用户ID。这种线程局部变量的使用对于虚拟线程来说是完全合理的。但是,请考虑使用更安全、更高效的作用域值。有关详细信息,请参阅作用域值。

线程局部变量的另一种用法与虚拟线程根本不同:缓存可重用对象。这些对象的创建成本通常很高(并且会消耗大量内存),是可变的,并且不是线程安全的。它们被缓存在线程局部变量中,以减少它们被实例化的次数和内存中的实例数量,但它们会被线程上不同时间运行的多个任务重用。

例如,SimpleDateFormat的实例的创建成本很高,而且不是线程安全的。出现的一种模式是将这样的实例缓存在ThreadLocal中,如以下示例所示:

static final ThreadLocal<SimpleDateFormat> cachedFormatter = ThreadLocal.withInitial(SimpleDateFormat::new);void foo() {...cachedFormatter.get().format(...);...
}

只有当线程(以及缓存在线程本地中的昂贵对象)被多个任务共享和重用时,这种缓存才有帮助,就像平台线程被池化时一样。许多任务在线程池中运行时可能会调用foo,但由于线程池只包含几个线程,因此对象只会实例化几次——每个池线程一次——缓存并重用。

然而,虚拟线程从不被池化,也从不被不相关的任务重用。因为每个任务都有自己的虚拟线程,所以来自不同任务的每次对foo的调用都会触发新SimpleDateFormat的实例化。此外,由于可能有大量虚拟线程同时运行,因此昂贵的对象可能会消耗相当多的内存。这些结果与线程本地缓存所要实现的结果正好相反。

没有单一的通用替代方案可供选择,但在SimpleDateFormat的情况下,您应该将其替换为DateTimeFormatter。DateTimeFormatter是不可变的,因此所有线程都可以共享一个实例:

static final DateTimeFormatter formatter = DateTimeFormatter….;void foo() {...formatter.format(...);...
}

请注意,使用线程局部变量缓存共享的昂贵对象有时是由异步框架在幕后完成的,因为异步框架隐含地假设它们由极少数池线程使用。这就是为什么混合虚拟线程和异步框架不是一个好主意的原因之一:对方法的调用可能会导致在线程本地变量中实例化旨在缓存和共享的代价高昂的对象。

避免长时间和频繁的固定

当前虚拟线程实现的一个限制是,在同步块或方法内部执行阻塞操作会导致JDK的虚拟线程调度程序阻塞宝贵的操作系统线程,而如果阻塞操作在同步块和方法之外执行,则不会阻塞。我们称这种情况为“钉扎”。如果阻塞操作是长期且频繁的,则固定可能会对服务器的吞吐量产生不利影响。保护短暂的操作,如内存中的操作,或具有同步块或方法的不频繁操作,应该不会产生不利影响。

为了检测可能有害的钉扎实例,(JDK飞行记录器(JFR)会发出JDK。VirtualThreadPined线程当锁定了阻塞操作时;默认情况下,当操作耗时超过20ms时,会启用此事件。

或者,可以使用系统属性jdk.tracePinnedThreads在线程被固定时阻塞时发出堆栈跟踪。使用选项Djdk.tracePinnedThreads=full运行时,当线程在固定时发生阻塞时,将打印完整的堆栈跟踪,突出显示本地帧和持有监视器的帧。使用选项Djdk.tracePinnedThreads=short运行会将输出仅限于有问题的帧。

如果这些机制检测到固定既长时间又频繁的位置,请在这些特定位置使用synchronized with ReentrantLock来替换(同样,在保护短时间或不频繁操作的位置,无需替换synchronized)。以下是同步化块的长期频繁使用示例。

synchronized(lockObj) {frequentIO();
}
lock.lock();
try {frequentIO();
} finally {lock.unlock();
}

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

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

相关文章

针对BSV区块链新推出的网络访问规则NAR和警报系统AS的解释与问答

​​发表时间&#xff1a;2024年2月22日 BSV区块链社区团队最近开设了一个Twitter&#xff08;X&#xff09;话题空间&#xff0c;讨论BSV区块链协会最新推出的网络访问规则和警报系统的相关问题。 本次讨论由BSV区块链社区负责人Brett Banfe主持&#xff0c;以便社区成员更好…

刷题DAY29 | LeetCode 491-递增子序列 46-全排列 47-全排列 II

491 递增子序列&#xff08;medium&#xff09; 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也…

综合练习(python)

前言 有了前面的知识积累&#xff0c;我们这里做两个小练习&#xff0c;都要灵活运用前面的知识。 First 需求 根据美国/英国各自YouTube的数据&#xff0c;绘制出各自的评论数量的直方图 第一版 import numpy as np from matplotlib import pyplot as plt import matplo…

matlab中Signal Editor定义梯形信号输出矩形信号

matlab中Signal Editor定义梯形信号输出矩形信号&#xff0c;可以通过如下勾选差值数据实现梯形信号输出。

文件路径中‘/’与‘\’用法详解,与等效使用方法介绍

1、两种符号详解 在数据处理时&#xff0c;使用C或python语言读入数据时&#xff0c;涉及到文件路径的输入&#xff0c;文件路径在windows下&#xff0c;默认形式为但斜线‘\’&#xff0c;如下图&#xff1a; 若输入路径时&#xff0c;直接写成如下形式&#xff1a;“E:\codin…

JMeter 二次开发之环境准备

通过JMeter二次开发&#xff0c;可以充分发挥JMeter的潜力&#xff0c;定制化和扩展工具的能力以满足具体需求。无论是开发自定义插件、函数二次开发还是定制UI&#xff0c;深入学习和掌握JMeter的二次开发技术&#xff0c;将为接口功能测试/接口性能测试工作带来更多的便利和效…

10:00面试,10:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

Keil笔记(缘更)

Keil 一、使用Keil时可能会出现的问题1.Project框不见了2.添加文件时找不到3.交换文件位置4.main.c测试报1 warning5.搜索CtrlF 二、STLINK点灯操作1.配置寄存器进行点灯2.使用库函数进行点灯 3.GPIO1.LED闪烁4.按键控制LED 注&#xff1a; 一、使用Keil时可能会出现的问题 1.…

KVM 集成 OpenvSwitch 虚拟交换机

KVM 集成 OpenvSwitch 虚拟交换机 KVM(Kernel-based Virtual Machine)是Linux内核中的一种虚拟化技术&#xff0c;它允许在同一台主机上运行多个虚拟机。 在默认情况下&#xff0c;KVM使用基于Linux bridge的网络虚拟化解决方案。Linux bridge是一种内核模块&#xff0c;可将…

网络编程——预备知识

网络编程——预备知识 &#x1f343;套接字&#x1f33f;什么是套接字&#x1f33f;套接字的类型&#x1f33f;套接字的位置 &#x1f343;IP&#x1f343;端口号Port&#x1f343;字节序&#x1f343;地址信息结构&#xff08;结构体类型&#xff09; &#x1f343;套接字 &a…

【Python】: Django Web开发实战(详细教程)

Python Django全面介绍 Django是一个非常强大的Python Web开发框架&#xff0c;它以"快速开发"和"干净、实用的设计"为设计宗旨。本文将从Django的基本概念开始&#xff0c;逐渐引导大家理解如何使用Django构建复杂的web应用程序。 Django基本概念与原理…

浅谈前端路由原理hash和history

1、认识前端路由 本质 前端路由的本质&#xff0c;是监听 url 地址或 hash 值的改变&#xff0c;来切换渲染对应的页面组件 前端路由分为两种模式 hash 模式 history 模式 两种模式的对比 2、hash 模式 &#xff08;1&#xff09;hash 定义 hash 模式是一种把前端路由的路…

【MySQL】数据库的基础概念

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习计网、mysql和算法 ✈️专栏&#xff1a;MySQL学习 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac…

【教程】rax3000m emmc刷机 支持硬件QOS MT7981到底值不值

为什么选择rax3000m&#xff1f; 1、恩山论坛237大佬放出了硬件QOS功能&#xff0c;而很多几百元路由器一旦开启QOS就会变软件NAT走CPU转发&#xff0c;效果还不如x86软路由。这样就非常适合刷机&#xff0c;在家里跑pt、迅雷等任务时候不会卡顿&#xff0c;实测&#xff0c;丢…

智慧公厕:卫生、便捷、安全的新时代厕所变革

在城市快速发展的背景下&#xff0c;公共厕所的建设和管理变得越来越重要。智慧公厕作为厕所变革的一项全新举措&#xff0c;通过建立公共厕所全面感知监测系统&#xff0c;以物联网、互联网、大数据、云计算、自动化控制技术为支撑&#xff0c;实现对公共厕所的智能化管理和运…

Fabric.js在vue2中使用

Fabric.js安装 这里我是基于vue来使用的&#xff0c;先安装上Fabric.js npm install fabric 在main.js中 import fabric from fabric Vue.use(fabric);Fabric 提供了 7 种基础形状&#xff1a; fabric.Circle (圆)fabric.Ellipse (椭圆)fabric.Line (线)fabric.Polyline (多条…

camunda 与 pycamunda学习

camunda 与 pycamunda 相关链接&#xff1a; camunda 官方社区&#xff1a;https://docs.camunda.org/manual/7.17/ 官方社区提供的REST_API:https://docs.camunda.org/manual/7.17/reference/rest/ GITHUB 社区&#xff1a;https://github.com/camunda-community-hub Git…

18.WEB渗透测试--抓包技术(上)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;17.WEB渗透测试--Kali Linux(五)-CSDN博客 网站协议抓包 1.BurpSuite Burp Suite 是用…

makefile基础与实战编译C++项目

从源码到执行程序 makefile运行流程 &#xff1a;这个符号用于在执行的命令之前&#xff0c;通常会告诉make不要输出命令本身&#xff0c;只输出命令的结果。但是当它位于命令行的开头时&#xff0c;它通常会让Make静默执行该命令&#xff0c;即不在命令行中显示该命令&#xf…

学习笔记-华为IPD转型2020:3,IPD的实施

3. IPD的实施 1999 年开始的 IPD 转型是计划中的多个转型项目中的第一个&#xff08;Liu&#xff0c;2015&#xff09;。华为为此次转型成立了一个专门的团队&#xff0c;从大约20人开始&#xff0c;他们是华为第一产业的高层领导。董事会主席孙雅芳是这个团队的负责人。该团…