マルチスレッドはてなキーワードしりとりを改造・その1

まず先日書いたコードはしりとりの最重要ルールの一つである「同じ単語は二度使えない」ルールを判定するための処理を丸々書き忘れていました(^_^;)
文字通り「忘れていた」んですが入力を受け付ける部分も関数ではなくクラス化して継承すればそういったミスは起こり得ないのでそうすべきかもしれません。今回もしてませんが・・・。
とりあえずその処理を書き足しました。

またコメントで教えていただいた内容を参考に辞書登録処理の排他制御付加に__setitem__メソッドを使ってみました。これをつかうと他の部分を一切変更せずに排他制御を加えられるので非常に便利です。
前回のコードではなんとなくセマフォを使っていた部分ですが、やはり本来のセマフォの使い方と異なっていたので素直にLockに変更しました。

スレッド間の待ち合わせの際にwhileループを使っていましたが待つ間無駄にループをグルグル回してCPUリソースのムダだったのでjoin()を使うように変更しました。
ふりがなと元の単語をセットでループする部分にリストのタプルをタプルのリストに変換してくれるzip関数を使うように変更しました。

APIから受け取ったふりがなをいったんリストにしてから再度Furiganaクラスのインスタンスのリストを作成していましたが直接インスタンスのリストを作成するようにしました。これによりmain関数を少しすっきりさせました。

# coding: utf-8

import shiritori
import threading


class PrlSupportHFD(shiritori.HatenaFuriganaDict):
    """親クラスの辞書登録処理に排他制御を加える"""
    def __init__(self):
        self.lock = threading.Lock()
        shiritori.HatenaFuriganaDict.__init__(self)

    def __setitem__(self, key, value):
        self.lock.acquire()
        shiritori.HatenaFuriganaDict.__setitem__(self, key, value)
        self.lock.release()


class ParallelAsk(threading.Thread):
    """スレッド設定"""
    lock = threading.Lock()

    def __init__(self, index, word, furiganadict, furigana):
        self.index = index
        self.word = word
        self.furiganadict = furiganadict
        self.furigana = furigana
        threading.Thread.__init__(self)

    def run(self):
        kana = self.furiganadict[self.word]

        ParallelAsk.lock.acquire()
        self.furigana[self.index] = shiritori.Furigana(kana, self.word)
        ParallelAsk.lock.release()


def multi_input_reader(usedwords, wordsmax=10):
    """入力を受けとり、単語数が最大受付数を超えていないか
       受け付けた単語内ですでに重複していないかを調べる
    """
    wordsstr = raw_input(u"次の単語を入力してください。次は"
                         + str(len(usedwords) + 1)
                         + u"語目です。")

    wordsstrU = unicode(wordsstr).replace(u"、", u",")

    wordsU = wordsstrU.split(",")

    if len(wordsU) > wordsmax:
        raise WordsInputError(value=wordsmax)

    if len(wordsU) != len(set(wordsU)):
        raise RepeatedWordsError()

    for v in wordsU:
        if v in usedwords:
            raise shiritori.UniquenessError(v, usedwords[v])

    return wordsU


class WordsInputError(shiritori.ShiritoriRuleError):
    def __str__(self):
        return (u"入力した単語が多すぎます。一度に入力できるのは"
               + str(self.value)
               + "語までです。")


class RepeatedWordsError(shiritori.ShiritoriRuleError):
    def __str__(self):
        return u"入力した単語に重複があります。"


def main():
    WORDS_MAX = 10  # 一度に入力を受け付ける最大単語数
    furiganadict = PrlSupportHFD()
    usedwords = {}

    initial = u"あ"  # しりとりの最初の文字

    print (u"最初のもじは「" + initial + u"」です")

    while True:
        try:
            # wordsUは元の単語、furiganasはその読みのリスト
            wordsU = multi_input_reader(usedwords, WORDS_MAX)

            furiganas = [i for i in wordsU]

            askers = []
            for i, word in enumerate(wordsU):
                asker = ParallelAsk(i, word, furiganadict, furiganas)
                askers.append(asker)
                asker.start()

            #並行処理中のスレッドすべてが終わるのを待つ
            for asker in askers:
                asker.join()

            for (origin, furigana) in zip(wordsU, furiganas):
                furigana.exam(initial, origin)
                initial = furigana.creatIL()
                print origin
                usedwords[origin] = len(usedwords) + 1

        except shiritori.CrtShiritoriError, e:
            print e
            break
        except shiritori.ShiritoriRuleError, e:
            print e
            continue

    print u"終了です"

if __name__ == "__main__":
    main()