スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門中級編第15回(Asynchronous Workflows(1) thread)

今回のお題は「Asynchronous Workflows(1) thread」です。
 
.NETでは、マルチスレッドを実現する方法が複数用意されています。
それらはスレッド、スレッドプール、Func(デリゲイト)、タイマーです。タイマー以外を順番に紹介していきたいと思います。
まずはスレッド(thread)です。スレッド とは「プログラムの別の部分と並行して実行できるプログラムの一部分」のことです。threadは日本語では糸ですが、菌糸(枝分かれしながら広がっている)の方が、イメージとしては近いように思います。プログラムの本線から分線を延ばすという感じです。
ここで、スレッドの処理の終了のタイミングについて説明しておきます。
スレッドには、フォアグラウンドスレッドとバックグランドスレッドの2種類があり、プロセス内のフォアグラウンドスレッドがすべて終了した時にそのプロセスは終了します。(逆に言えば、すべてのフ
ォアグラウンドスレッドが終了しなければプロセスは終了しない。)
一方、プロセス終了時に実行中のすべてのバックグランドスレッドはAbortが呼び出されて終了させられます。
デフォルトではフォアグラウンドスレッドとなってます。
 
まずはもっとも単純なスレッドの例から始めます。
スレッドを作成して走らすにはThreadクラスのインスタンスを作成して,それをstartします。
コンストラクタの引数としてはdelegate型が指定されていますが,F#ではこのような場合、関数を自動的にdelegateに変換してくれるので、例えば次のようにインスタンスを作成できます。(コンストラクタに渡す関数はunit ->unit型である必要があります。)
 

 
open System
open System.Threading
 
let funcInThread1 () =
    for i in 1 .. 100 do
        printfn "%d" i
 
(new Thread(funcInThread1)).Start()
(new Thread(fun ()->for i in 101 .. 200 do printfn "%d" i )).Start()
 
実行例(一部)
163
66
164
67
165
6166 //68の間に166が割り込んでいる
8
167
69
 
スレッドのメリットとしては、「各スレッドに優先順位を設定できる」、「スレッドの一時停止/再開/中断を行うことができる」という点があり、デメリットとしては、「スレッドの作成と破棄を繰り返すとパフォーマンスが落ちる」、「メソッドにパラメータを設定できない」、「メソッドの戻り値を得るのが困難」という点です。
 
次にThreadがらみの例題でよく使われる関数Thread.Sleepを紹介します。
これはStaticなメソッドで、引数ミリセカンド(1/1000秒)だけ、現在実行中のスレッドの実行を遅らせます。
 

 
open System
open System.Threading
 
 
let funcInThread1 () =
    for i in 1 .. 100 do
        Thread.Sleep(10)
        printfn "%d" i
 
let funcInThread2 ()  =
    for i in 101 .. 200 do
        Thread.Sleep(10)
        printfn "%d" i
 
(new Thread(funcInThread1)).Start()
(new Thread(funcInThread2)).Start()
 
実行例(一部)
 
92
192
93
193
194
94
95
 
次にスレッドプール(Thread pool)を紹介します。スレッドプールとは、キューに入れられたリクエスト(処理)をスレッドプールが用意しているスレッドにより次々に実行していく仕組みです。
(スレッドプールのイメージはhttp://www.atmarkit.co.jp/fdotnet/mthread/mthread02/mthread02_01.htmlが参考になるかと思います。)
スレッドプールにスレッドを登録するにはThreadPool.QueueUserWorkItem関数を利用します。
 
一番単純な登録方法は引数としてobj->unit型の関数を渡す方法です。

 
ThreadPool.QueueUserWorkItem (fun _ -> for i in 1 .. 9 do printfn "%d" i)
 
実際にはThreadPool.QueueUserWorkItem関数の引数は、WaitCallback型のデリゲートかこれとobjectのタプルですが、上ではF#が関数を自動的にWaitCallback型のデリゲートに変換してくれています。
なおThreadPool.QueueUserWorkItem関数の返り値は、キューできたか(ちゃんとThreadPoolに投げ込めたかどうか)を表すBool値です。
 
上では引数のobjは未使用でしたが、これを使用する例を一つ挙げておきます。
 
let funForWaitCallback (pObj:obj) = //型注釈が必要
    let max,str = pObj :?> (int*string)
    for i in 1 ..max do
        printfn "%s" str
 
あとはWaitCallbackのインスタンスを作成して、ThreadPool.QueueUserWorkItemに加えます。タプルの第一成分の関数のインスタンスの引数として第二成分が使用されます。
 
type Program =
  static member main() =
 
    let funForWaitCallback (pObj:obj) = //型注釈が必要
        let max,str = pObj :?> (int*string)
        for i in 1 ..max do
            printfn "%s" str
 
    printfn "Main Start"
    printfn "%A" (ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),(2,"A")) )
    printfn "%A" (ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),(5,"B")) )
    printfn "exit Main"
    
do Program.main()
 
(実行例)
Main Start
A
A
true
true
exit MaB
B
B
B
B
in
続行するには何かキーを押してください . . .
 
 
WaitCallbcak関数の登録部分は次のようにも書けます。
 
let funForWaitCallback max str= 
    for i in 1 ..max do
        printfn "%s" str
 
ThreadPool.QueueUserWorkItem(fun _ -> funForWaitCallback 2 "A")
ThreadPool.QueueUserWorkItem(fun _ -> funForWaitCallback 5 "B")
 
さて次は、実行されるメソッドに渡される状態オブジェクトを利用して、何らかの結果を取得してみます。
(次はうまくいかない例です。)
 
type Program =
  static member main() =
   let funForWaitCallback (pObj:obj) = //型注釈が必要
        let max,str,res = pObj :?> (int*string*(int ref)) //resが結果格納用
        for i in 1 ..max do
            printfn "%s" str
        res := max*100 
 
   printfn "Main Start"
   let p1 =  (200,"A",(ref 0)) 
   let p2 =  (50,"B",(ref 0))
   ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),p1) |> ignore //#1
   ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),p2) |> ignore //#2
   let (_,_,ans1) = p1 //#3
   let (_,_,ans2) = p2 //#4
   printfn "exit Main %d %d " !ans1 !ans2 //#5
    
do Program.main()
 
(実行例)
Main Start
exit Main 0 0 
A
A
後略
 
#1,#2とメインスレッドは並行に実行されるので、#3,#4の部分ではまだans1,2が計算され、書き込まれていない状態となるので、こういうことが起こります。これを回避するには、例えば次のようにします。(もっと良い方法を後日紹介します。)
 
type Program =
  static member main() =
   let funForWaitCallback (pObj:obj) = //型注釈が必要
        let max,str,res,isFinished = pObj :?> (int*string*(int ref)*(bool ref)) 
        for i in 1 ..max do
            printfn "%s" str
        res := max*100 
        isFinished := true
 
   printfn "Main Start"
   let p1 =  (200,"A",(ref 0),(ref false)) 
   let p2 =  (50 ,"B",(ref 0),(ref false))
   ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),p1) |> ignore
   ThreadPool.QueueUserWorkItem(new WaitCallback(funForWaitCallback),p2) |> ignore
   let (_,_,_,isFin1) = p1
   let (_,_,_,isFin2) = p2
   while !isFin1 = false || !isFin2 = false do //!は否定ではありません
     Thread.Sleep(0)
   let (_,_,ans1,_) = p1
   let (_,_,ans2,_) = p2
   printfn "exit Main %d %d " !ans1 !ans2
    
do Program.main()
 
実行例
Main Start
A
中略
A
A
A
exit Main 20000 5000 
 
スレッドプールのメリットとしては、「 効率よく複数のスレッドを実行できる」、「object型のパラメータを1つだけ設定できる 」という点があり、デメリットとしては、「パラメータがobject型1つのみ」、「メソッドの戻り値を得るのが困難」、「優先順位付けや待機、停止など、スレッドの細かな制御が難しい」、「同時に実行できるスレッドの数が制限されている」という点です。
 
参考URL
http://www.atmarkit.co.jp/fdotnet/mthread/mthread02/mthread02_01.html
 
http://dobon.net/vb/melma/dotnet20.txt
http://dobon.net/vb/melma/dotnet21.txt
http://dobon.net/vb/melma/dotnet22.txt
http://dobon.net/vb/melma/dotnet23.txt
 
http://www.ailight.jp/blog/kazuk/articles/6282.aspx

スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。