並行処理 その3
Rubyの勉強
スレッド間の同期の実験、下のプログラムでボタン1に関連付けられているメソッドは2つのスレッドが並行して実行される。一つは変数@xを+1、変数@yを−1する処理。もう一方は@xと@yの合計を表示する処理である。前者のスレッドで変数@xと@yの値を変化させているが合計すれば常に200になるはずである。
しかしボタン1を押すと200以外の数値になりエラーとなる場合がある。これは後者のメソッドによる2つの変数の値の読み取りが、ちょうど@x=@x+1を実行し、かつ、@y=@y-1が実行されていないタイミングで為されるためである。(このプログラムではエラー頻度を上げる為に前者のスレッドにsleepを挿入している)
このようなことが発生しないようにするには、一方のスレッドが変数をアクセスする間、他方のスレッドによる変数アクセスをロックすればよい(はず)具体的には、Mutexクラスのインスタンス@aを作成して、各スレッドが変数をアクセスする部分をsynchronizeメソッドで囲む。
@a = Mutex.new @a.synchronize {……}
こうすることでエラーの発生が無くなる。
@zをローカル変数ではなくインスタンス変数にしたのは、@a.synchronizeの{}でひとつの変数のスコープが形成されて外部から参照できなくなる為。
#!/usr/bin/env ruby require "qte" require "qpe" require "thread" include Qte include Qpe class SampleWindow < QMainWindow def initialize() super() setCaption(tr("サンプル")) @msg = QLabel.new(tr("これはサンプルプログラム"),self) @msg.setGeometry(10,10,300,30) @ebox1 = QMultiLineEdit.new(self) @ebox1.setGeometry(0,100,635,300) @ebox1.setReadOnly(true) @pb1 = QPushButton.new(tr("ボタン1"),self) @pb1.setGeometry(300,5,100,30) connect(@pb1,QSIGNAL("clicked()"), self, 'cnt') # @a = Mutex.new @x = 100 @y = 100 @z = nil end def cnt @ebox1.clear @x = 100 @y = 100 Thread.new do #スレッド1 Thread.pass 100.times do |i| # @a.synchronize { @x = @x + 1 sleep 0.1 #エラー発生頻度を上げる為 @y = @y - 1 # } end end 100.times do #スレッド2 # @a.synchronize { @z = @x + @y # } @ebox1.insertLine(@z.to_s) if @z != 200 then @ebox1.insertLine("!!!Error!!!") end end end end $defaultCodec = QTextCodec.codecForName("utf8") app = QPEApplication.new([$0]+ARGV) app.setDefaultCodec($defaultCodec) QApplication.setFont(QFont.new("lcfont",18)) app.showMainWidget(SampleWindow.new) app.exec