GPars的Actor实现

Actor是一种Continuation技术,可以在少量的线程运行大量Actor对象。Actor对象之间通过消息机制进行交互。而Actor本身线程安全,这样的模型使并发编程的复杂度降低,同时也在一定的场景下实现了可扩展性。

gpars是Java和Groovy都可以使用的并行编程库,他实现了Actor、Agent、DataFlow等模型,旨在为Groovy提供高层的并行编程模型。以下分析gpars 0.12中非阻塞Actor的实现。

@Grab("org.codehaus.gpars:gpars:0.11")
import groovyx.gpars.actor.Actors

def worker = Actors.actor {
    loop {
        react {
            reply it.reverse()
        }
    }
}

def console = Actors.actor {
    worker << "Hello GPars"
    react {
        println it
    }
}

console.join()

首先,在工厂类中Actors里会初始化一个DefaultPGroup用来封装后台的线程池、管理actors。Actors默认使用ResizeablePool,他是JDK Concurrent Framework中的ThreadPoolExecutor的封装,coreSize和maxSize不同所以称Resizeable。

Actors的工厂方法actor生成的是DefaultActor,它是非阻塞actor的默认实现。(ActorGroup:67)

DefaultActor的构造方法接受一个Groovy的闭包对象,将其封装为DefaultActorClosure对象后,调用其父类AbstractLoopingActor的initialize方法(DefaultActor:73).

initialize方法创建一个Runnable对象AsyncMessagingCore,并将线程池传递给core对象。(AbstractLoopingActor:57)AsyncMessagingCore对象负责消息的传递和处理,是线程池处理的目标对象。

调用start启动actor后,actor会向自己发送一个start消息(AbstractLoopingActor:173).
core获得start消息后,调用DefaultActor覆盖的handleStart方法(DefaultActor:328)。

在handleStart中,actor会调用用户传入的闭包方法。上面的例子是一种典型的用法,loop是DefaultActor中的方法,loop也并不是无限空转的,他仅在收到消息被时被触发(DefaultActor:191)react也是DefaultActor中的方法,它将nextContinuation方法设为内部闭包对象,用来处理actor接收的消息。

向Actor发送消息,是通过actor的send方法和重载的leftShift运算符进行操作。(AbstractLoopingActor:236)actor调用core的store方法,将ActorMessage压入core的队列中。入队列之后,core会检查锁对象activeUpdater,判断当前core是否在线程处理中,如果不在,则将core加入线程池中处理。activeUpdater是一个AtomicIntegerFieldUpdater对象,他的compareAndSet可以保证原子性。而通过activeUpdater也可以保证同一时刻只有一个core被线程池处理,从而使actor的线程不安全代码也线程安全地运行。

进入线程池后,core首先将自己放进threadlocal对象中,并保存当前线程的引用。然后会循环消费MessageQueue中的消息直至Queue的可处理部分为空。(AsyncMessagingCore:126)。handleMessage在AbstractLoopingActor中被覆盖,会根据消息的类型进行分发调用(前面提到的start消息就是一种)。默认的业务消息,在DefaultActorClojure中调用DefaultActor的onMessage方法处理。

onMessage中,react的闭包会被调用来处理业务。之后nextContinuation被置为null,这时loop闭包被重新调用,react闭包重新被赋给nextContinuation。这部分代码就是前面所说的loop并非空转,而是在消息处理完成后重新准备而已。

此外,core的MessageQueue的实现是DefaultMessageQueue。它使用两个LinkedList作为输入(向actor输入)队列和输出队列,当输入队列为空时,通过同步方法swap交换输入输出队列。swap是整个actor系统里唯一一个同步方法。这样的机制保证actor的core在线程池中处理时,外界仍然可以向actor发送消息,消息会在actor被调度出线程池之前全部处理掉。不过,他的前提是只有一个线程读这个队列,这个条件在actor系统里,通过core对象的activeUpdater可以有效的保证。

Actor模式采用这种onDemand方式的线程使用,允许大量的actor共存,并只有活跃的actor会占用线程,非活跃状态的actor处在dettach状态,并不消耗计算资源,取消了空转的loop。

geb for browser functional testing

虽然现在不做前段了,但是发现好的工具还是很兴奋。今天在twitter上看到Grails in Action的作者 @pledbrook 转了一个geb 0.4的消息,顺带看了一下这个工具

http://geb.codehaus.org

geb项目旨在创造一套groovy dsl帮助人们进行webapp的functional test。它是对selenium的封装,举例:

@Grapes([
    @Grab('org.seleniumhq.selenium:selenium-firefox-driver:latest.release'),
    @Grab('org.codehaus.geb:geb-core:latest.release')
])
import geb.*

println "Dependencies downloaded, ready for testing"
Browser.drive('http://sunng.info:8000/Pacajus'){
    assert title== 'Pacajus'

    assert $("p", 3).text() == 'Population: 41558'
}
println "Tested, bye"

打开页面,执行断言。如果断言失败,driver方法会报null:
Caught: geb.error.DriveException: null

只要在命令行用groovy执行即可,grapes会搞定依赖关系。

很方便吧,文档上说还可以跟grails / junit等等集成,快去看看吧
http://geb.codehaus.org/manual/latest/index.html

The post is brought to you by lekhonee v0.7

Maven recipe #1: Test Java code with groovy

问题: 受够了,不想写Java了,写个含有数据的map还要new出来一个一个put进去,想用groovy解决单元测试
解决: gmaven+groovy eclipse插件可以解决这个需求
在pom.xml中添加gmaven的依赖,注意,仅用来测试。老大不让生产代码里有不可靠的东西。

...
        <dependency>
            <groupId>org.codehaus.groovy.maven.runtime</groupId>
            <artifactId>gmaven-runtime-default</artifactId>
            <version>1.0-rc-3</version>
            <scope>test</scope>
        </dependency>
...
            <plugin>
                <groupId>org.codehaus.groovy.maven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>1.0-rc-3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generateStubs</goal>
                            <goal>compile</goal>
                            <goal>generateTestStubs</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

在eclipse中新建source folder, src/test/groovy,在其中创建groovy test case即可

Groovy http-builder tips

最近用Groovy的HttpBuilder模块开发RESTful服务的客户端,遇到不少问题,浪费不少时间

网上的例子都是从Grape开始的,但是按照例子上运行,无论是通过grape install还是在代码里@Grab都没有办法把http builder加到classpath里,甚至依赖关系已经下载到~/.groovy/grapes里了,但是还是在import的时候报错。用Grails的时候也是一样,如果通过ivy定义依赖,通过ant下载到lib目录中,在grails shell里还是没有办法引用。不理解了,难道还需要手动再指定path吗。最后用grails的rest插件(grails install-plugin rest)总算是下载到了path中了。

第二是http builder的报错太智能了,它会把服务器端50x的报错输出在客户端。在查看错误的时候一定要分清楚。

第三是net.sf.json-lib这个库在转json的时候有一点问题,比如这样的js对象
{“result”:”success”}

会因为key上有引号而无法解析,必须是这样的格式:
{result: “success”}

至于那种是正确的json我也没有兴趣了解了,只是非常遗憾的是couchdb返回的都是前一种形式。

当然了,瑕不掩瑜,http builder还是提供了非常方便的封装,比直接用HttpURLConnection或是HttpClient更加Groovy

Mapping Geometry in Grails and MySQL

针对地理数据的ORM,有一个Hibernate的扩展HibernateSpatial项目可以将JTS对象映射到MySQL/PostGIS/Oracle中。这个扩展同样可以用在Grails里,这里有一篇简单的介绍,关于在Grails和MySQL中管理地理数据:
http://www.grails.org/MySQL+GIS-Geometry+with+Grails

不过按照这个文章里介绍的方法用,很可能会遭遇这样的报错:

org.hibernate.MappingException: No Dialect mapping for JDBC type: 2003

这个问题最终在这里得到了解答:
http://n2.nabble.com/No-Dialect-mapping-for-JDBC-type-2003-td1141106.html
按照邮件列表里的反映,上面的配置在Postgis里是可以work的,但是如果用Mysql还需要指定JPA的columnDefinition,对应的Hibernate属性是sql-type。虽然作者承诺会在今后的版本里修改这个问题,不过眼下的M2版本还没有修正这个问题。为此,Grails的用户特地提出在Grails中加入sql-type的支持:
http://jira.codehaus.org/browse/GRAILS-3201
现在按照下面文档的说明,可以在mapping里指定sqlType了:
http://grails.org/doc/latest/ref/Database%20Mapping/column.html

实例代码里的domain定义应该改成:

import com.vividsolutions.jts.geom.Polygon
import org.hibernatespatial.GeometryUserType

public class MyPoly {
    String name
    Polygon poly

    static mapping = {
        poly type: GeometryUserType, sqlType:"GEOMETRY"
    }

}

于是,再也没有莫名其妙的No Dialect报错了。