ブログトップ 記事一覧 ログイン 無料ブログ開設

tekkの日記 C#,VB.NET このページをアンテナに追加 RSSフィード Twitter

人気記事 No.1 どんなオブジェクトでもコピーできる汎用のディープコピー処理   codeplexにて公開しているオープンソースプロジェクトcreate hyper-v Server USB Memory

おすすめ記事!Windows 8 を体験しよう。気軽に試せるVHDインストール。みんなで試そう。Go Metro!

2009-11-22

プロセスレベルのグローバル変数(Singleton, Shared, Module)と、スレッドレベルのグローバル変数(ThreadStatic=スレッドローカルストレージ)

02:06 | プロセスレベルのグローバル変数(Singleton, Shared, Module)と、スレッドレベルのグローバル変数(ThreadStatic=スレッドローカルストレージ)を含むブックマーク

サンプルコードGlobalInformation.zip 直

.Netでのグローバル変数の持ち方について考えてみます。

グローバル変数は、変数の生存期間(ライフタイム)と適用範囲(スコープ)を最小にするという原則に従って極力使わないようにするべきものですが、逆に言えば生存期間がアプリケーション開始直後から終了まで、適用範囲がアプリケーション全体に渡る変数グローバル変数で保持するべきと言えます。設定ファイルの内容だとか、アプリ全体の状態などが該当すると思います。

グローバル変数というと一般的にはプロセスレベルですが、スレッドレベルでもグローバル変数を保持したい場合があります。よくあるのは、多数のクライアントからリクエストを受け付けるサーバアプリケーションクライアントのリクエストに含まれるユーザIDやIPアドレスなどがあります。.Net RemotingやWCFで有効なプログラミングテクニックです。引数として各処理に渡しても問題ありませんが、リクエストを処理する全関数引数として渡すのは煩雑なのでグローバル変数を使います。

スレッドレベルのグローバル変数を使うにはスレッドローカルストレージ(TLS)を使います。TLSは.NETだけでなくマルチスレッドを扱うことの出来る言語では用意されている一般的なものです。.NETでは変数定義にThreadStatic属性を付けるだけで簡単に変数TLSに設定することができます。TLSに設定されたデータの読み書きは、そのスレッドだけに限定されるのでマルチスレッド特有の排他制御の問題は発生しません。プロセスレベルのグローバル変数をThreadIDで管理するよりも安全で効率的なのでスレッドレベルのグローバル変数を持つ場合は必ずTLSを使用します。

Public Class ThreadInformation

    <ThreadStatic()> _
    Public Shared Name As String

End Class

ThreadAttribute属性を付与するプロパティは、初期値を設定してはいけません。(初期値を設定して初期化しても問題ありませんが、意図した通りに初期化してくれるのはメインスレッドのみです。以降のスレッドでは0や空文字、Nothingに初期化されます。)以下は、MSDNの引用です。

ThreadStaticAttribute でマークしたフィールドの初期値を指定しないでください。このような初期化は、クラスのコンストラクタの実行時に一度だけ行われるもので、関係するスレッドは 1 つだけです。初期値を指定しなければ、フィールドが、値型の場合はその既定値に、参照型の場合は nullNothingnullptrnull 参照 (Visual Basic では Nothing) に初期化されることを前提にできます。

検証コード

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Debug.Print(ThreadInformation.Name)
        ThreadInformation.Name = "Hello2"
        Debug.Print(ThreadInformation.Name)

        Dim t As New Threading.Thread(AddressOf ThreadStart)
        t.Start()
        t.Join()

        Debug.Print(ThreadInformation.Name)

    End Sub

    Public Sub ThreadStart(ByVal param As Object)

        Debug.Print(ThreadInformation.Name)

        ThreadInformation.Name = "Hello3"
        Debug.Print(ThreadInformation.Name)

        GlobalInformation .Name = "tekk"

    End Sub

End Class

Public Module GlobalInformation

    Public Name As String = String.Empty

End Module

Public Class ThreadInformation

    <ThreadStatic()> _
    Public Shared Name As String = "Initialize Main Thread Only"

End Class

イミディエイトウインドウに以下のように出力されます。

Initialize Main Thread Only

Hello2

Hello3

Hello2

次にグローバル変数の定義の仕方を見ていきます。シングルトンデザインパターン、Sharedキーワード、Moduleの3種類で見ていきます。

Public Class GlobalInformation

    Private Sub New()
        ' nop
    End Sub

    Private Shared _instance As New GlobalInformation

    Public Shared Function Instance() As GlobalInformation
        Return _instance
    End Function

    Public Name As String = String.Empty

End Class

コンストラクタをPrivateとしているので、このクラスはNewできません。

シングルトンデザインパターンだと使用時は以下のようになります。Instanceと書かなければいけない所が少し面倒です。

GlobalInformation.Instance.Name = "tekk"

Public Class GlobalInformation

    Public Shared Name As String = String.Empty

End Class

使用時は以下のようになります。かなりスッキリします。但し、使うメンバのすべてにSharedキーワードを付ける必要があります。グローバル情報を定義する際が少し面倒ですが、使用時の記載が簡略化できます。

GlobalInformation.Name = "tekk"

  • Moduleによるグローバル情報

VB.NETでしか使えませんが、Moduleを使用すると自動的にSingletonなクラスとなります。インスタンスが存在するSingletonクラスなのでSharedを付ける必要はありません。

Public Module GlobalInformation

    Public Name As String = String.Empty

End Module

使用時は以下となります。

GlobalInformation.Name = "tekk"