• 编程实践心得与设计思想


    Scala.Actor


                这段时间系统的学习了Scala,编程思想上可谓收获不少,想从里面挑些值得写的东西分享给大家,Scala的Actor

    可谓这门语言的一个亮点,函数式编程核心价值在于多核编程,所以就打算说说这个Actor,总结一下学习心得。先很俗

    套的简单谈谈概念,然后会拿四个例子做补充。主要内容包括基本原理,设计思想,单机环境并发测试。

     

                Actor是一种基于事件的轻量级线程,在以前的并发模型中,我们需要关注共享的数据结构,而使用Actor则需要

    关注操作数据的代码结构,因为减少了数据的共享。Actor的主要能力来源于消息传递,而不是采用阻塞调用的处理形式。

    如果创建直接或间接扩展 Actor的类,要确保对对象的所有调用都通过消息传递进行。

     

         我把Actor的设计思想归为两类,使用目的归为两类。从设计思想上来说Scala推荐的是以消息传递为核心的设计

    思想,由于Scala可以无缝使用Java类库,所以也可以采用以共享数据为核心的设计,当然也可以写出混合式设计风格的

    。使用目的主要有两种,一种是Scala提供APIJava调用,另一种就是Scala自给自足。举三个例子,例子很简单,是一

    个累加器。

      

    1、以消息传递为核心的设计:使用Actoractor方法,使用不可变对象,不考虑数据共享问题,以消息传递为设计核心。

     

     1 import actors._, Actor._
     2 /*
     3  * Author:ShiYang
     4  * Blog:http://shiyangxt.cnblogs.com
     5  * */
     6 object SendMessageStyle {
     7 
     8   def main(args: Array[String]): Unit = {
     9     val caller = self
    10     val accumulator = actor {
    11       var continue = true
    12       var sum = 0
    13       loopWhile(continue) {
    14         reactWithin(500) {
    15           case number: Int => sum += number
    16           case TIMEOUT =>
    17             continue = false
    18             caller ! sum
    19         }
    20       }
    21     }
    22     accumulator ! 1
    23     accumulator ! 2
    24     accumulator ! 3
    25     receiveWithin(1000) {
    26       case result => println("Total is " + result)
    27     }
    28   }
    29 }

     

    2、以共享数据为核心的设计:构建由Actor继承共享数据操作类,以共享数据为核心。

     

     1 import actors._, Actor._
     2 
     3 /*
     4  * Author:ShiYang
     5  * Blog:http://shiyangxt.cnblogs.com
     6  * */
     7 object SharedDataStyle {
     8   case class Add(number: Int)
     9   case class GetResult(sender: Actor)
    10 
    11   class AddActor extends Actor {
    12     override def act(): Unit = process(0)
    13     def process(value: Int): Unit = {
    14       reactWithin(500) {
    15         case Add(number) => process(value + number)
    16         case GetResult(a) => a ! value; process(value)
    17         case _ => process(value)
    18       }
    19     }
    20   }
    21 
    22   def main(args: Array[String]): Unit = {
    23     val addActor = new AddActor
    24     addActor.start()
    25     addActor ! Add(1)
    26     addActor ! Add(2)
    27     addActor ! Add(3)
    28     addActor ! GetResult(self)
    29     receiveWithin(1000) {
    30       case result => println("Total is " + result)
    31     }
    32   }
    33 }

     

    3、以API形式提供给Java程序使用:由于Java不能直接向Actor发消息,所以需要对Scala!()方法进行封装。

     

     1 import actors._, Actor._
     2 /*
     3  * Author:ShiYang
     4  * Blog:http://shiyangxt.cnblogs.com
     5  * */
     6 object ForJavaStyle {
     7   case class Add(number: Int)
     8   case class GetResult(sender: Actor)
     9   private class AddActor extends Actor {
    10     override def act(): Unit = process(0)
    11     def process(value: Int): Unit = {
    12       reactWithin(500) {
    13         case Add(number) => process(value + number)
    14         case GetResult(a) => a ! value; process(value)
    15         case _ => process(value)
    16       }
    17     }
    18   }
    19   private val addActor = new AddActor
    20   addActor.start()
    21   private def add(sender: Actor, num: Int): Unit = {
    22     sender ! Add(num)
    23   }
    24   private def getResult(sender: Actor): Int = {
    25     sender ! GetResult(self)
    26     receiveWithin(1000) {
    27       case result: Int => result
    28     }
    29   }
    30   def addForJava(num: Int): Unit = {
    31     add(addActor, num)
    32   }
    33   def getResultForJava(): Int = {
    34     getResult(addActor)
    35   }
    36 }

     

    Java端调用代码:

     

     1 /*
     2  * Author:ShiYang
     3  * Blog:http://shiyangxt.cnblogs.com
     4  * */
     5 public class GetFromScala {
     6 
     7     public static void main(String[] args) {
     8         ForJavaStyle$.MODULE$.addForJava(1);
     9         ForJavaStyle$.MODULE$.addForJava(2);
    10         ForJavaStyle$.MODULE$.addForJava(3);
    11         System.out.println("Total is "
    12                 + ForJavaStyle$.MODULE$.getResultForJava());
    13     }
    14 }


          通过上面的例子可见ScalaJava语言有非常大的补充,提高了生产力。为Java提供了轻松实现多核并行编程的能

    力。为了进一步测试Actor的并发性能,于是做了一个简单的单机环境并发测试。程序是构建一个Actor动态有序数组,

    并发创建NActor对象,为了证明这些对象全都可用,顺序从数组的第一个Actor发消息到最后一个Actor,只有当一个

    Actor接收到前一个Actor发送的消息后,才向后一个Actor发送消息。当最后一个数组元素接收到消息后,再把消息从数组

    尾部用同样处理过程逆序发送到数组头部。这个消息发送过程不是并发处理,是顺序处理。这里只是为了证明这些对象全都

    可用。如果为了测试并发处理,可以修改程序,让每个数组元素给后一位数组元素发消息。这样就会看到输出混乱的发送信

    息,因为并发是无序的。


    测试环境:双核4G内存,Windows XPSun JVM1.6,单机环境,Scala版本2.9.0.1

     

    测试结果:当使用Receive方法接收消息时,由于Receive会在结束任务前一直持有线程,而Scala在后台默认只给Receive

    方法启动256个线程,我的程序又是顺序的发消息,而且不是临时接收器(只处理一次消息),所以Receive在这种情况下,

    只有255个并发。React接收器由于不需要长期持有线程,空闲即释放线程。所以React在本程序中可以跑20w的并发,如果

    简单优化一下JVM,就可以达到100w的并发量。默认React接收器后台会调用4个线程组成的线程池。如果修改程序让每个数

    组元素给后一位数组元素并发的发消息,那么在不阻塞线程的情况下,Receive方法也可以达到和React一样的并发量。因为

    这个测试程序是顺序发送消息,所以就没有设置超时,如果是并发环境,建议加上超时,避免线程阻塞。

     

    下面是测试程序:

     

     1 import actors._, Actor._, java.util._
     2 /*
     3  * Author:ShiYang
     4  * Blog:http://shiyangxt.cnblogs.com
     5  * */
     6 object ConcurrentTest {
     7 
     8   val actors = new ArrayList[Actor]
     9   val length = 1000000
    10   var startTime = System.nanoTime
    11 
    12   def main(args: Array[String]): Unit = {
    13     for (i <- 0 to length)
    14       actors.add(actor {
    15         info("react: " + i + " actor created")
    16         reactMessage
    17       })
    18     actors.get(0! (00)
    19   }
    20 
    21   def info(msg: String) = println(msg + " received by " +
    22     Thread.currentThread)
    23 
    24   def receiveMessage {
    25     var continue = true
    26     while (continue) {
    27       receive {
    28         case (id: Int, direction: Int) =>
    29           sendMessage(id: Int, direction: Int)
    30         case "finish" =>
    31           continue = false
    32           val endTime = System.nanoTime
    33           println("Finish, spend time:" +
    34             (endTime - startTime) / 1000000000.0 + " secs")
    35         case _ => println("input error")
    36       }
    37     }
    38   }
    39 
    40   def reactMessage {
    41     var continue = true
    42     loopWhile(continue) {
    43       react {
    44         case (id: Int, direction: Int) =>
    45           sendMessage(id: Int, direction: Int)
    46         case "finish" =>
    47           continue = false
    48           val endTime = System.nanoTime
    49           println("Finish, spend time:" +
    50             (endTime - startTime) / 1000000000.0 + " secs")
    51         case _ => println("input error")
    52       }
    53     }
    54   }
    55 
    56   //direction=0->sendLatter;direction=1->sendFormer
    57   def sendMessage(id: Int, direction: Int) {
    58     if (direction == 0 && id != length) {
    59       info("Actor" + id + " send message to the Actor" + (id + 1))
    60       actors.get(id + 1! (id + 10)
    61     } else if (id != 0 && direction == 1) {
    62       info("Actor" + id + " send message to the Actor" + (id - 1))
    63       actors.get(id - 1! (id - 11)
    64     } else if (direction == 0 && id == length) {
    65       actors.get(length) ! (length, 1)
    66     } else if (id == 0 && direction == 1) {
    67       actors.get(0! "finish"
    68     }
    69   }
    70 }

     

           前些天看到Scala正在努力支持.net平台,不过我觉得.net平台有F#,所以对于.net程序员来说Scala提供不了什么

    附加价值。如果非要找出来这样做的优点的话,就是Scala可以同时支持两个主流平台,野心不小。如果看后有所收获,请

    推荐让更多人看到,算我为Scala的推广尽了点绵薄之力。行文仓促,如果有不对的地方,欢迎指正。


  • 相关阅读:
    【leetcode】二叉搜索树中的众数
    【leetcode】和为零的N个唯一整数
    【leetcode】统计位数为偶数的数字
    【leetcode】寻找数组的中心索引
    【leetcode】找出给定方程的正整数解
    【leetcode】寻找比目标字母大的最小字母
    【leetcode】找出井字棋的获胜者
    输出0~N之间的素数
    判断正整数是否是素数(数据结构课后练习题2.13a)
    052-85
  • 原文地址:https://www.cnblogs.com/baiduligang/p/4247036.html
Copyright © 2020-2023  润新知