并发编程笔记7--并发编程基础

news2024/11/25 15:54:51

1、线程简介

1.1、什么是线程

现代操作系统中运行一个程序,会为他创建一个进程。而每一个进程中又可以创建许多个线程。现代操作系统中线程是最小的调度单元。
两者关系:一个线程只属于一个进程,而一个进程可以拥有多个线程。线程是一个轻量级的进程
一个Java程序从main方法开始执行,然后按照既定的顺序执行代码,看上去没有其他线程参与。但实际上Java程序天生就是多线程程序。我们可以通过JMX来查看一个普通的Java程序包括哪些线程。示例代码如下:

public class MultiThread {

    public static void main(String[] args) {
        ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(true,true);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName());
        }
    }
}

上面代码执行结果如下:
image.png
从上图可以看到,一个Java程序运行不仅是main方法的运行,而是main进程和一些其他进程的同时运行。

1.2、为什么要使用多线程

  1. 更多的处理器核心:现代计算机大多是多核处理器,一个程序作为一个进程在处理器上运行。程序运行过程中可以创建多个线程,而一个线程只能在一个处理器核心上。试想一下,一个单线程程序在运行时只能使用一个处理器核心,那么在多的处理器核心加入也不会提升程序的运行效率。相反如果采用多线程技术,那么运行在多核处理器上就会显著提升运行效率。
  2. 更快的相应时间
  3. 更好的编程模型

**

1.3、线程优先级

在《Java并发编程的艺术》的一书中,作者的实验代码的结果得出的结论是线程的优先级不能作为程序正确性的依赖,操作系统完全可以不用理会Java代码对优先级的设置(作者环境为jdk1.7+Mac OS X10.10)。但是我自己跑出的结果可以看到代码中设置的优先级是有用的(本人环境jdk1.8+win10)。实例代码如下:

public class Priority {

    private static volatile boolean notStart=true;
    private static volatile boolean notEnd=true;

    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs=new ArrayList<>();

        for (int i=0;i<10;i++){
            int priority=i<5?Thread.MIN_PRIORITY:Thread.MAX_PRIORITY;
            Job job=new Job(priority);

            jobs.add(job);

            Thread thread=new Thread(job,"Thread:"+i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart=false;
        TimeUnit.SECONDS.sleep(10);
        notEnd=false;
        for (Job job:jobs){
            System.out.println("Job priority:"+job.priority+" count:"+job.jobCount);
        }
    }

    static class Job implements Runnable{

        private int priority;
        private long jobCount;
        Job(int priority){
            this.priority=priority;
        }


        @Override
        public void run() {
            while (notStart){
                Thread.yield();
            }
            while (notEnd){
                Thread.yield();
                jobCount++;
            }
        }
    }
}

本人跑出的结果为:
image.png

从上图可以看到优先级为10的结果和优先级为1的结果差距是挺大的。至于为什么会和作者的结论不一样,尚未搞清楚。

1.4、线程的状态

线程在运行过程中可能处于以下6种状态中的某一个,在给定的一个时刻,线程只能处于其中的一个状态。

状态名称说明
NEW初始状态,线程被构建,但是还未调用start方法
RUNNABLE运行状态,线程调用start方法后,Java线程将操作系统中的就绪和运行两种状态笼统的成为运行中
BLOCKED阻塞状态,表示线程阻塞与锁
WAITING等待状态,进入该状态的线程需要等待其他线程做出一些特定的动作(通知,中断)
TIME_WAITING超时等待,表示线程等待指定的时间之后自动唤醒,继续执行
TERMINATED终止状态,表示当前线程已经执行完毕

我们可以通过jstack命令来查看线程的执行状态。示例代码如下:

public class ThreadState {

    //改该程一直睡眠
    static class TimeWaiting implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //该线程一直等待
    static class Waiting implements Runnable{

        @Override
        public void run() {
            while (true){
                synchronized (Waiting.class){
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class Blocked implements Runnable{

        @Override
        public void run() {
            synchronized (Blocked.class){
                while (true){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();
        new Thread(new Blocked(),"BlockedThread-1").start();
        new Thread(new Blocked(),"BlockedThread-2").start();
    }
}

1、先通过jps命令来查看线程pid
2、通过jstack pid来查看线程的状态

image.png
image.png
image.png
image.png

java线程变迁图如下:
image.png
从上图可以看到,当线程被创建之后,执行start方法之后线程处于运行状态。当线程执行wait方法之后,线程处于等待状态,这时此线程需要其他线程唤醒才能继续执行(notify和notifyAll)。而超时等待状态是在等待状态的基础上加了等待超时机制,也就是说线程在等待指定的时间后,就会自动回到执行状态,不需要其他线程唤醒。当线程调用同步方法时,在没有获取到锁的情况下,线程会被阻塞住,此时线程处于阻塞状态。当线程执行run方法之后,就处于终止状态了。
阻塞状态是线程阻塞在进入由synchronize修饰的方法或者代码块时的状态。但是阻塞状态在java.concurrent包中的Lock接口上的状态是等待状态。那是因为java.concurrent包中Lock接口对阻塞的实现均使用了LockSupport类中的方法。

1.5、Daemon线程

Daemon线程是一种支持型线程,主要用作程序中后台调度以及支持性工作。在JVM中,当不存在非Daemon线程时,JVM将会退出。
Daemon线程时支持型线程,当JVM退出时,Daemon线程中的finally块并不一定会执行。实例代码如下:

public class Daemon {

    static class DaemonRunner implements Runnable{

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println("DaemonThread finally run.");
            }
        }
    }

    public static void main(String[] args) {
        Thread thread=new Thread(new DaemonRunner(),"DaemonThread");
        thread.setDaemon(true);
        thread.start();
    }
}

如果是Daemon线程执行结果如下:
image.png
非Daemon线程执行结果如下:
image.png

PS:在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

2、线程的启动和终止

2.1、线程的启动

线程的启动通过调用线程的start方法来启动。start方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start方法的线程。
PS:启动一个线程前,最好设置线程的名称,这样,在使用jstack排查程序问题时,就会给开发人员提供一些提示。自定义线程最好能够起个别名。

2.2、线程的中断

中断可以理解为线程的一个标识位属性。他表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否中观来进行响应,线程通过isInterrupted来进行是否中断判断,也可以调用静态方法Thread.interrupted对当前线程的中断标识位进行复位。但是如果线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted时,依旧会返回false。
在Java的许多API中,有许多抛出InterruptedException的方法,这些方法在抛出InterruptedException之前,JVM会先将线程中的中断标识位清除,这是调用该线程对象的isInterrupted时,依旧会返回false。
示例代码如下:

public class Interrupted {


    static class SleepRunner implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class BusyRunner implements Runnable{

        @Override
        public void run() {
            while (true){

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread sleepThread=new Thread(new SleepRunner(),"SleepRunner");
//        sleepThread.setDaemon(true);

        Thread busyThread=new Thread(new BusyRunner(),"BusyRunner");
//        busyThread.setDaemon(true);

        sleepThread.start();
        busyThread.start();

        TimeUnit.SECONDS.sleep(5);

        sleepThread.interrupt();
        busyThread.interrupt();

        System.out.println("SleepThread interrupted is"+ sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is"+ busyThread.isInterrupted());

        TimeUnit.SECONDS.sleep(2);
    }
}

运行结果如下:
image.png
从结果中可以看到抛出异常的SleepThread线程在抛出后,其标识位被清楚了返回的是false,而一直运行的BusyThread线程返回是true。

2.3、过期的suspend,resume,stop

1、suspend():暂定线程
2、resume():恢复线程
3、stop():终止线程

过期的原因:以suspend方法为例,在调用后,线程不会释放占用的资源(比如锁),而是占用着资源进行睡眠,这时就有可能导致死锁。同样stop方法在终止线程时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放的机会。

2.4、安全的终止线程

中断是一种简便的线程间的交互方式,而这种方式最适合用来取消或停止任务。当然还可以用共享变量来终止任务。示例代码如下:

public class Shutdown {

    static class Runner implements Runnable{
        private long i;
        private volatile boolean on =true;
        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()){
                i++;
            }

            System.out.println(i);
        }

        public void cancel(){
            on=false;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runner one=new Runner();
        Thread thread=new Thread(one,"one");
        thread.start();

        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();

        Runner weo=new Runner();
        Thread thread1=new Thread(weo,"weo");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        weo.cancel();
    }
}

运行结果如下:
image.png
在示例中,main方法中通过中断操作和cancel方法均可使线程终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源而不是武断的将线程终止。

3、线程间的通信

3.1、volatile和synchronize

java允许多线程同时访问同一个共享变量。由于每一个线程都有这个变量的拷贝(JMM协议),所以线程中看到的变量不一定是最新的。
关键字volatile可以用来修饰变量,就是用来告知程序对这个变量的访问要从内存中取,对这个变量的修改要立即写到刷新到内存中,这样其他线程每次看到的都是最新的数据。volatile详情
关键字synchronize可以修饰方法或者代码块,他用来保证同一时刻只能有一个线程能进入方法或者同步代码块中。他保证了线程对变量访问的排他性和可见性。synchronize详情

3.2、等待/通知机制

一个线程修改了一个值,另一个线程感知到变化,然后进行相关的操作。整个过程开始于一个线程,而最终执行与另一个线程。前者是生产者,后者就是消费者。这种结构在功能层面上实现了解耦,体系结构具有良好的伸缩性。那么在java中要如何实现呢?
在java中简单的办法就是让消费者线程循环检查变量是否符合预期。如下面的伪代码,在while循环中设置不满足的条件,如果条件允许则退出while循环,从而完成消费的工作。示例代码如下:

while(value != desire){
	Thread.sleep(1000);
}
doSomeThing();

上面这段代码,条件不足时就睡眠一段时间,这样做的目的是防止过快的无效尝试,浪费CPU的资源。这种方式看似能解决问题,但是却存在以下问题:

  1. 难以确保及时性
  2. 难以降低开销

对于以上问题,Java中内置了等待/通知机制。等待/通知相关方法是任意Java对象都具备的,因为其定义在所有对象的超类java.lang.Object上。相关方法描述如下:

方法名称描述
notify()通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到该对象的锁。
notifyAll()通知所有等待在该对象上的线程
wait()调用该方法的线程进入WAITING状态,只有等待另外线程的通知或者中断才能返回。在调用wait方法后,会释放对象的锁。
wait(long)超时等待一段时间,参数是毫秒,也就是等待长达n毫秒后,会没有通知就可以超时返回。调用该方法的线程会进入TIMED_WAITING状态
wait(long,int)对于超时等待更细粒度的控制,可以达到纳秒

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程基于对象O来完成交互。对于wait,notify,notifyAll的使用示例代码如下:

public class WaitNotify {

    static boolean flag=true;
    static final Object lock=new Object();

    static class Wait implements Runnable{

        @Override
        public void run() {
            synchronized (lock){
                while (flag){
                    System.out.println(Thread.currentThread()+" flag is true.wait "+ LocalDateTime.now());
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread()+" flag is false. running "+LocalDateTime.now());
            }
        }
    }

    static class Notify implements  Runnable{

        @Override
        public void run() {
            synchronized (lock){
                System.out.println(Thread.currentThread()+" hold lock. notify "+LocalDateTime.now());
                lock.notify();
                flag=false;
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            synchronized (lock){
                System.out.println(Thread.currentThread()+" hold lock again. sleep"+LocalDateTime.now());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread=new Thread(new Wait(),"WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);

        Thread notify=new Thread(new Notify(),"NotifyThread");
        notify.start();
    }
}

执行结果如下:
image.png
从上述的例子中,有以下使用wait,notify,notifyall的细节:

  1. 调用wait、notify、notifyAll需要先对调用对象加锁
  2. 调用wait方法之后,线程状态由RUNNING变为WAITING,并将当前线程放到对象上的等待队列中。
  3. notify、notifyAll方法调用之后,等待线程依旧不会从wait返回,需要调用notify、notifyAll的线程释放锁之后,等待线程才有机会从wait返回。
  4. notify方法将等待队列中的一个线程从等待队列移动到同步队列中,notifyAll方法则是将等待队列中所有的线程全部移动到同步队列中。被移动的线程从WAITING变为BLOCKED。
  5. 从wait方法返回的前提是从对象获取到锁。

上述代码运行过程如下:
image.png
在图中,首先WaitThread获取到对象的锁,然后调用wait方法,从而放弃了锁,并进入等待队列中。由于WaitThread释放了锁,NotifyThead线程获取到对象的锁,并调用了线程的notify方法,将WaitThread从等待队列中转移到同步队列中,此时WaitThread线程的状态变为阻塞状态,在NotifyThread线程释放锁之后,WaitThread再次获取到锁,并从wait方法返回继续执行。

3.3、等待/通知经典范式

我们可以从上述示例代码WaitNotify中提炼出等待/通知的经典范式。改范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。
等待方(消费者)遵循的原则如下:

  1. 获取对象的锁
  2. 如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件
  3. 条件满足继续执行对应的逻辑

对应的伪代码如下:

synchronize(对象){
	while(条件不满足){
  	对象.wait();
  }
  对应的处理逻辑
}

通知方(生产者)遵循的原则如下:

  1. 获取对应的锁
  2. 改变条件
  3. 通知所有等待在对象上的线程

对应的伪代码如下:

synchronize(对象){
	改变条件;
 	对象.notifyAll();
}

4、管道输入/输出流

管道输入/输出流和普通输入/输出流或者网络输入/输出流的区别在于,管道输入输出流主要用于线程间的数据传输,而传输的媒介就是内存。
管道输入输出流的实现主要分为PipedOutputStream、PipedInputStream、PipedReader、PipedWriter,前两种面向字节,后两种面向字符。示例代码如下:

public class Piped {

    static class Print implements Runnable{
        private PipedReader in;

        public Print(PipedReader in){
            this.in=in;
        }

        @Override
        public void run() {
            int receive=0;

            while (true) {
                try {
                    if ((receive=in.read())!=-1){
                        System.out.print((char)receive);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        PipedWriter out=new PipedWriter();
        PipedReader in=new PipedReader();
        out.connect(in);
        Thread printThread=new Thread(new Print(in),"PrintThread");
        printThread.start();
        int receive=0;
        while (true) {
            try {
                if ((receive=System.in.read())!=-1) {
                    out.write(receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果如下:
image.png
在示例中,创建PringThread线程用来接受main方法的输入,任何main方法线程的输入均通过PipedWriter写入,而PringThread线程的另一端通过PipedReader将内容读出并打印。
对于pip流必须先进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,那么对于流的访问会抛出异常。

5、Thread.join()的使用

如果一个线程A执行了thread.join()语句,其含义就是线程A等待线程thread执行完之后从thread.join()返回,并继续执行自己的代码。线程Thread除了提供join()方法外,还有join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在指定的超时时间内没有终止,那么将会从thread.join方法中返回。示例代码如下:

public class Join {

    static class Domino implements Runnable{

        private Thread thread;

        public Domino(Thread thread){
            this.thread=thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+" terminate.");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread pre=Thread.currentThread();

        for (int i=0;i<10;i++){
            Thread thread=new Thread(new Domino(pre),String.valueOf(i));
            thread.start();
            pre=thread;
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }
}

执行结果如下:
image.png
从上述输出可以看到,每个线程的终止,都依赖于前一个线程的终止。

6、ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的值。
可以通过set(T)方法来设置一个值,在当前线程下在通过get()方法获取到原先设置的值。示例代码如下:

public class Profiler {

    private static final ThreadLocal<Long> TIME_THREAD_LOCAL=new ThreadLocal<Long>(){
        protected Long init(){
            return System.currentTimeMillis();
        }
    };

    public static void begin(){
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
    }

    public static long end(){
        return System.currentTimeMillis()-TIME_THREAD_LOCAL.get();
    }

    public static void main(String[] args) throws InterruptedException {
        Profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Cost: "+Profiler.end()+" mills");
    }
}

运行结果如下:
image.png

ThreadLocal结构

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

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

相关文章

循环进阶-素数回文数的个数c++

题目描述 计算鸭请你帮忙求1到n之间&#xff08;包括 n&#xff09;&#xff0c;既是素数又是回文数的整数有多少个。 输入 一个大于1小于1000的整数n。 输出 1到n之间的素数回文数个数。 样例输入 23 样例输出 5 分析 这道题就是怎样判断素数和怎样判断回文数的结合…

clion读取文件设置为读取当前目录下的文件

1.问题 使用vs读取文件时一切正常 但是同样的代码在clion中无法正常执行 原因 原因&#xff1a;clion的源文件找不到input.txt文件的位置 需要设置工作目录&#xff0c;例如此时input.txt在当前目录下&#xff0c;那么就设置 设置当前文件的工作目录为$FileDir$即可&am…

Unity面试八股文之基础篇

文章目录 前言1. Unity的生命周期加载第一个场景Editor在第一次帧更新之前帧之间更新顺序协程销毁对象时退出时 2. Unity 协程和线程,进程的区别3. 本地坐标系 世界坐标系4. 碰撞器和触发器的区别后话 前言 开设这个栏目的博文会写一些有关unity的面试题目&#xff0c;在面试的…

清理mysql binglog文件

mysql随着使用时间的推移&#xff0c;binglog文件会越来越大&#xff0c;比如我们的oa系统&#xff0c;上线4年多了&#xff0c;最近总有磁盘空间满影响系统正常使用的情况出现。检查后发现binglog是罪归祸首。 binglog文件最好不要采用应删除的方式清理&#xff0c;如下方式可…

【数据结构与算法】之堆的应用——堆排序及Top_K问题!

目录 1、堆排序 2、Top_K问题 3、完结散花 个人主页&#xff1a;秋风起&#xff0c;再归来~ 数据结构与算法 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 1、堆排序 对一个无序的数组…

使用OpenCV dnn c++加载YOLOv8生成的onnx文件进行目标检测

在网上下载了60多幅包含西瓜和冬瓜的图像组成melon数据集&#xff0c;使用 LabelMe 工具进行标注&#xff0c;然后使用 labelme2yolov8 脚本将json文件转换成YOLOv8支持的.txt文件&#xff0c;并自动生成YOLOv8支持的目录结构&#xff0c;包括melon.yaml文件&#xff0c;其内容…

HACL-Net:基于MRI的胎盘植入谱诊断的分层注意力和对比学习网络

文章目录 HACL-Net: Hierarchical Attention and Contrastive Learning Network for MRI-Based Placenta Accreta Spectrum Diagnosis摘要方法实验结果 HACL-Net: Hierarchical Attention and Contrastive Learning Network for MRI-Based Placenta Accreta Spectrum Diagnosis…

世界上首位AI程序员诞生,AI将成为人类的对手吗?

3月13日&#xff0c;世界上第一位AI程序员Devin诞生&#xff0c;不仅能自主学习新技术&#xff0c;自己改Bug&#xff0c;甚至还能训练和微调自己的AI模型&#xff0c;表现已然远超GPT-4等“顶流选手”。 AI的学习速度如此之快&#xff0c;人类的教育能否跟上“机器学习”的速…

2、xss-labs之level2

1、打开页面 2、传入xss代码 payload&#xff1a;<script>alert(xss)</script>&#xff0c;发现返回<script>alert(xss)</script> 3、分析原因 打开f12&#xff0c;没什么发现 看后端源码&#xff0c;在这form表单通过get获取keyword的值赋给$str&am…

《拯救大学生课设不挂科第三期之Windows下安装Dev C++(VC 6.0上位替代)与跑通Hello World程序C/C++版教程》【官方笔记】

背景&#xff1a; 大学老师使用的VC6.0(VC 6.0)太老了&#xff0c;老师为什么用VC6.0&#xff0c;象漂亮认为是高校缺乏鲶鱼刺激。很大一部分高校已经与市场脱节&#xff0c;这也是为什么很多同学毕业后想要入行计算机基本都得自己重新回炉打造一次&#xff0c;这和高校的老旧…

OpenStack平台Keystone组件的使用

1. 规划节点 安装基础服务的服务器规划 IP地址 主机名 节点 192.168.100.10 controller Openstack控制节点 2. 基础准备 使用机电云共享的单节点的openstack系统&#xff0c;自行修改虚拟网络编辑器、网络适配器&#xff0c;系统用户名&#xff1a;root&#xff0c;密…

在Github上寻找安装ROS软件包

1、创建一个功能包 并下载git sudo apt install git 2、找到自己想在github上要克隆的包 复制此链接 3、克隆到本地 git clone 链接 4.scripts目录用于放置脚本文件和python程序 使用脚本安装编译需要的依赖库 5、下载完成后&#xff0c;在~catkin_ws目录下运行catkin_make进…

电脑键盘如何练习盲打?

电脑键盘如何练习盲打&#xff1f;盲打很简单&#xff0c;跟着我做&#xff0c;今天教会你。 请看【图1】&#xff1a; 【图1】中&#xff0c;红色方框就是8个基准键位&#xff0c;打字时我们左右手的8个手指就是放在这8个基准键位上&#xff0c;F键和J键上各有一个小突起&…

FTP协议——BFTPD安装(Linux)

1、简介 BFTPD&#xff0c;全称为 Brutal File Transfer Protocol Daemon&#xff0c;是一个用于Unix和类Unix系统的轻量级FTP服务器软件。它的设计理念是提供一个简单、快速、安全的FTP服务器解决方案&#xff0c;特别适用于需要低资源占用的环境。 2、步骤 环境&#xff1…

网络爬虫原理及其应用

你是否想知道Google 和 Bing 等搜索引擎如何收集搜索结果中显示的所有数据。这是因为搜索引擎对其档案中的所有页面建立索引&#xff0c;以便它们可以根据查询返回最相关的结果。网络爬虫使搜索引擎能够处理这个过程。 本文重点介绍了网络爬虫的重要方面、网络爬虫为何重要、其…

卷积神经网络CNN动态演示和输出特征图计算公式

目录 一、卷积运算 1、卷积&#xff08;Convolution&#xff09; 2、填充&#xff08;Padding&#xff09; &#xff08;1&#xff09;Valid Padding &#xff08;2&#xff09;Same Padding 3、步长 4、卷积核大小为什么一般为奇数奇数&#xff1f; 5、卷积核kernel和…

蓝桥杯杨辉三角

PREV-282 杨辉三角形【第十二届】【蓝桥杯省赛】【B组】 &#xff08;二分查找 递推&#xff09;&#xff1a; 解析&#xff1a; 1.杨辉三角具有对称性&#xff1a; 2.杨辉三角具有一定规律 通过观察发现&#xff0c;第一次出现的地方一定在左部靠右的位置&#xff0c;所以从…

驱动编译报error: negative width in bit-field ‘<anonymous>’错误

错误如下图所示&#xff1a; 代码如下&#xff1a; 问题点&#xff1a;module_param的其他用户的权限参数上。 在Linux中&#xff0c;文件权限由读(r)、写(w)、执行(x)权限组成&#xff0c;分别对应数值4、2、1。 第一位0是占位符&#xff0c;在这里没有意义&#xff0c;因为…

[emailprotected](2)核心概念-JSX

目录 1&#xff0c;什么是 jsx2&#xff0c;空标签3&#xff0c;通过大括号使用 js4&#xff0c;防止注入攻击5&#xff0c;元素的不可变性 官方文档 1&#xff0c;什么是 jsx Facebook 起草的 js 扩展语法。本质上是 js 对象&#xff0c;会被 babel 编译&#xff0c;最终转换…

Halcon 极坐标转换图像

一、概述 先看效果 将圆形的用极坐标转换成矩性然后再进行识别或者其他缺陷检测&#xff0c;最后在还圆到原图中 二、原理&#xff1a; halcon 圆环类缺陷检测的一种方法&#xff08;极坐标变换法&#xff09;_halcon缺口检测-CSDN博客 图像极坐标变换与反变换&#xff08;…