从0开始深入理解并发、线程与等待通知机制(上)含大厂面试题

news2024/11/18 5:51:47

目录

一,基础概念

        进程与线程

        进程(就是一代代码的执行程序,程序的实例)

        线程

        大厂面试题:进程间的通信

        CPU 核心数和线程数的关系

        上下文切换(Context switch)

        并行和并发

二,认识 Java 里的线程 

        Java 程序天生就是多线程的

        线程的启动与中止

        启动

面试题:新启线程有几种方式?

        中止

        深入理解 run()和 start()


一,基础概念

        在正式学习 Java 的并发编程之前,还有几个并发编程的基础概念我们需要熟悉和学习。

        进程与线程

        进程(就是一代代码的执行程序,程序的实例)

        我们常听说的是应用程序,也就是 app,由指令和数据组成。但是当我们不 运行一个具体的 app 时,这些应用程序就是放在磁盘(也包括 U 盘、远程网络 存储等等)上的一些二进制的代码。一旦我们运行这些应用程序,指令要运行, 数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中 还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令,管理 内存、管理 IO 的。

        当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

        站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小单位。

        线程

        一个机器中肯定会运行很多的程序,CPU 又是有限的,怎么让有限的 CPU 运行这么多程序呢?就需要一种机制在程序之间进行协调,也就所谓 CPU 调度。

        线程则是 CPU 调度的最小单位。

        线程必须依赖于进程而存在,线程是进程中的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,,只拥有在运行中必不可少的资源(如程序计数器,一组寄存器和栈), 但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。一个进程可以拥有多个线程,一个线程必须有一个父进程。线程,有时也被称为轻量级进程 (Lightweight Process,LWP),早期 Linux 的线程实现几乎就是复用的进程,后来 才独立出自己的 API

        java线程无处不在

        Java 中不管任何程序都必须启动一个 main 函数的主线程(代码如下); Java Web 开发里 面的定时任务、定时器、JSP Servlet、异步消息处理机制,远程访问接口 RM , 任何一个监听事件,onclick 的触发事件等都离不开线程和并发的知识。

package com.laoyang;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * @author:Kevin
 * @create: 2023-10-04 13:27
 * @Description: 线程工厂
 */

public class day1thread1 {
    public static void main(String[] args) {
        //java 虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //获取线程和线程堆信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        //遍历所有的线程信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
        }

    }
}

如图会发现java启动时会启动6个线程,当然如果对某个线程感兴趣,我们将在文章下面会详细介绍

        大厂面试题:进程间的通信

        同一台计算机的进程通信称为 IPCInter-process communication),不同计算机之间的进程通信被称为 R(mote)PC,需要通过网络,并遵守共同的协议,比如大家熟悉的 Dubbo 就是一个 RPC 框架,而 Http协议也经常用在 RPC 上,比如 SpringCloud 微服务。

        大厂常见的面试题就是,进程间通信有几种方式?

        1. 管道,分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用

于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外,
它还允许无亲缘关系进程间的通信。
        
        2. 信号 signal ):信号是在软件层次上对中断机制的一种模拟,它是比较
复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器
收到一个中断请求效果上可以说是一致的。
        
        3. 消息队列 message queue ):消息队列是消息的链接表,它克服了上两
种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息
队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
        4. 共享内存(shared memory):可以说这是最有用的进程间通信方式。它
使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共
享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

        

        5. 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间

得同步和互斥手段。
        
        6. 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络
中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用
Unix domain socket (比如同一机器中 MySQL 中的控制台 mysql shell MySQL
务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验
和、维护序号和应答等,比纯粹基于网络的进程间通信肯定效率更高。
        CPU 核心数和线程数的关系

        前面说过,目前主流 CPU 都是多核的,线程是 CPU 调度的最小单位。同一 时刻,一个 CPU 核心只能运行一个线程,也就是 CPU 内核和同时运行的线程数 是 1:1 的关系,也就是说 8 CPU 同时可以执行 8 个线程的代码。但 Intel 引入超线程技术后,产生了逻辑处理器的概念使核心数与线程数形成 1:2 的关系。 在我们前面的 Windows 任务管理器贴图就能看出来,内核数是 6 而逻辑处理器数是 12

       在 Java 中提供了 Runtime.getRuntime().availableProcessors(),可以让我们获 取当前的 CPU 核心数,注意这个核心数指的是逻辑处理器数。一般用于并发调优使用!

        获得当前的 CPU 核心数在并发编程中很重要,并发编程下的性能优化往往 和 CPU 核心数密切相关。

        上下文切换(Context switch

        概念:就是操作系统将一个线程或者进程从CPU上调度进来或者调度出去的时候,需要把我们的线程或者进程的相关数据要单独保存,或者重新载入的过程。 

        CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换。

        上下文是 CPU 寄存器和程序计数器在任何时间点的内容。

        上下文切换可以更详细地描述为内核(即操作系统的核心)CPU 上的进程 (包括线程)执行以下活动:

        1. 暂停一个进程的处理,并将该进程的 CPU 状态(即上下文)存储在内存中的

某个地方
        2. 从内存中获取下一个进程的上下文,并在 CPU 的寄存器中恢复它
        3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。

        从数据来说,以程序员的角度来看, 是方法调用过程中的各种局部的变量与资源;

        以线程的角度来看, 是方法的调用栈中存储的各类信息。

        引发上下文切换的原因一般包括:线程、进程切换、系统调用等等。上下文切换通常是计算密集型的,因为涉及一系列数据在各种寄存器、 缓存中的来回 拷贝。就CPU 时间而言,一次上下文切换大概需要 5000~20000 个时钟周期,相对一个简单指令几个乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。

        并行和并发

        并发 Concurrent:指应用能够交替执行不同的任务,比如单 CPU 核心下执行多 线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉 到的速度不断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度太快,我们无法察觉到而已

        并行 Parallel:指应用能够同时执行不同的任务,:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行

        两者区别:一个是交替执行,一个是同时执行,如下图所示。

二,认识 Java 里的线程 

        Java 程序天生就是多线程的

        一个 Java 程序从 main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main()方法的是一个名称为 main 的线程。

        而一个 Java 程序的运行就算是没有用户自己开启的线程,实际也有有很多JVM 自行启动的线程,一般来说有:

[6] Monitor Ctrl-Break       // 监控 Ctrl-Break 中断信号的
[5] Attach Listener         //内存 dump ,线程 dump ,类信息统计,获取系统属性等
[4] Signal Dispatcher         // 分发处理发送给 JVM 信号的线程
[3] Finalizer         // 调用对象 finalize 方法的线程
[2] Reference Handler        //清除 Reference 的线程
[1] main         //main 线程,用户程序入口
尽管这些线程根据不同的 JDK 版本会有差异,但是依然证明了 Java 程序天生就是多线程的。
        线程的启动与中止
        启动
package com.laoyang.Thread;

/**
 * @author:Kevin
 * @create: 2023-10-04 14:45
 * @Description: 新线程启动
 */

public class day1threadstart {

    /*扩展自Thread类*/
    private static class UseThread extends Thread{
        @Override
        public void run() {
            super.run();
            System.out.println("i am extend thread");
        }
    }

    /*实现接口*/
    private static class UseRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("i am implements thread");
        }
    }

    public static void main(String[] args) {
        UseThread useThread = new UseThread();
        useThread.start();

        Thread thread = new Thread(new UseRunnable());
        thread.start();

    }


}

        Thread 和 Runnable 的区别

        Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)的抽象。 Thread 可以接受任意一个 Runnable 的实例并执行。

        但是我们发现不管是继承Thread或者实现Runnable接口时,都没有返回值。所以出现了实现Callable<泛型>Future FutureTask

        Callable<泛型>:位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明 了一个方法,只不过这个方法叫做 call(),这是一个泛型接口,call()函数返回的类型就是传递进来的 V 类型。但是这个是异步的,不知道什么时候结束,所以就出现了Future

         Future:就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回果。
        
        因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就有了
下面的 FutureTask
        
        

        FutureTask:FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

        

        因此我们通过一个线程运行 Callable ,但是 Thread 不支持构造方法中传递Callable 的实例,所以我们需要通过 FutureTask 把一个 Callable 包装成 Runnable ,然后再通过这个 FutureTask 拿到 Callable 运行后的返回值。
package com.laoyang.Thread;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author:Kevin
 * @create: 2023-10-04 15:31
 * @Description: 演示通过callable 形式的线程启动
 */

/**
 *类说明:演示Future等的使用
 */
public class UseFuture {


    /*实现Callable接口,允许有返回值*/
    private static class UseCallable implements Callable<Integer>{
        private int sum;
        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");
//			Thread.sleep(1000);
            for(int i=0 ;i<5000;i++){
                if(Thread.currentThread().isInterrupted()) {
                    System.out.println("Callable子线程计算任务中断!");
                    return null;
                }
                sum=sum+i;
                System.out.println("sum="+sum);
            }
            System.out.println("Callable子线程计算结束!结果为: "+sum);
            return sum;
        }
    }

    public static void main(String[] args)
            throws InterruptedException, ExecutionException {

        UseCallable useCallable = new UseCallable();
        //包装
        FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
        Random r = new Random();
        new Thread(futureTask).start();

        Thread.sleep(1);
        if(r.nextInt(100)>50){
            System.out.println("Get UseCallable result = "+futureTask.get());
        }else{
            System.out.println("Cancel................. ");
            futureTask.cancel(true);
        }

    }

}
面试题:新启线程有几种方式?

        这个问题的答案其实众说纷纭,有 2 种,3 种,4 种等等答案,建议比较好的回答是:

        按照 Java 源码中 Thread 上的注释:

        官方说法是在 Java 中有两种方式创建一个线程用以执行,一种是派生自 Thread 类,另一种是实现 Runnable 接口。

        当然本质上 Java 中实现线程只有一种方式,都是通过 new Thread()创建线程 对象,调用 Thread#start 启动线程。

        至于基于 callable 接口的方式,因为最终是要把实现了 callable 接口的对象通过 FutureTask 包装成 Runnable ,再交给 Thread 去执行,所以这个其实可以和 实现 Runnable 接口看成同一类。
        而线程池的方式,本质上是池化技术,是资源的复用,和新启线程没什么关系。
        所以,比较赞同官方的说法,有两种方式创建一个线程用以执行。
        中止

        线程自然终止

        要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

        stop

        暂停、恢复和停止操作对应在线程 Thread API 就是 suspend()resume() stop()。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主 要有:

        以 suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。(典型的占着茅坑不拉屎

        stop()方 法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

        中断(安全终止的方式)

        安全的中止则是其他线程通过调用某个线程 A interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表 线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。线程通过检查自身的中断标志位是否被置为 true 来进行响应,

        线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法         Thread.interrupted()来进行判断当前线程是否被中断,不过 Thread.interrupted()会同时将中断标识位改写为 false

package com.laoyang.Thread;

/**
 *类说明:如何安全中断线程
 */
public class EndThread {
	
	private static class UseThread extends Thread{

		private boolean cancel;

		public UseThread(String name) {
			super(name);
		}

		public void setCancel(boolean cancel) {
			this.cancel = cancel;
		}

		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			System.out.println(threadName+" interrrupt flag ="+isInterrupted());
			while(!isInterrupted()){
				//Thread.sleep();

			//while(!Thread.interrupted()){
//			while(true){
				System.out.println(threadName+" is running");
				System.out.println(threadName+"inner interrrupt flag ="
						+isInterrupted());
			}
			//System.out.println(threadName+" interrrupt flag ="+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		UseThread endThread = new UseThread("endThread");
		endThread.start();
		Thread.sleep(20);
		endThread.interrupt();//中断线程,其实设置线程的中断标识位=true

	}

}

        如果一个线程处于了阻塞状态(如线程调用了 thread.sleepthread.join、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在 这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false

        不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,

        一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,

        二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
        注意:处于死锁状态的线程无法被中断
        深入理解 run()start()

        Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。 只有执行了 start()方法后,才实现了真正意义上的启动线程。

        从 Thread 的源码可以看到,Thread start 方法中调用了 start0()方法,而start0()是个 native 方法,这就说明 Thread#start 一定和操作系统是密切相关的。

        start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现 的 run()方法,start()方法不能重复调用,如果重复调用会抛出异常(注意,此处可能有面试题:多次调用一个线程的 start 方法会怎么样?)。

        而 run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。多次调用run方法时就是主线程执行的。

okk,本此文章就先记录在这里,继续加油。

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

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

相关文章

SwiftUI 4.0:两种方式实现子视图导航功能

0. 概览 从 SwiftUI 4.0 开始&#xff0c;觉悟了的苹果毅然抛弃了已“药石无效”的 NavigationView&#xff0c;改为使用全新的 NavigationStack 视图。 诚然&#xff0c;NavigationStack 从先进性来说比 NavigationView 有不小的提升&#xff0c;若要如数家珍得单开洋洋洒洒…

Leetcode---114双周赛

题目列表 2869. 收集元素的最少操作次数 2870. 使数组为空的最少操作次数 2871. 将数组分割成最多数目的子数组 2872. 可以被 K 整除连通块的最大数目 一、收集元素的最小操作次数 直接模拟&#xff0c;倒序遍历即可&#xff0c;代码如下 class Solution { public:int mi…

ROS(5)PX4仿真安装及运行

1、配置&#xff0c;提升下载速度 启动 $ cd clash-for-linux$ sudo bash start.sh$ source /etc/profile.d/clash.sh$ proxy_on 关闭 $ cd clash-for-linux$ sudo bash shutdown.sh$ proxy_off 2、安装PX4开源无人机 git clone https://github.com/PX4/PX4-Autopilot.git…

全栈开发笔记1:首个项目的收获

本文为编程导航实战项目学习笔记。 文章目录 7.跨域问题解决 2023.10.26.项目部署 2023.10.15.统一处理返回值 2023.10.14.开发注册和用户管理 2023.09303.开发登陆注册接口 2023.09.172.数据库设计1.前后端初始化 2023.9.16 7.跨域问题解决 2023.10.2 三种方式&#xff1a; …

ps插件:alpaca增效工具 (完美替代AI创成式填充) 2.8.1 中文版

Alpaca是一个Photoshop插件&#xff0c;提供了多种功能&#xff0c;帮助用户更高效地进行图像处理和设计。可以进行模型训练并无缝地融入图像中。同时还提供文本到图像的生成、图像到图像的变化、涂色、放大、深度图创建等功能&#xff0c;极大地提升了设计和艺术创作的效率和创…

Go,从命名开始!Go的关键字和标识符全列表手册和代码示例!

目录 一、Go的关键字列表和分类介绍关键字在Go中的定位语言的基石简洁与高效可扩展性和灵活性 关键字分类声明各种代码元素组合类型的字面表示基本流程控制语法协程和延迟函数调用 二、Go的关键字全代码示例关键字全代码示例 三、Go的标识符定义基础定义特殊规定关键字与标识符…

【Aseprite像素画】如何取巧做到各种画面效果(小工具的各种技巧)

文章目录 参考链接&#xff1a;具体如下1、水中倒影2、参考图片3多个帧添加动画物品4多个帧删除动画物品5六毛钱受击效果6添加标签7导出特定标志的gif图8忽略标志帧&#xff0c;然后播放9轮廓线10多个图层轮廓线11洋葱皮12替换多个不同帧的色块簇13连接细胞14快速连续删除15冻结…

战火使命兑换码最新,战火使命礼包码

战火使命手游是一款二次元卡牌游戏&#xff0c;玩家可以通过使用兑换码来获取礼包奖励。如果你还不知道如何获取兑换码&#xff0c;下面为你提供最新的礼包码合集。 关注【娱乐天梯】&#xff0c;获取内部福利号 战火使命兑换码最新&#xff1a; 1、兑换码&#xff1a;ZHSM0421…

安装matplotlib_

安装pip 安装matplotlib 安装完毕 导入出现bug......

C++算法 —— 动态规划(10)二维费用背包

文章目录 1、动规思路简介2、一和零3、盈利计划 背包问题需要读者先明白动态规划是什么&#xff0c;理解动规的思路&#xff0c;并不能给刚接触动规的人学习。所以最好是看了之前的动规博客&#xff0c;以及两个背包博客&#xff0c;或者你本人就已经懂得动规了。 1、动规思路简…

弧度、圆弧上的点、圆的半径(r)、弧长(s)之间的关系

要计算弧度和圆弧上的点&#xff0c;需要知道以下几个要素&#xff1a; 圆的半径&#xff08;r&#xff09;&#xff1a;即圆的中心到圆周上任意一点的距离。 弧长&#xff08;s&#xff09;&#xff1a;从圆周上的一个点到另一个点所经过的弧长。 弧度&#xff08;θ&#x…

为什么Spring不建议使用基于字段的依赖注入

在我们通过IDEA编写Spring的代码的时候&#xff0c;假如我们编写了如下代码&#xff1a; IDEA会给我们一个warning警告&#xff1a; 翻阅官方文档&#xff1b;我们会发现&#xff1a; 大意就是强制依赖使用构造器注入&#xff0c;可选依赖使用setter注入那么这是为什么呢&am…

App测试时常用的adb命令你都掌握了哪些呢?

adb 全称为 Android Debug Bridge&#xff08;Android 调试桥&#xff09;&#xff0c;是 Android SDK 中提供的用于管理 Android 模拟器或真机的工具。 adb 是一种功能强大的命令行工具&#xff0c;可让 PC 端与 Android 设备进行通信。adb 命令可执行各种设备操作&#xff0…

【服务器】在 Linux CLI 下安装 Anaconda

【服务器】在 Linux CLI 下安装 Anaconda 1 系统环境2 下载安装包3 安装 1 系统环境 查看系统信息 cat /etc/os-release2. 查看架构 uname -a # output # Linux localhost.localdomain 4.18.0-193.28.1.el8_2.x86_64 #1 SMP Thu Oct 22 00:20:22 UTC 2020 x86_64 x86_64 x86…

4.Tensors For Beginners-Vector Definition

在上一节&#xff0c;已经了解了前向和后向转换。 什么是向量&#xff1f; 定义1&#xff1a;向量是一个数字列表 这很简洁&#xff0c;也通俗易懂。 现有两个向量&#xff1a; 如果要把这两个向量给加起来&#xff0c;只需把对应位置的元素(组件)给加起来。 而要缩放向量&…

angularjs开发环境搭建

Angularjs是一个前端页面应用开发框架&#xff0c;其使用TypeScript作为开发语言&#xff0c;Angularjs的特性包括&#xff0c;使用组件、模板以及依赖注入的开发框架构建可扩展的web应用&#xff0c;使用易于集成的类库支持页面路由、页面表单、前后端接口交互等各种不同特性&…

MySQL5.7版本与8.0版本在CentOS系统安装

目录 前置要求 1. MySQL5.7版本在CentOS系统安装 1.1 安装 1.1.1 配置yum仓库 1.1.2 使用yum安装MySQL 1.1.3 安装完成后&#xff0c;启动MySQL并配置开机自启动 1.1.4 检查MySQL的运行状态 1.2 配置 1.2.1 获取MySQL的初始密码 1.2.2 登陆MySQL数据库系统 …

Python爬取诗词名句网中三国演义的乱码问题

一、乱码问题 为解决中文乱码问题&#xff0c;可使用chardet.detect()检测文本编码格式 详细&#xff1a; Python爬虫解决中文乱码_脑子不好真君的博客-CSDN博客 二、代码 #爬取三国演义 import requests import chardet from bs4 import BeautifulSoupurlhttps://www.shicim…

【Vue3】自定义指令

除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 1. 生命周期钩子函数 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。 在 <script …

多通道反向字典模型

方法 将单词的definition embedding输入Bi-LSTM模型&#xff0c;经过处理得到5个分数并加权求和得到最终的置信分数 最后对分数向量进行降序排序&#xff0c;得到word rank 代码实现&#xff1a; _, indices torch.sort(score, descendingTrue) 辅助信息 这是AAAI 2020的论…