java调用rust代码 java rust

Java和Rust在实现多线程编程时的异同Java的实现
打开Follower.java里的这个函数
这里的Follower.this.invitations就是我们的消息队列,定义是:private LinkedListInvitation invitations;LinkedList不是线性安全的集合,需要我们加同步 。具体的同步方法就是函数里写的 , 通过Java常见的用wait,notify和notifyall给对象加锁 。
处理并发有wait、notify和notiyall,有兴趣的朋友可以去这里了解一下: 。Follower就是一个等待leader发送invitation,处理并返回结果的过程 。
Leader.java
这么一段代码:
里面就是Leader发送邀请inv,并等待follower返回结果的大概逻辑,通过对消息体加锁 , 是Java传统的实现多线程并发的方式 。还有消费者的消息队列也会加锁,在Java里,有个对象叫LinkedBlockingQueue,是不用加锁就可以put和take的,但在例子里 , 我们选用了更简单的LinkedList,也是为了表现一下加锁的逻辑 。
Rust的实现
Leader的结构为:
Follower的结构为:
对于其他语言转过来的同学,这里的Vec,i32 , bool都很好理解,不过里面出现的Arc和Mutex,Sender,Receiver就是新东西了,上面这4个都是Rust标准库的东西,也是这次分享要介绍的重点对象 , 是这4个东西共同实现了消息的生产 , 传递和消费 。
下面简单介绍一下分别是做什么用的:
ArcT实现了sync接口 。Sync接口是做什么呢?权威资料是这么说的:当一个类型T实现了Sync,它向编译器表明这个类型在多线程并发时没有导致内存不安全的可能性 。
如果看不懂不要紧 , 我们先看看实际中是怎么用的:
在这个例子里,我们关注这几句:
let data = https://www.04ip.com/post/Arc::new(Mutex::new(vec![1u32, 2, 3]));
let data = https://www.04ip.com/post/data.clone();
let mut data = https://www.04ip.com/post/data.lock().unwrap();
下面分别解释一下是做什么的:
简单的说Arc::new表明了这是通过clone()方法来使用的 , 每clone,都会给该对象原子计数 1 , 通过引用计数的方法来保证对象只要还被其中任何一个线程引用就不会被释放掉,从而保证了前面说的:这个类型在多线程并发时没有导致内存不安全的可能性 。
如果我们不定义为Arc就传到其他线程使用,编译器会报:
error: capture of moved value: `data`
data[i]= 1;
我们可以记住clone()就是Arc的用法 。
接下来我们看Mutex:
Mutex实现了send接口 。同样,在权威资料里是这么描述的:这个类型的所有权可以在线程间安全的转移
那我们又是怎么用Mutex的呢?就是用lock().unwrap() 。lock()的作用是获取对象,如果当前有其他线程正在使用MutexT里面的T对象时,本线程就会阻塞,从而保证同时只有一个线程来访问对象,mutex也另外提供了try_lock()的方法,是不阻塞的,只要其他线程被占用 , 就返回err , 通常Arc和Mutex都是一起使用的 。
回到我最原始的题目,Mutex和Arc实现了对象本身的线程共享 , 但是在线程间如何传递这个对象呢?就是靠channel,channel通常是这么定义的let (tx, rx) = mpsc::channel();它会返回两个对象tx和rx,就是之前我提到的sender和receiver 。
在我的Rust实现里,关键的语句是以下几个:
let leaders = (0..leader_cnt).map(|i|
Arc::new(Mutex::new(Leader::new(i,dance_types.len() as i32)))
).collect::Vec_();
这一句是new一堆leader出来 , Arc和Mutex表明leader是可以多线程共享和访问的 。
同样Follower也是:
let followers = (0..follower_cnt).map(|i|
Arc::new(Mutex::new(Follower::new(i,dance_types.len() as i32,leader_cnt)))
).collect::Vec_();
接下来这几句就有点不好理解了 。
这里定义了一堆的sender和receiver,其中把他们都作为leader和follower的成员变量存起来 。大概意思就是每一个leader都通过sender列表可以发送invitation给所有follower,同时又有单个receiver来接受所有follower发给自己的处理结果inviresult 。
同样follower也是这么做 。这样在之后每一个follower和leader作为一个线程跑起来之后,都能在相互之间建立了一条通信的通道 。
这个是和Java实现多线程并发最大的不同之处!Java是通过给对象加锁 , Rust是通过channel转移对象的所有权,在代码里 , leader发送inv给folloer是下面这一句
match self.senders[*follower_id as usize].lock().unwrap().send(inv){ , 其中的lock().unwrap()是获得该leader对该follower的发送通道的所有权,send(inv)就是转移具体的发送对象invitation所有权了 。
这个转移按照我的理解,应该是内存拷贝 。就是在follower接收的时候,let inv = match self.receiver.recv() { , 原来leader里面的inv在send之后已经是不可访问了 , 如果你之后再次访问了inv,会报use of moved value错误,而follower里面的inv则是在follower的栈里新生成的对象,所以,在Java里面我只定义了invitation对象,但是在Rust里面,我要再定义一个InviResult,因为我即使在follower线程里面填了result字段,leader线程也不能继续访问inv了 。所以需要依靠follower再次发送一个invresult给leader,所以整个Rust程序大概就是这么一个思路 。
实践总结
之前我测试比较Java和Rust实现的性能时,由于没有把调试信息去掉,导致Java比Rust慢很多 , 特别是那些调试信息都是调用String.format , 这是比几个string相加慢上10倍的方法 , 两者都去掉调试信息后,leader和follower都会2000的时候,在我低端外星人笔记本里,性能差别大概是2倍吧,没我想象中大,Rust的程序整个写下来比较费力,一方面是对ownership机制不熟,思维没有转变过来,另一方面Rust的确需要开发者分部分精力到语法细节上 。
编者注:冯总也有一些其它的实践体会,请参见CSDN对冯耀明的专访,请戳这里 。也可以查看他的个人博客里的总结 。
下面摘录采访中关于Rust的内容过来:
首先Rust里面的ownership和lifetime概念真的很酷,就因为这个概念实现无内存泄露,野指针和安全并发 。
其次,Rust的语法不简单,也是有不少坑的,据说Rust的潜在用户应该是现在的C和C程序员,他们可能会觉得比较习惯,说不定还 觉得更简单 。由于ownership机制,一些在其他语言能够跑通的程序在Rust下就要调整实现了,它会改变你写程序的思维方式 。据说一些写Rust超 过半年的程序员已经爱上它了!
我对Rust感受较深的是下面几点:
初学者不熟悉ownership机制,会无数次编译失败 。但一旦编译成功,那么程序只剩下逻辑错误了 。同样,由于ownership机制,将来在项目里修改Rust代码将可能是痛苦的过程,因为原来编译通过的代码可能加入新功能就编译不过了,这是我的猜测 。
Rust编译速度慢 , 不过据说最近每一个Rust新发布的版本编译速度都比之前的版本提高了30% 。
Rust没有类 , 有的是结构体加方法 , 我喜欢这种简单的概念 。
Rust没有类继承,只有接口,虽然接口可以提供默认的实现 。这样一来,在大型项目里原来类继承来重用代码的效果是否就要用成员变量实例来完成呢?
Rust没有null,取而代之的是None和OptionT , 也因此,结构体在初始化的时候必须初始化所有字段 。
Rust有我一直很想要的错误值返回机制,而不必通过抛异常或者需要每每定义包含结果和错误体实现 。
Rust用send和sync两个接口来处理多线程并发,其中ArcT和MutexT分别实现了这两个接口,简单易用 。
Rust目前没有一个强大的IDE,支持断点调试,变量监控等 。
它跟现在动态语言是两个截然不同的方向,它适合一些资深的程序员,我倒是觉得有必要有这么一本书,叫《从C到Rust,你需要改善的20个编程 习惯》,能从实践上告诉开发者Rust里我们应该遵从什么样的编程习惯 。Rust未来是否像C那样流行开来成为新一代的主流语言没有人能够知道,但它绝对 是值得你去了解和关注的语言 。
进一步的思考:反转链表 - Java和Rust的不同实现
Rust的list应该怎么定义,譬如反转列表又是怎么做呢?
由于ownership的机制和不存在空指针的情况,很多在其他带GC的语言能够跑起来的程序在Rust下面就要换一种做法 。最近试用Rust的基础数据结构时,更加加强了我的看法 。下面以最原始的链表list为例 。
在Java中,考虑最基本的链表定义
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(val);
ListNode pNext = this.next;
while (pNext != null) {
sb.append(",");
sb.append(pNext.val);
pNext = pNext.next;
}
sb.append("]");
return String.format("%s", sb.toString());
}
}
如果我们要反转链表 , 可以这么做:
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode pNext = head.next;
ListNode pPrevious = null;
while (head != null) {
pNext = head.next;
head.next = pPrevious;
pPrevious = head;
head = pNext;
}
return pPrevious;
}
那如果我们按照一般思维,在Rust里对应的实现就是这样子的:
struct ListNode{
id :i32,
next :OptionBoxListNode
}
反转链表:
fn reverseList2(head :mut OptionBoxListNode) - OptionBoxListNode {
match *head{
None = None,
Some(head) = {
let mut head = Some(head);
let mut pNext = head.unwrap().next;
let mut pPrevious:OptionBoxListNode = None;
while true {
match head {
None ={break;}
_ ={}
}
pNext = head.unwrap().next;
head.unwrap().next = pPrevious;
pPrevious = head;
head = pNext;
}
pPrevious
}
}
}
然后编译,报了以下错误:
=》match *head{
ERROR:cannot move out of borrowed content
=》 pNext = head.unwrap().next;
ERROR:cuse of moved value: `head`
这些错误就是因为Rust的ownership机制,让我们无法像Java或者C里保存临时变量,特别是在循环里 。反复试过各种写法 , 都行不通 。
最后,换成这么来做
链表定义:
use List::*;
enum List {
Cons1(i32, BoxList),
Nil,
}
// Methods can be attached to an enum
impl List {
#[inline]
fn new() - List {
Nil
}
#[inline]
fn prepend(self, elem: i32) - List {
Cons1(elem, Box::new(self))
}
fn len(self) - i32 {
match *self {
Cons1(_, ref tail) = 1tail.len(),
Nil = 0
}
}
fn stringify(self) - String {
match *self {
Cons1(head, ref tail) = {
format!("{}, {}", head, tail.stringify())
},
Nil = {
format!("Nil")
},
}
}
}
fn reverseList(list:List, acc:List ) - List{
match list{
Cons1(val,tail) = {
reverseList(*tail,acc.prepend(val))
}
Nil = acc
}
}
fn main() {
let mut head = List::new();
let mut i=0;
while i10 {
i =1;
head = head.prepend(i);
}
println!("{:30}",head.stringify());
let result = List::new();
let result = reverseList(head,result);
span style="white-space:pre"/spanprintln!("{:30}",result.stringify());
}
从结果可以看到,链表已经实现反转了 。所以在Rust下面,很多做法都要换一下 。有人说这就是Rust函数式编程的思维 。我但愿这种递归式的做法不会有溢出 。
rust串联器怎么用Rust串联器执行:1) Rust代码经过分词和解析,生成AST(抽象语法树) 。2) 然后把AST进一步简化处理为HIR(High-level IR),目的是让编译器更方便的做类型检查 。3) HIR会进一步被编译为MIR(Middle IR),这是一种中间表示4) 最终MIR会被翻译为LLVM IR,然后被LLVM的处理编译为能在各个平台上运行的目标机器码 。
LLVM相对于gcc的一大改进就是大大提高了中间语言的生成效率和可读性, LLVM的中间语言是一种介于c语言和汇编语言的格式 , 他既有高级语言的可读性,又能比较全面地反映计算机底层数据的运算和传输的情况,精炼而又高效 。
rust替换函数名1、函数简介
①、Rust 的函数使用关键字 fn 开头,函数名称使用snake case规范风格(所有字母小写并使用下划线分隔);
②、可以有一系列的输入参数 , 可以有一个返回值;
③、函数返回可以使用 return 语句,也可以使用表达式(末尾不带分号);
④、函数也可以不写返回类型,这种情况下,编译器会认为返回类型是unit();
⑤、可执行程序的入口是 fn main();
⑥、调用函数时 , Rust不关心函数定义在哪(前后都无所谓),只要定义了就行;
⑦、Rust 函数体内可以定义其它模块,比如静态变量、常量、函数、trait、类型等 。
2、函数实例
fn main() {
let num = add(1,2);
println!("{}",num)
}
fn add(x:i32,y:i32) - i32{
x y
}
登录后复制
3、函数返回值
需要注意语句和表达式的区别,表达式没有分号,有返回值 。语句结尾有分号,没有返回值 。
所以对于如下函数,如果函数体加上分号,则会报错:
fn add(x:i32,y:i32) - i32{
x y;
}
【java调用rust代码 java rust】登录后复制
把 x y; 变成表达式即可(去掉末尾的分号) 。
4、发散函数
Rust 支持一种特殊的发散函数(Diverging functions),它的返回类型是感叹号 ! 。
如果一个函数根本就不能正常返回,那么它就可以这样写:
fn diverges() - !{
//panic! 会直接导致栈展开 , 所以这个函数调用后面的代码都不会执行,它的返回类型就是!
panic!("函数不能返回");
}
登录后复制
发散函数的最大特点:可以被转换成任意一个类型 。
在Rust 中 , 有下列情况返回类型都是 ?。?
1.panic! 以及基于它实现的各种函数/宏,比如unimplemented!、unreachable!
2.死循环loop{}
3.进程退出函数std::process::exit以及类似的libc 中的exec一类函数 。
5、const fn
函数可以用 const 关键字修饰,这样的函数可以在编译阶段被编译器执行,返回值也被视为编译期常量 。
需要注意的是:const 函数是在编译阶段执行的,因此相比普通函数有很多限制,并非所有的表达式和语句都可以在其中使用 。
关于java调用rust代码和java rust的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。

    推荐阅读