Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

.NETマルチスレッドプログラミング 1:スレッドの実行と同期

.NET Frameworkにおけるマルチスレッドプログラミングの基本

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/08/10 12:00

本稿は、.NET Frameworkにおけるマルチスレッドプログラミングの解説です。四部作の「パート1」では、コンソールアプリケーションのサンプルを作成しながら、スレッドの実行から同期までの基礎的な内容を学びます。

目次

目次

はじめに

 本稿では、.NET Frameworkにおけるマルチスレッドプログラミングについて、次のような方針で解説します。また、本稿は四部作の「パート1」です。

  • マルチスレッドプログラミングの基本事項を、全般的に紹介します。ただし、説明は簡潔に要点のみを押さえ、深い部分までは掘り下げません。より詳しい内容を知りたい方は、記事内に示すリンク先や、末尾の「参考資料」を参照してください。
  • この記事では、筆者が重要と判断した項目をなるべく前の方で紹介しています。特にパート2までは、筆者が絶対に知っておくべきと判断した話題です。一般的な入門書のように系統立てて構成していないため、多少分かりづらい点があるかもしれません。
  • 直感的に理解できるように、各項目のはじめに簡単なサンプルを示しています。そのため、まずはサンプルをダウンロードし、実際の動作を確認してみることをお勧めします。
  • サンプルでは、できるだけ記述を簡潔にするために例外処理を省略しています。実際には、例外処理を実装する必要があります。
  • 「スレッドとは何か」「どのような時にマルチスレッドとすべきか」という点に関しては、この記事ではほとんど触れません。これらに関しては、下記の補足をご覧ください。

 マルチスレッドプログラミングは非常に複雑で難しく、多くの危険を伴います。そのため、マルチスレッドを実装する必然性がないのであれば、手を出すべきではありません。

 しかし、それでもなおマルチスレッドプログラミングを習得したいという方々にとって、この記事が微力ながらもお役に立てれば幸いです。

補足
 「スレッドとは何か」については、以下のページが参考になります。
 また、「どのようなときにマルチスレッドとすべきか」については、以下のページが参考になります。

対象読者

 .NET Frameworkにおけるマルチスレッドプログラミングについて勉強したいという方を対象としています。ただし、ここではプログラミングの基本的な事柄については説明しませんので、不明な点はMSDNライブラリ等で調べてください。

必要な環境

 サンプルはVisual Studio .NET 2003で作成し、.NET Framework 1.1で動作確認をしています。

新しいスレッドを作成し、実行する

 .NET Frameworkでは複数のスレッドを使用する方法として、主に次の3つが用意されています。

 スレッドタイマを含めて4つとする説明も多いようです。また、非同期デリゲートやスレッドタイマもスレッドプールを使用するので、実際には2つとも言えます。

 これらの使い分けについては、各見出しで説明することとし、まずはThreadオブジェクトを使った方法から説明します。

 サンプル「start_01」は、マルチスレッドを使わない(つまりシングルスレッドの)普通のコンソールアプリケーションです。

「VB.NET/start_01/Class1.vb」 (VB.NET)
Class Class1
    'エントリポイント
    Public Shared Sub Main()
        Console.WriteLine("スタート")

        'DoSomethingメソッドを実行
        DoSomething()

        Console.WriteLine("Enterキーを押してください")

        Console.ReadLine()
    End Sub

    'メソッド
    Private Shared Sub DoSomething()
        '長い時間のかかる処理があるものとする
        Dim i As Long
        For i = 0 To 1000000000
        Next i

        '処理が終わったことを知らせる
        Console.WriteLine("終わりました")
    End Sub
End Class
「C#/start_01/Class1.cs」 (C#)
class Class1
{
    //エントリポイント
    public static void Main()
    {
        Console.WriteLine("スタート");

        //DoSomethingメソッドを実行
        DoSomething();

        Console.WriteLine("Enterキーを押してください");

        Console.ReadLine();
    }

    //メソッド
    private static void DoSomething()
    {
        //長い時間のかかる処理があるものとする
        for (long i = 0; i < 1000000000; i++);

        //処理が終わったことを知らせる
        Console.WriteLine("終わりました");
    }
}

 このアプリケーションでは、当然のことですが、次のような順番で文字列が出力されます。

「start_01」の出力結果
スタート
終わりました
Enterキーを押してください。

 サンプル「start_02」は、マルチスレッドを使用しています。果たして、どう変わったでしょうか?

「VB.NET/start_02/Class1.vb」 (VB.NET)
Class Class1
    'エントリポイント
    Public Shared Sub Main()
        Console.WriteLine("スタート")

        'DoSomethingメソッドを別のスレッドで実行する
        'Threadオブジェクトを作成する
        Dim t As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething))
        'スレッドを開始する
        t.Start()

        Console.WriteLine("Enterキーを押してください")

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド
    Private Shared Sub DoSomething()
        '長い時間のかかる処理があるものとする
        Dim i As Long
        For i = 0 To 1000000000
        Next i

        '処理が終わったことを知らせる
        Console.WriteLine("終わりました")
    End Sub
End Class
「C#/start_02/Class1.cs」 (C#)
class Class1
{
    //エントリポイント
    public static void Main()
    {
        Console.WriteLine("スタート");

        //DoSomethingメソッドを別のスレッドで実行する
        //Threadオブジェクトを作成する
        System.Threading.Thread t = 
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething));
        //スレッドを開始する
        t.Start();

        Console.WriteLine("Enterキーを押してください");

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド
    private static void DoSomething()
    {
        //長い時間のかかる処理があるものとする
        for (long i = 0; i < 1000000000; i++);

        //処理が終わったことを知らせる
        Console.WriteLine("終わりました");
    }
}

 このコードでは、DoSomethingメソッドを新たに作成したスレッドで実行しています。Thread.Startメソッドを呼び出すことにより、スレッドの実行を開始しています。

 このアプリケーションを実行すると、文字列が出力される順番が次のようになります。

スタート
Enterキーを押してください
終わりました

 つまり、DoSomethingメソッドが開始されてから終わるまでの間に、Mainメソッドでは次の処理(Console.WriteLine)が行われていることが分かります。

 先のシングルスレッドの例と違い、DoSomethingメソッドが別スレッドで開始されたため、Mainメソッドを実行しているスレッド(「メインスレッド」、または「プライマリスレッド」)がDoSomethingの処理でブロックされることなく、2つのスレッドが平行して同時に実行されます。

 正確には、シングルプロセッサでは「同時」ではありません。詳しくは、「スレッドおよびスレッド処理」をご覧ください。

 以上のような新しいスレッドを作成してメソッドを実行する手順をまとめると、次のようになります。

  1. 引数も戻り値もないメソッドを作成し、この中に別スレッドで実行させる処理を記述する。
  2. このメソッドを指定して、ThreadStartデリゲートオブジェクトを作成する。
  3. ThreadStartオブジェクトを指定して、Threadオブジェクトを作成する。
  4. ThreadオブジェクトのStartメソッドを呼び出して、スレッドを開始する。

フォアグラウンドスレッドとバックグラウンドスレッド

 上に紹介したマルチスレッドアプリケーション「thread_02」をもう一度実行してみてください。このアプリケーションを実行して、「Enterキーを押してください」と表示された直後に[Enter]キーを押したらどうなるでしょうか? たとえ[Enter]キーを押しても、「終わりました」と表示されるまではアプリケーションが終了しません。つまり、Mainメソッドが終了しても、DoSomethingメソッドが終了するまでアプリケーションは終了しません。

 このコードに一行だけコードを加えて、サンプル「background_01」を作成しました。これでどう変わったでしょうか?

「VB.NET/background_01/Class1.vb」 (VB.NET)
Class Class1
    'エントリポイント
    Public Shared Sub Main()
        Console.WriteLine("スタート")

        'DoSomethingメソッドを別のスレッドで実行する
        'Threadオブジェクトを作成する
        Dim t As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething))
        '↓このコードを加えた↓
        t.IsBackground = True
        'スレッドを開始する
        t.Start()

        Console.WriteLine("Enterキーを押してください")

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド
    Private Shared Sub DoSomething()
        '長い時間のかかる処理があるものとする
        Dim i As Long
        For i = 0 To 1000000000
        Next i

        '処理が終わったことを知らせる
        Console.WriteLine("終わりました")
    End Sub
End Class
「C#/background_01/Class1.cs」 (C#)
class Class1
{
    //エントリポイント
    public static void Main()
    {
        Console.WriteLine("スタート");

        //DoSomethingメソッドを別のスレッドで実行する
        //Threadオブジェクトを作成する
        System.Threading.Thread t = 
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething));
        //↓このコードを加えた↓
        t.IsBackground = true;
        //スレッドを開始する
        t.Start();

        Console.WriteLine("Enterキーを押してください");

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド
    private static void DoSomething()
    {
        //長い時間のかかる処理があるものとする
        for (long i = 0; i < 1000000000; i++);

        //処理が終わったことを知らせる
        Console.WriteLine("終わりました");
    }
}

 このアプリケーションでは、「Enterキーを押してください」と表示された直後に[Enter]キーを押すと、「終わりました」と表示される前であってもすぐにアプリケーションが終了します。つまり、DoSomethingメソッドが終了しなくても、Mainメソッドが終了すればアプリケーションは終了します。

 この違いは、フォアグランドスレッドとバックグラウンドスレッドの違いです。はじめの例(「thread_02」)では、フォアグランドスレッドでDoSomethingメソッドを実行しました。これに対して、ここで示した例(「background_01」)では、Thread.IsBackgroundプロパティをtrueにしてバックグラウンドスレッドでDoSomethingメソッドを実行しました。

 プロセス内のすべてのフォアグラウンドスレッドが終了した時に、そのプロセスは終了し、同時にすべてのバックグランドスレッドは強制的に終了させられます。逆に言えば、すべてのフォアグラウンドスレッドが終了しなければプロセスは終了しませんが、バックグランドスレッドが終了しなくてもプロセスは終了します。

 IsBackgroundプロパティのデフォルト値がfalseのため、何も指定しないとフォアグラウンドスレッドになってしまうことに注意してください。このことを知らないと、「メインスレッドが終了したのになぜかアプリケーションが終了しない」と頭を悩ませることになります。

スレッドが終了するまで待機する

 上のコードにさらに1行加えて、サンプル「join_01」を作成しました。今度はどうなるでしょうか?

「VB.NET/join_01/Class1.vb」 (VB.NET)
Class Class1
    'エントリポイント
    Public Shared Sub Main()
        Console.WriteLine("スタート")

        'DoSomethingメソッドを別のスレッドで実行する
        'Threadオブジェクトを作成する
        Dim t As New System.Threading.Thread( _
            New System.Threading.ThreadStart( _
            AddressOf DoSomething))
        t.IsBackground = True
        'スレッドを開始する
        t.Start()

        '↓この行を追加↓
        t.Join()

        Console.WriteLine("Enterキーを押してください")

        Console.ReadLine()
    End Sub

    '別スレッドで実行するメソッド
    Private Shared Sub DoSomething()
        '長い時間のかかる処理があるものとする
        Dim i As Long
        For i = 0 To 1000000000
        Next i

        '処理が終わったことを知らせる
        Console.WriteLine("終わりました")
    End Sub
End Class
「C#/join_01/Class1.cs」 (C#)
class Class1
{
    //エントリポイント
    public static void Main()
    {
        Console.WriteLine("スタート");

        //DoSomethingメソッドを別のスレッドで実行する
        //Threadオブジェクトを作成する
        System.Threading.Thread t = 
            new System.Threading.Thread(
            new System.Threading.ThreadStart(DoSomething));
        t.IsBackground = true;
        //スレッドを開始する
        t.Start();

        //↓この行を追加↓
        t.Join();

        Console.WriteLine("Enterキーを押してください");

        Console.ReadLine();
    }

    //別スレッドで実行するメソッド
    private static void DoSomething()
    {
        //長い時間のかかる処理があるものとする
        for (long i = 0; i < 1000000000; i++);

        //処理が終わったことを知らせる
        Console.WriteLine("終わりました");
    }
}

 t.Joinを追加する前は、

スタート
Enterキーを押してください
終わりました

 という順番で文字列が出力されていましたが、t.Joinを追加した後は、

スタート
終わりました
Enterキーを押してください

 という順番になります。

 ThreadクラスのJoinメソッドは、そのスレッドが終了するまで現在のスレッドをブロックします。つまり上記の例では、DoSomethingメソッドを実行しているスレッドが終了するまで、メインスレッドはJoinメソッドにより待機させられます。その結果、DoSomethingメソッドが終了した後に、「Console.WriteLine("Enterキーを押してください")」が実行されます。

 Joinメソッドは、別のスレッドで行っている処理が確実に終了しなければ次に進めない場合に使用します。つまり、スレッドの同期に使用します。スレッドの同期処理については、次項の「スレッドの同期」で説明します。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2005/12/22 02:04 ソース内のコメントを修正。

著者プロフィール

  • どぼん!(ドボン!)

    DOBON.NET内で.NET Frameworkの機能を紹介したWebサイト.NET Tipsやメールマガジン「.NETプログラミング研究」の発行人。

バックナンバー

連載:.NET Frameworkにおけるマルチスレッドプログラミングの基本
All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5