Chapter 09

goroutine と共に利用する

ikawaha
ikawaha
2021.01.03に更新
このチャプターの目次

kagome の形態素解析器のコンストラクタや形態素解析メソッドはスレッドセーフに作ってあるので、特段気にせず goroutine で利用できます。

複数スレッドから利用するサンプルとして、テキストを文区切りして、区切った文ごとに goroutine を呼び出して形態素解析する Word Count のサンプルを以下に示します。

Word Count

以下の例では形態素解析器をあらかじめ用意して複数の goroutine で再利用していますが、このような使い方は特に問題になりません。また、gotoutine 内で毎回 tokenizer.New(ipa.Dict()) と形態素解析器のコンストラクタよ呼び出しても問題ありません。辞書は singleton で同じものを利用するので、sync.Once を自分で書いたりする必要もありません(ただし初回の生成だけ辞書を読み込む処理が走るので少し遅くなります)。

package main

import (
	"bufio"
	"fmt"
	"io"
	"strings"
	"sync"

	"github.com/ikawaha/kagome-dict/ipa"
	"github.com/ikawaha/kagome/v2/filter"
	"github.com/ikawaha/kagome/v2/tokenizer"
)

const sampleText = `人魚は、南の方の海にばかり棲んでいるのではあ
                    りません。北の海にも棲んでいたのであります。
                    北方の海うみの色は、青うございました。ある
                    とき、岩の上に、女の人魚があがって、あたりの景
                    色をながめながら休んでいました。

                     小川未明作 赤い蝋燭と人魚より`

// 名詞フィルター
var nounFilter = filter.NewPOSFilter(filter.POS{"名詞"})

func WordCount(ch chan<- string, r io.Reader) {
	// 形態素解析器をあらかじめ用意します
	t, err := tokenizer.New(ipa.Dict())
	if err != nil {
		panic(err)
	}
	// 文区切り用の scanner
	scanner := bufio.NewScanner(r)
	scanner.Split(filter.ScanSentences)
	var wg sync.WaitGroup
	for scanner.Scan() {
		wg.Add(1)
		go func(s string) {
			defer wg.Done()
			tokens := t.Tokenize(s)   // goroutine から同じ形態素解析器を呼んでも大丈夫です
			nounFilter.Keep(&tokens)  // 名詞フィルターに該当するものだけを残す
			for _, tok := range tokens {
				ch <- tok.Surface
			}
		}(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		close(ch)
	}
	wg.Wait()
	close(ch)
}

func main() {
	ch := make(chan string, 1)

	r := strings.NewReader(sampleText)
	go WordCount(ch, r)

	m := map[string]int{}
	for {
		s, ok := <-ch
		if !ok {
			break
		}
		m[s]++
	}
	for k, v := range m {
		fmt.Printf("%v\t%v\n", k, v)
	}
}

結果

北方	1
方	1
小川	1
未明	1
北	1
とき	1
作	1
蝋燭	1
海	3
岩	1
女	1
景色	1
人魚	3
の	2
色	1
上	1
あたり	1
南	1