Java也可以轻松编写并发程序

news2025/1/14 18:13:31

如今,多核处理器在服务器,台式机及笔记本电脑上已经很普遍了,同时也被应用在更小的设备上,比如智能手机和平板电脑。这就开启了并发编程新的潜力,因为多个线程可以在多个内核上并发执行。在应用中要实现最大性能的一个重要技术手段是将密集的任务分隔成多个可以并行执行的块,以便可以最大化利用计算能力。

处理并发(并行)程序,一向都是比较困难的,因为你必须处理线程同步和共享数据的问题。对于java平台在语言级别上对并发编程的支持就很强大,这已经在Groovy(GPars), Scala和Clojure的社区的努力下得以证明。这些社区都尽量提供全面的编程模型和有效的实现来掩饰多线程和分布式应用带来的痛苦。Java语言本身在这方面不应该被认为是不行的。Java平台标准版(Java SE) 5 ,和Java SE 6引入了一组包提供强大的并发模块。Java SE 7中通过加入了对并行支持又进一步增强它们。

接下来的文章将以Java中一个简短的并发程序作为开始,以一个在早期版本中存在的底层机制开始。在展示由Java SE7中的fork/join框架提供的fork/join任务之前,将看到java.util.concurrent包提供的丰富的原语操作。然后就是使用新API的例子。最后,将对上面总结的方法进行讨论。

在下文中,我们假定读者具有Java SE5或Java SE6的背景,我们会一路呈现一些Java SE7带来的一些实用的语言演变。

Java中普通线程的并发编程

首先从历史上来看,java并发编程中通过java.lang.Thread类和java.lang.Runnable接口来编写多线程程序,然后确保代码对于共享的可变对象表现出的正确性和一致性,并且避免不正确的读/写操作,同时不会由于竞争条件上的锁争用而产生死锁。这里是一个基本的线程操作的例子:

 Thread thread = new Thread() { @Override public void run() { System.out.println("I am running in a separate thread!"); } }; thread.start(); thread.join();

例子中的代码创建了一个线程,并且打印一个字符串到标准输出。通过调用join()方法,主线程将等待创建的(子)线程执行完成。
对于简单的例子,直接操作线程这种方式是可以的,但对于并发编程,这样的代码很快变得容易出错,特别是好几个线程需要协作来完成一个更大的任务的时候。这种情况下,它们的控制流需要被协调。

例如,一个线程的执行完成可能依赖于其他将要执行完成的线程。通常熟悉的例子就是生产者/消费者的例子,因为如果消费者队列是空的,那么生产者应该等待消费者,并且如果生产者队列是空的,那么消费者也应该等待生产者。该需求可能通过共享状态和条件队列来实现,但是你仍然必须通过使用共享对象上的java.lang.Object.nofity()和java.lang.Object.wait()来实现同步,这很容易出错。

最终,一个常见的错误就是在大段代码甚至整个方法上使用synchronize进行互斥。虽然这种方法能实现线程安全的代码,但是通常由于排斥时间太长而限制了并行性,从而造成性能低下。

在通常的计算过程中,操作低级原语来实现复杂的操作,这是对错误敞开大门。因此,开发者应该寻求有效地封装复杂性为更高级的库。Java SE5提供了那样的能力。

java.util.concurrent包中丰富的原语

Java SE5引入了一个叫java.util.concurrent的包家族,在Java SE6中得到进一步增强。该包家族提供了下面这些并发编程的原语,集合以及特性:

  • Executors,增强了普通的线程,因为它们(线程)从线程池管理中被抽象出来。它们执行任务类似于传递线程(实际上,是实现了java.util.Runnable的实例被封装了)。好几种实现都提供了线程池和调度策略。而且,执行结果既可以同步也可以异步的方式来获取。
  • 线程安全的队列允许在并发任务中传递数据。一组丰富的实现通过基本的数据结构(如数组链表,链接链表,或双端队列)和并发行为(如阻塞,支持优先级,或延迟)得以提供。
  • 细粒度的超时延迟规范,因为大部分java.util.concurrent包中的类都支持超时延迟。比如一个任务如果没有在有限之间内完成,就会被executor中断。
  • 丰富的同步模式超越了java提供的互斥同步块。这些同步模式包含了常见的俗语,如信号量或同步栅栏。
  • 高效的并发数据集合(maps, lists和sets)通过写时复制和细粒度锁的使用,使得在多线程上下文中表现出卓越的性能。
  • 原子变量屏蔽开发者访问它们时执行同步操作。这些变量包装了通用的基本类型,比如Integers或Booleans,和对象引用。
  • 大量锁超越了内部锁提供的加锁/通知功能,比如,支持重入,读写锁,超时,或者基于轮询的加锁尝试。

作为一个例子,让我们想想下面的程序:

注意:由于Java SE7引入了新的整数字面值,下划线可以在任何地方插入以提高可读性(比如,1_000_000)。

import java.util.*;import java.util.concurrent.*;import static java.util.Arrays.asList;public class Sums { static class Sum implements Callable<Long> { private final long from; private final long to; Sum(long from, long to) { this.from = from; this.to = to; } @Override public Long call() { long acc = 0; for (long i = from; i <= to; i++) { acc = acc + i; } return acc; } } public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(2); List <Future<Long>> results = executor.invokeAll(asList( new Sum(0, 10), new Sum(100, 1_000), new Sum(10_000, 1_000_000) )); executor.shutdown(); for (Future<Long> result : results) { System.out.println(result.get()); } }}

这个例子程序利用executor来计算长整形数值的和。内部的Sum类实现了Callable接口,并被excutors用来执行结果计算,而并发工作则放在call方法中执行。java.util.concurrent.Executors类提供了好几个工具方法,比如提供预先配置的Executors和包装普通的java.util.Runnable对象为Callable实例。使用Callable比Runnable更优势的地方在于Callable可以有确切的返回值。

该例子使用executor分发工作给2个线程。ExecutorService.invokeAll()方法放入Callable实例的集合,并且等待直到它们都返回。其返回Future对象列表,代表了计算的“未来”结果。如果我们想以异步的方式执行,我们可以检测每个Future对象对应的Callable是否完成了它的工作和是否抛出了异常,甚至我们可以取消它。相比当使用普通的线程时,你必须通过一个共享可变的布尔值来编码取消逻辑,并且通过定期检查该布尔值来破坏该代码。因为invokeAll()是阻塞的,我们可以直接迭代Future实例来获取它们的计算和。

另外要注意executor服务必须被关闭。如果它没有被关闭,主方法执行完后JVM就不会退出,因为仍然有激活线程存在。

Fork/Join 任务

概览

Executors相对于普通的线程已经是一个很大的进步,因为executors很容易管理并发任务。有些类型的算法存在需要创建子任务,并且让它们彼此通信来完成任务。这些都是”分而治之”的算法,也被称为”map and reduce”,这是参考函数式编程的同名函数。想法是将数据区通过算法处理分隔为更小切独立的块,这是”map”阶段。反过来,一旦这些块被处理完成了,各部分的结果就可以收集起来形成最终的结果,这就是”reduce”阶段。

一个简单的例子想要计算出一个庞大的整形数组的和(如图1)。由于加法是可交换的,可以拆分数组分更小的部分,并且用并发线程计算各部分和。各部分和可以被加来从而计算出总和。因为线程可以独立对一个数组的不同区域使用这种算法操作。相比于单线程算法(迭代数组中每个整形),你将看到在多核架构中有了明显的性能提升。

图1:整形数组中的部分和

通过executors解决上面的问题是很容易的:将数组分为n(可用的物理处理单元)部分,创建Callable实例来计算每一部分的和,提交它们到一个管理了n个线程的池中,并且收集结果计算出最终结果。

然而,对其他类型的算法和数据结构,其执行计划并不是那么简单。特别是,识别出要以有效的方式被独立处理的“足够小”的数据块的”map”阶段并不能提前知道到数据空间的拓扑结构。基于图和基于树的数据结构尤为如此。在这些情况下,算法应该创建层级”划分”,即在部分结果返回之前等待子任务完成,虽然在像图1中的数组性能较差,但有好几个并发部分和的计算的级别可以使用(比如,在双核处理器上将数组分为4个子任务)。

为了实现分而治之算法的executors的问题是创建不相关的子任务,因为一个Callable是无限制的提交一个新的子任务给它的executors,并且以同步或异步的方式等待它的结果。问题是并行:当一个Callable等待另一个Callable的结果时,它就处于等待状态,从而浪费了一个机会来处理队列中等待执行的另一个Callable。

通过Doug Lea努力填补了这一缺陷,在Java SE7中,fork/join框架被加到了java.util.concurrent包中。java.util.concurrent的Java SE5和Java SE6版本帮助处理并发,并且Java SE7的添加则帮助处理并行。

添加支持并行

核心的添加是新的ForkJoinPool执行者,专门执行实现了ForkJoinTask接口的实例。ForkJoinTask对象支持创建子任务来等待子任务完成。有了这些清晰的语义,当一个任务正在等待另一个任务完成并且有待执行的任务时,executor就能够通过”偷取”任务,在内部的线程池里分发任务。

ForkJoinTask对象主要有两个重要的方法:

  • fork()方法允许ForkJoinTask任务异步执行,也允许一个新的ForkJoinTask从存在的ForkJoinTask中被启动。
  • 反过来, join()方法允许一个ForkJoinTask等待另一个ForkJoinTask执行完成。

如图2所示,通过fork()和join()实现任务间的相互合作。注意fork()和join()方法名称不应该与POSIX中的进程能够复制自己的过程相混淆。fork()只会让ForkJoinPool调度一个新的任务,而不会创建子虚拟机。

图2:Fork和Join任务间的协作

有两种类型的ForkJoinTask的定义:

  • RecursiveAction的实例代表执行没有返回结果。
  • 相反,RecursiveTask会有返回值。

通常,RecursiveTask是首选的,因为大部分分而治之的算法会在数据集上计算后返回结果。对于任务的执行,不同的同步和异步选项是可选的,这样就可以实现复杂的模式。

例子:计算文档中的单词出现次数

为了阐述新的fork/join框架的使用,让我们用一个简单的例子(计算一个单词在文档集中的出现次数)。首先,也是最重要的,fork/join任务应该是纯内存算法,而没有I/O操作。此外,应该尽可能避免通过共享状态来进行任务间的通信,因为这通常意味着加锁会被执行。理想情况下,仅当一个任务fork另一个任务或一个任务join另一个任务时才进行任务通信。

我们的应用操作一个文件目录结构并且加载每一个文件的内容到内存中。因此,我们需要下面的类来表示模型。文档表示为一些列行:

class Document { private final List<String> lines; Document(List<String> lines) { this.lines = lines; } List<String> getLines() { return this.lines; } static Document fromFile(File file) throws IOException { List<String> lines = new LinkedList<>(); try(BufferedReader reader = new BufferedReader(new FileReader(file))) { String line = reader.readLine(); while (line != null) { lines.add(line); line = reader.readLine(); } } return new Document(lines); }}

注意:如果你对Java SE7比较陌生,你应该会对fromFlie方法中的亮点感到惊讶:

  • LinkedList使用钻石语法(<>)让编译器推断出范型参数类型。因为lines是List<String>类型,所以LinkedList<>被扩展为LinkedList<String>。钻石操作符使得范型处理更容易,其避免了重复类型,因为这些类型在编译时就能被轻易的推断出来。
  • try块使用了自动资源管理的语言特性。任何实现了java.lang.AutoClosable的类都可以在try块中打开。而不管是否有异常抛出,任何在try块中声明的资源将会在执行离开try块时合理地关闭。在Java SE7之前,正确地关闭资源很快变成嵌套的if/try/catch/finally块的一张噩梦,而且经常很难写正确。

一个文件夹时一个简单的基于树的结构:

class Folder { private final List<Folder> subFolders; private final List<Document> documents; Folder(List<Folder> subFolders, List<Document> documents) { this.subFolders = subFolders; this.documents = documents; } List<Folder> getSubFolders() { return this.subFolders; } List<Document> getDocuments() { return this.documents; } static Folder fromDirectory(File dir) throws IOException { List<Document> documents = new LinkedList<>(); List<Folder> subFolders = new LinkedList<>(); for (File entry : dir.listFiles()) { if (entry.isDirectory()) { subFolders.add(Folder.fromDirectory(entry)); } else { documents.add(Document.fromFile(entry)); } } return new Folder(subFolders, documents); }}

现在我们可以开始我们的主类了:

import java.io.*;import java.util.*;import java.util.concurrent.*;public class WordCounter { String[] wordsIn(String line) { return line.trim().split("(\\s|\\p{Punct})+"); } Long occurrencesCount(Document document, String searchedWord) { long count = 0; for (String line : document.getLines()) { for (String word : wordsIn(line)) { if (searchedWord.equals(word)) { count = count + 1; } } } return count; }}

occurrencesCount方法返回一个单词在文档中的出现次数,利用wordIn方法产生一行内的单词组,它会基于空格或标点符号来分割每一行。
我们将实现两种类型的fork/join任务。一个文件夹下的单词出现次数就是该单词在该文件夹下的所有的子文件夹和文档中出现次数的总和。因此,我们将用一个任务计数在文档中的出现次数和用另一个任务在文件夹下的计数,后者将forks子任务,然后将这些任务join起来,集合他们的结果。

依赖的任务关系很容易理解,如图3所示,因为它直接映射底层文档或文件夹树结构。fork/join框架通过在等待一个任务执行文档或文件夹单词计数时可以通过join()同时执行一个文件夹任务,实现了并行最大化。

图3:Fork/Join单词计数任务

让我们以DocumentSearchTask任务开始,它将计算一个文档中单词的出现次数:

class DocumentSearchTask extends RecursiveTask<Long> { private final Document document; private final String searchedWord; DocumentSearchTask(Document document, String searchedWord) { super(); this.document = document; this.searchedWord = searchedWord; } @Override protected Long compute() { return occurrencesCount(document, searchedWord); }}

因为我们的任务需要返回值,因此它们扩展自RecursiveTask类,由于出现次数用long值表示,所以用Long作为范型参数。compute()方法是RecursiveTask的核心,这里的实现就简单的委派给上面的occurencesCount()方法。现在我们可以实现FolderSearchTask,该任务将对树结构中的文件夹进行操作:

class FolderSearchTask extends RecursiveTask<Long> {    private final Folder folder;    private final String searchedWord;        FolderSearchTask(Folder folder, String searchedWord) {        super();        this.folder = folder;        this.searchedWord = searchedWord;    }        @Override    protected Long compute() {        long count = 0L;        List<RecursiveTask<Long>> forks = new LinkedList<>();        for (Folder subFolder : folder.getSubFolders()) {            FolderSearchTask task = new FolderSearchTask(subFolder, searchedWord);            forks.add(task);            task.fork();        }        for (Document document : folder.getDocuments()) {            DocumentSearchTask task = new DocumentSearchTask(document, searchedWord);            forks.add(task);            task.fork();        }        for (RecursiveTask<Long> task : forks) {            count = count + task.join();        }        return count;    }}

该任务的compute()方法的实现简单地对构造函数中传递的文件夹的每个元素fork出新的文档或文件夹任务,然后join所有的计算出的部分和并返回部分和。

对于fork/join框架,我们现在缺少一个方法来引导单词 计数操作和一个fork/join池执行者:

private final ForkJoinPool forkJoinPool = new ForkJoinPool();Long countOccurrencesInParallel(Folder folder, String searchedWord) { return forkJoinPool.invoke(new FolderSearchTask(folder, searchedWord));}

一个初始的FolderSearchTask引导了所有任务。ForkJoinPool的invoke方法允许等待计算的完成。在上面的例子中,使用了ForkJoinPool的空构造函数,并行性将匹配硬件可用的处理器单元数(比如,在双核处理器上该值为2)。

现在我们可以写main()方法,通过命令行参数来获得要操作的文件夹和搜索的单词:

public static void main(String[] args) throws IOException { WordCounter wordCounter = new WordCounter(); Folder folder = Folder.fromDirectory(new File(args[0])); System.out.println(wordCounter.countOccurrencesOnSingleThread(folder, args[1]));}

此示例的完整源代码还包括更传统的,基于递归实现的相同算法,并运行在一个运行在单线程上:

Long countOccurrencesOnSingleThread(Folder folder, String searchedWord) { long count = 0; for (Folder subFolder : folder.getSubFolders()) { count = count + countOccurrencesOnSingleThread(subFolder, searchedWord); } for (Document document : folder.getDocuments()) { count = count + occurrencesCount(document, searchedWord); } return count;}

讨论

在来自Oracle的Sun Fire T2000服务器上进行了一个非正式的测试,该服务器可以规定Java虚拟机可用的处理器核数。上述的fork/join和单线程实例的不同版本都运行来查找JDK源代码文件中import的出现次数。

这些不同版本都会运行好几次,以确保Java虚拟机热点优化有足够的时间来执行。对于2, 4, 8和12核最佳的执行时间被收集了起来并且在加速,那就是说,这些比值(单线程耗时/fork-join耗时)被计算出来了。图4核表1反应了其结果。

如你所见,随着处理器核数极小的增长就实现了近似线性地加速,因为fork/join框架关心的是最大化并行。

表1:非正式的测试执行时间和加速

Number of CoresSingle-Thread Execution Time (ms)Fork/Join Execution Time (ms)Speedup
218798110261.704879376
41947383292.337975747
81891142084.494058935
121941028766.748956885

图4:随着核数(水平轴)增长而加速(垂直轴)

我们也可以通过让fork任务不操作在文档级别,而是行级别来改进计算能力。这使得并发任务可能在同一文档的不同行执行。然而这将是牵强的。事实上,fork/join任务应该执行足够的计算量,以克服fork/join线程池或任务管理开销。行级别的操作很琐碎,反而影响方法的效率。

附带的源代码还有一个基于整型数组的归并算法fork/join例子,有趣的是它使用RecursiveAction来实现的。fork/join任务在调用join()时不会返回值。而是,这些任务共享可变状态:待排序的数组。实验再次 表明随内核数量增长,将实现近线性地加速。

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

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

相关文章

SpringBoot+Vue实现前后端分离的小而学在线考试系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

访问者模式(Visitor)

参考&#xff1a; 模板方法设计模式 (refactoringguru.cn) design-patterns-cpp/TemplateMethod.cpp at master JakubVojvoda/design-patterns-cpp GitHubhttps://github.com/JakubVojvoda/design-patterns-cpp/blob/master/state/State.cpp) 文章目录一、什么是访问者模式…

【Python机器学习】Sklearn库中Kmeans类、超参数K值确定、特征归一化的讲解(图文解释)

一、局部最优解 采用随机产生初始簇中心 的方法&#xff0c;可能会出现运行 结果不一致的情况。这是 因为不同的初始簇中心使 得算法可能收敛到不同的 局部极小值。 不能收敛到全局最小值&#xff0c;是最优化计算中常常遇到的问题。有一类称为凸优化的优化计算&#xff0c;不…

数字货币市场风暴肆虐,币圈人应该把握哪些新的赛道机遇

11月11日&#xff08;周五&#xff09;美股盘前&#xff0c;曾经为全球第二大加密货币交易所FTX在推特发布了申请破产保护的声明&#xff0c;创始人SBF已经辞去CEO职务。据声明&#xff0c;FTX已经任命John J. Ray III 担任CEO&#xff0c;SBF还将协助相关破产事宜。据FTX在推特…

[附源码]Python计算机毕业设计Django面向高校活动聚App

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

记录--三分钟打造自己专属的uni-app工具箱

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 介绍 可曾想过我们每次创建新项目&#xff0c;或者换地方写程序&#xff0c;都要把之前写过的工具类找出来又要复制粘贴一遍有些麻烦&#xff0c;尤其是写uni-app自定义模板主要还是开发工具完成的。这…

反序列化漏洞之CVE-2016-7124

目录 魔术函数 发生条件 靶场练习 魔术函数 __constuct: 构建对象的时被调用 __destruct: 明确销毁对象或脚本结束时被调用 __invoke: 当以函数方式调用对象时被调用 __toString: 当一个类被转换成字符串时被调用 __wakeup: 当使用unserialize时被调用&#xff0c;可用于做些…

【python】pandas 之 DataFrame、Series使用详解

目录 一&#xff1a;Pandas简介 二&#xff1a;Pandas数据结构 三&#xff1a;Series 四&#xff1a;字典生成Series 五&#xff1a;标量值生成Series 六&#xff1a;Series类似多维数组 七&#xff1a;Series类似字典 八&#xff1a;矢量操作与对齐 Series 标签 九…

对话交通银行:中国金融业数据仓库有哪些重要趋势?

数字经济时代&#xff0c;什么才是金融机构的核心竞争力&#xff1f;笔者访谈了交通银行软件开发中心总经理刘雷。刘雷指出&#xff1a;“数据和数据能力是金融机构发展的核心竞争力”。 当下&#xff0c;金融机构的数字化转型正迈入纵深阶段&#xff0c;使得两大核心诉求更加…

SpringMVC学习:四、SpringMVC的高级开发(异常处理器、文件上传、 拦截器)

5. SpringMVC的高级开发 5.1 异常处理器 ​ springmvc在处理请求过程中出现异常信息交由异常处理器进行处理&#xff0c;自定义异常处理器可以实现一个系统的异常处理逻辑。 思路: ​ 系统中异常包括两类&#xff1a;预期异常和运行时异常RuntimeException&#xff0c;前者…

如何利用代理IP做SEO监控优化?

从事互联网营销相关的用户多多少少都会接触到SEO&#xff0c;一般来说企业为了实现传播效果&#xff0c;每天都需要大量重复地做各种渠道的投放&#xff0c;这是一项逐渐累积的长期性工作。而这其中关键的优化分析与监控&#xff0c;势必需要大量的数据支持。接下来就一起来了解…

Linux--seq命令

seq(sequeue)用于序列化输出一个数到另一个数之间的整数&#xff0c;输出连续的数字、 固件间隔的数字、指定格式的数字。 一、使用方法 seq [选项] 尾数seq [选项] 首数 尾数seq [选项] 首数 增量 尾数 [选项] -f, --formatFORMAT use printf style floating-point FO…

深度学习 Day22——利用LSTM实现火灾温度预测

深度学习 Day22——利用LSTM实现火灾温度预测 文章目录深度学习 Day22——利用LSTM实现火灾温度预测一、前言二、我的环境三、LSTM介绍1、长期依赖的问题2、LSTM3、LSTM结构四、前期工作1、设置GPU2、导入数据3、数据可视化五、构建数据集1、设置X、y2、设置归一化3、划分数据集…

[附源码]Nodejs计算机毕业设计基于的校园失物招领平台Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

[附源码]Python计算机毕业设计高校学生综合素质测评系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

分析Linux 内核 SCSI IO 子系统

【推荐阅读】 浅析linux内核网络协议栈--linux bridge virtio-net 实现机制【一】&#xff08;图文并茂&#xff09; 怎么在Windows下使用Makefile文件 概述 LINUX 内核中 SCSI 子系统由 SCSI 上层&#xff0c;中间层和底层驱动模块 [1] 三部分组成&#xff0c;主要负责管…

PreSTU:一个专门为场景文本理解而设计的简单预训练模型

摘要&#xff1a;在视觉与语言&#xff08;V&L&#xff09;模型中&#xff0c;阅读和推理图像中的文本的能力往往是缺乏的。我们如何才能学习出强大的场景文本理解&#xff08;STU&#xff09;的V&L模型呢&#xff1f;本文分享自华为云社区《场景文本理解预训练PreSTU》…

C#编程基础(万字详解,这一篇就够了)

C#及其开发环境简介 C#概述 C#的编程功能 C#与.Net的关系 .Net C# C#的集成开发环境 Windows上编写C#程序 Linux/Mac OS上编写C#程序 运行第一个HelloWorld程序 C#基本语法 程序实例 C#基本语法 using关键字 class关键字 注释 成员变量 成员函数 实例化一个类…

【图像分割】粒子群优化指数熵图像分割【含Matlab源码 287期】

⛄一、图像分割简介 理论知识参考&#xff1a;【基础教程】基于matlab图像处理图像分割【含Matlab源码 191期】 ⛄二、部分源代码 %%无递推 clc; clear; Cimread(‘1.jpg’); Crgb2gray©; %Cimnoise(C,‘gaussian’,0,0.005); figure subplot(1,2,1) imshow© tic …

vue初级入门以及demo实现详解

vue的初级入门1&#xff0c;vue是什么2&#xff0c;vue的特点2.1&#xff0c;采用组件化模式2.2&#xff0c;声明式编码2.3&#xff0c;虚拟DOM Diff算法3&#xff0c;vue的环境的基本配置和使用3.1&#xff0c;在浏览器安装开发工具3.2&#xff0c;开发环境3.3&#xff0c;de…