Go concurrency patternsで触れられていた
Google SearchのFake frameworkのコードが非常にすっきり記載されていて感動したので、これをJavaで書いたらどうなるんだろうと好奇心から書いてみた。
まず、Go言語の方のやつを、書き出してみる。
package main
import (
"time" "math/rand" "fmt")
var (
Web1 = fakeSearch("web1")
Image1 = fakeSearch("image1")
Video1 = fakeSearch("video1")
Web2 = fakeSearch("web2")
Image2 = fakeSearch("image2")
Video2 = fakeSearch("video2")
)
type Result string
type Search func(query string) Result
func fakeSearch(kind string) Search {
return func(query string) Result {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return Result(fmt.Sprintf("%s result for %q\n", kind, query))
}
}
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
func Google(query string) (results []Result) {
c := make(chan Result)
go func() { c <- First(query, Web1, Web2) } ()
go func() { c <- First(query, Image1, Image2) } ()
go func() { c <- First(query, Video1, Video2) } ()
timeout := time.After(80 * time.Millisecond)
for i := 0; i < 3; i++ {
select {
case result := <-c:
results = append(results, result)
case <-timeout:
fmt.Println("timed out")
return }
}
return}
func main() {
rand.Seed(time.Now().UnixNano())
start := time.Now()
result := First("golang",
fakeSearch("replica 1"),
fakeSearch("replica 2"))
elapsed := time.Since(start)
fmt.Println(result)
fmt.Println(elapsed)
}
コードの解説はスライドに譲るとして、First関数の中での、return <-c の使い方が素晴らしい。
で、これをJavaで書いてみた。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
class Web1 implements Callable<String>{
public String call() {
try {
Thread.sleep(10*1000);
}catch (Exception e){
}
return "Web1";
}
}
class Web2 implements Callable<String>{
public String call() {
return "Web2";
}
}
class Image1 implements Callable<String>{
public String call() {
try {
Thread.sleep(10*1000);
}catch (Exception e){
}
return "Image1";
}
}
class Image2 implements Callable<String>{
public String call() {
return "Image2";
}
}
class Video1 implements Callable<String>{
public String call() {
try {
Thread.sleep(10*1000);
}catch (Exception e){
}
return "Video1";
}
}
class Video2 implements Callable<String>{
public String call() {
return "Video2";
}
}
public class CmpGoChannel {
public static void First(ExecutorService executor1,Callable<String> callable1, Callable<String> callable2) throws Exception{
List<Future<String>> list = new ArrayList<>();
list.add(executor1.submit(callable1));
list.add(executor1.submit(callable2));
boolean loopDone = false;
while(true) {
for (Future<String> f : list) {
System.out.print(".");
if (f.isDone()) {
System.out.println(f.get());
loopDone = true;
break;
}
}
if(loopDone){
break;
}
}
}
public static void main(String... args) throws Exception{
ExecutorService executor1 = Executors.newFixedThreadPool(2);
ExecutorService executor2 = Executors.newFixedThreadPool(2);
ExecutorService executor3 = Executors.newFixedThreadPool(2);
CmpGoChannel c = new CmpGoChannel();
List<Future<String>> list = new ArrayList<>();
Callable<String> web1 = new Web1();
Callable<String> web2 = new Web2();
First(executor1,web1,web2);
Callable<String> image1 = new Image1();
Callable<String> image2 = new Image2();
First(executor2,image1,image2);
Callable<String> video1 = new Video1();
Callable<String> video2 = new Video2();
First(executor3,video1,video2);
executor1.shutdown();
executor2.shutdown();
executor3.shutdown();
}
}
まず気づいたのが、Javaにchannelに相当する仕組みがなかったこと。JavaのConcurrencyはそんなに人気のあるものではなかったが、そうは言ってもそれなりに使われている実績はあるので、書こうとしてみて「そういや、なかったな」みたいな気付きがあった。
なので、ここは
while(true) {
for (Future<String> f : list) {
System.out.print(".");
if (f.isDone()) {
System.out.println(f.get());
loopDone = true;
break;
}
}
if(loopDone){
break;
}
}
といった感じで while(true)と終了フラグで乗り切るというちょっと汚いコードで対応した。
このあたりですでに、簡素なコードを書けるというGoの魅了が十分に表れている。