2018年12月16日日曜日

Golangのfake google searchフレームワークをjavaで書いてみた。その2

その1を投稿したあと、某友人から「CompletableFuture使ったらもっとスッキリできるよ」って情報もらった。なので、それ使って書き換えてみた。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.function.Supplier;

class FakeSearch implements Supplier<String>{
    String name = null;
    public FakeSearch(String name){
        this.name = name;
    }
    @Override    public String get() {
        try {
            Thread.sleep((new Random().nextInt(10) * 1000));
        }catch (Exception e){

        }
        return name;
    }
}

public class CmpGoChannel2 {
    public static void First(Supplier<String> supplier1,Supplier<String> supplier2) throws Exception{
        CompletableFuture<String> future1 =
                CompletableFuture.supplyAsync(supplier1);
        CompletableFuture<String> future2 =
                CompletableFuture.supplyAsync(supplier2);

        CompletableFuture<Object> future3 = CompletableFuture.anyOf(future1, future2);
        System.out.println(future3.get());
    }

    public static void main(String... args) throws Exception{
        FakeSearch web1 = new FakeSearch("Web1");
        FakeSearch web2 = new FakeSearch("Web2");
        FakeSearch image1 = new FakeSearch("Image1");
        FakeSearch image2 = new FakeSearch("Image2");
        FakeSearch video1 = new FakeSearch("Video1");
        FakeSearch video2 = new FakeSearch("Video2");


        CmpGoChannel2 c = new CmpGoChannel2();
        List<Future<String>> list = new ArrayList<>();

        First(web1,web2);
        First(image1,image2);
        First(video1,video2);

    }

}
while(true)と終了フラグで乗り切ってた汚い部分がだいぶきれいになった。

public static void First(Supplier<String> supplier1,Supplier<String> supplier2) throws Exception{
    CompletableFuture<String> future1 =
            CompletableFuture.supplyAsync(supplier1);
    CompletableFuture<String> future2 =
            CompletableFuture.supplyAsync(supplier2);
    CompletableFuture<Object> future3 = CompletableFuture.anyOf(future1, future2);
    System.out.println(future3.get());
}


行数もざっくり60行程度まで収まった。

2018年12月15日土曜日

Golangのfake google searchフレームワークをjavaで書いてみた。その1

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の魅了が十分に表れている。