HIDORI on The Web

Just another WordPress site

ThreadStatic のコワイ話

without comments

これは C# Advent Calendar 2015 25日目の記事です。

生ランダム禁止

オンラインゲームでは、頻繁に乱数を使用します。

ですが

のように、Random クラスをそのまま使うのはご法度です。

なぜでしょう?

オンラインゲームのプログラムは、サーバー上で実行されます。

サーバーは、ごく短い時間に多数のリクエストを処理します。

一方、Random クラスの引数なしコンストラクタを使用した場合、既定のシード値はシステム時計から取得する仕様です。

ということは、「複数のリクエスト処理に対して同一の乱数系列が使用される可能性がある」ということです。

具体的には、どういう事象が起きるでしょうか?

例えば、乱数が「くじ引き」的なもので使用されたとして、その乱数系列が「大当たり」を含むものだったとしたら?

同時アクセスしているユーザ全員が「大当たり」となってしまいます。

ユーザは大喜びかもしれませんが、運営的には大打撃です。

品質とコスト

「C# 厳密な乱数」などの検索結果を参考に

のようなコードを書いたとします。

しかし、これも最善ではありません。

確かに、RNGCryptoServiceProvider クラスを使用することで乱数の品質は向上します。

ですが、RNGCryptoServiceProvider.GetBytes() は比較的重たい処理であるため、ごく短い時間に多数のリクエストを処理しなければならないサーバアプリケーションにおいて、1つの乱数を生成する毎に実行するのは不適切です。

ではどうするべきか?

避けるべきは「複数のリクエスト処理に対して同一の乱数系列が使用される」ことで、これは Random クラスが「既定のシード値はシステム時計から取得」することに起因しています。

よって

のように「(システム時計に依存しない)良質のシード値を与えた Random クラスのインスタンスをキャッシュする」ことで、乱数の「品質」と「低生成コスト」の両立を図ることが出来ます。

はずなのですが。。。

static のコワイ話

前述のような手法(インスタンスをキャッシュする)は乱数生成に限らず、しばしば見受けられるます。

でもコレ、ホントにホントに大丈夫???

だって、リファレンスには

スレッドセーフ

この型のすべてのパブリック static (Visual Basic では Shared) メンバーは、スレッド セーフです。 インスタンス メンバーの場合は、スレッド セーフであるとは限りません。

なんて書いてあるじゃないですか。

ということで、検証してみました。

手元の環境では

の実行結果は

910279

でした。

ダメダメです。想像以上にダメダメです。

たかだか 1000000 件の乱数の中に 0 が 910279 件も混じっているなんて、あってはならない事態です。

プログラムの末尾にブレークポイントを仕掛けて values をウオッチしてみると

CSharp-Advent-Calendar-2015-12-25-1

となっており、「ある時を境に Random.Next() が突然 0 のみを返す」ようになったのが分かります。

Random クラスのインスタンスを複数のスレッドから同時並行に使用した結果

Random クラスのインスタンスが(内部に保持する状態が)ぶっこわれた

わけです。

例外すら発生せず「ただ淡々と 0 を返すだけ」という動作は予想しがたいものですが、リファレンスの「スレッドセーフではない」という注記に反した使用をしている以上、期待に反した動作をしても文句は言えません。

さらにもうひと工夫必要、ということです。

ThreadStatic で万全!?

要するに、「複数スレッド間で Random クラスのインスタンスが共有されること」が問題なわけで

のように、ThreadStatic 属性を使用して問題を回避するのが一般的と言えるでしょう。

これでようやく、サーバーアプリケーションでの使用に耐える「品質」と「低生成コスト」を両立した乱数を手に入れることができました。

メデタシ、メデタシです。

そう、async/await が登場するまでは。

ThreadStatic のコワイ話

async/await は、非同期処理の呼び出しと完了待ち処理を簡潔に記述することができるスグレモノです。

サーバサイドプログラムで使用した場合、非同期呼び出しの完了待ちという非生産的な処理にスレッドを解放できるため、積極的に使っていきたいところです。

ただし、非同期プログラム絡みの難しさや複雑さを完全に隠蔽しているものではないため、async/await の動作や効果に対する理解が不足してると、思わぬところでハマる可能性があります。

個人的に特に留意しておきたいと考えているのは「await 以降の行は別スレッドによって処理が継続される」点です。

手元の環境では

の実行結果は

10
12

でした。

await 前後で実行スレッドの id が異なっているのが分かります。

スレッドが異なる!?

前節で ThreadStatic 属性を使用しました。

ThreadStaticAttribute は、「静的フィールドの値がスレッドごとに一意であることを示します。」とされています。

この、「スレッドごと」という点に注目です。

では

* await 前後で実行スレッドが異なる
* ThreadStatic は静的フィールドをスレッドごとに一意にする

という2点を踏まえて、以下のコードを見てみましょう。

async/awit の使用による、新たな問題の可能性に気付けたでしょうか?

つづく(ぉぃ

Written by hidori

12月 24th, 2015 at 11:59 pm

Posted in 未分類

プラグインが使用する API を制限する

with one comment

元ネタは

プラグイン(拡張機能)での権限管理
http://bbs.wankuma.com/index.cgi?mode=al2&namber=72341

で、↑のサンプルを書いてみました。

GitHub: hidori/Samples/PlugIn-20140605
https://github.com/hidori/Samples/tree/master/PlugIn-20140605

サンプルでやってることは以下のとおり。

  • アプリケーションは、プラグインに API を公開する
  • アプリケーションは、プラグイン読み込み時に、プラグインに対して使用する API を問い合わせる
  • アプリケーションは、ユーザに対してプラグインが使用する API を提示して、API 利用の可否を問い合わせる
  • アプリケーションは、ユーザの回答に応じて、プラグインが使用する API に制限を課す

ソリューションの構成

ソリューションに含まれているプロジェクトは以下のとおり。

  • Application1
  • Application1.PlugIn
  • PlugIn1
  • PlugIn2

各プロジェクトの役割は以下のとおり。

Application1

プラグインを使用するアプリケーションのサンプル。

Application1.PlugIn

アプリケーションとプラグインで共有するべき、以下の定義を含むクラスライブラリ。

  • アプリケーションが使用するプラグインが実装するべきインターフェース定義 (IPlugIn)
  • アプリケーションがプラグイン公開する API のインターフェース定義 (IApi1, IApi2, IApi3)
PlugIn1, PlugIn2

プラグインのサンプル。PlugIn1 と PlugIn3 は、それぞれ使用する API が異なる。

Written by hidori

6月 6th, 2014 at 9:54 am

Posted in 未分類

回文数

without comments

回文数の判定
http://bbs.wankuma.com/index.cgi?mode=al2&namber=72241

に参戦してみた。

せっかくなので LINQ 解。

Written by hidori

5月 27th, 2014 at 9:51 pm

Posted in 未分類

PowerShell で空の mdb を作る

without comments

SSMS で SQL Server から複数のテーブルをまとめてエクスポートしたい場合、エクスポート先として mdb ファイルを選択すると何かと便利です。

んが、しかし! ACCESS 2012 では mdb ファイルの新規作成って出来ないんですよ。これは困った。

まさか、空の mdb を作るためだけにわざわざ旧版 ACCESS のインストールが必要なの???

…なんて問題は、以下のような PowerShell スクリプトで解決。

PowerShell って便利。

 

Written by hidori

4月 3rd, 2013 at 12:44 am

Posted in 未分類

Windows Server 2012 ストレージプールの注意点

with one comment

ストレージプール便利です。素晴らしいです。

「ストレージプールってなんなのさ?」という人は、まず

ディスクを仮想化するストレージ・プール機能

とか読んでみてください。

ストレージプールは、簡単に言うと一種のソフトウェア RAID なわけですが、従来の RAID よりも柔軟性が高いのが特徴です。

従来の RAID では基本的に同容量の物理ディスクを組み合わせてボリュームを構成しなければなりませんでしたが、ストレージプールではそういった制限がありません。

ストレージプールに登録された容量の異なる物理ディスクから

  • Simple
  • Mirror
  • Parity

という、3タイプの「レイアウト」の仮想ディスクを作成することができます。

それぞれの「レイアウト」の特徴は↑記事など参照のこと。

以下、実際にストレージプールを使用してみるまで気づかなかった注意点などをつらつら書きます。

ストレージプールを構成する物理ディスクの削除は容易ではない

ストレージプールのコンテキストメニューに「削除」って項目はあるんですよ。

でもこれ、事実上使えないのと同じ。

物理ディスクにデータがあると「代替となる物理ディスクを追加してから削除しろ」と言われます。

ストレージプール全体の残りディスクに容量に余裕があってもダメなんです。

物理ディスクの容量に余裕があれば、ストレージプールから物理ディスクを削除する際に分散配置されたデータの再配置をやってくれるものと勝手に思っていたんですが。。。どうやらそういうことはしてくれないみたい。

なので、例えば「ストレージプールを4台の物理ディスクで組んでみだけど、やっぱり3台でよかったな」なんて場合、物理ディスク1台をストレージプールから削減して他に使いまわす、なんてことができなわけです。

物理ディスクの容量が不足すると仮想ディスクがオフラインになる

これは結構びっくりしました。

例えば

  • 1TB x2 の物理ディスクでストレージプールを作成
  • 16TB の仮想ディスク(レイアウト=Simple)を作成
  • 16TB の MBR ボリュームを作成

したとします。

物理容量的には 1TBx2 = 2TB しかないので、2TB 近く書き込むと何かが起きるわけです。

書き込みエラーかディスクフル的なエラーになるのかなーと思ってたらなんと!何の警告もなく 16TB の仮想ディスクがオフラインになりました。

robocopy で大量のファイルコピーをしている時にこの現象を体験しましたが、ディスクがオフラインになっていることに気づくまでは少し時間がかかりました。

確かに、仮想ディスク的には容量にゆとりがあるように見えるわけで、他にいい方法もない気はしますが、こーなることを事前に知らないと結構焦ります。

当初のもくろみ

我が家のメインサーバー機に

  • 32GB SSD x1
  • 1TB HDD x3
  • 2TB HDD x2
  • 1.5TB HDD x2

という構成の物理ディスクを接続しました。

この時点でオンボードの SATA ポートは全部埋まりました。

32GB SSD x1 はシステムドライブとし、HDD 7台を全部1つのストレージプールに追加して

  • Simpe
  • Mirror

の2つの仮想ディスクを作成。

Simple は仮想マシンの vhd/vhdx 配置用、Mirror はファイル共有用です。

機材の整理をしていたら、1.5TB HDD x2 が出てきたので、1TB HDD と交換しようと思いました。

管理ツールで 1TB HDD を選択し、「削除」を実行します。

んが、ストレージプールに追加されているべての物理ディスクに Simple, Mirror 両仮想ディスクのデータが分散配置されるらしく、「代替となる物理ディスクを追加しろ」と言われます。

そんなん言われても、オンボードの SATA ポートは全部埋まっているし、どーせーっちゅんじゃ!てなもんです。

ストレージプールを構成する物理ディスクの削除は容易ではないことに気づいた瞬間でした。

結局、どうした?

この規模の構成で1つのストレージプールから複数の仮想ディスクを作成するのはよくないという結論に至りました。

ディスクアレイとか豪華な装置が使えるなら別ですが、限りある資源を有効に活用する場合には最適な選択とは言えないようです。

結局、我が家のメインサーバー機には

  • 32GB SSD x1
  • 64GB SSD x2
  • 2TB HDD x2
  • 1.5TB HDD x2

という構成の物理ディスクを接続することにしました。

32GB SSD x1 は引き続きシステムドライブとして使用し

  • 64GB SSD x2
  • 2TB HDD x2 + 1.5TB HDD x1

という組み合わせで、2つのストレージプールを作りました。

64GB SSD x2 で構成されるストレージプールには vhd/vhdx を配置するための Simple ボリュームを、HDD x4 で構成されるストレージプールにはファイル共有用の Mirro ボリュームをそれぞれ作成しました。

当面、この状態で運用してみようと思います。

Written by hidori

9月 12th, 2012 at 6:23 pm

Posted in 未分類

Windows Server 2012 を最速でゲット?

without comments

ネタ元: Windows 8 RTM を最速でゲット?

みんな大好き Windows Server 2012 のダウンロード提供開始まであと数時間となりました。

Windows 8 RTM のダウンロード提供開始の時に作った「MSDN サブスクライバーダウンロードサイトのトップページの更新を定期的にチェックする」ツールを、Windows Server 2012 RTM の提供開始に向けて少し調整しました。(サインイン画面にページ表示ごとに変化する項目が含まれていたので、それを誤検出しないように調整しました)

WS2012Biff

使い方はネタ元を参照してください。

ダウンロード: https://skydrive.live.com/redir?resid=F11BB9FD8E1BC5F9!54267

Written by hidori

9月 4th, 2012 at 8:46 pm

Posted in 未分類

Windows 8 Enterprise の新規ユーザー作成をスキップする

without comments

ネタ元: ミニ セットアップで新規ユーザーの作成をスキップする方法

ネタ元では、Windows 7 のミニセットアップ(Sysprep 後の初回起動時に実行される初期化シーケンス)で新規ユーザー作成をスキップするため方法が紹介されています。

Windows 8 でも Sysprep 用の応答ファイルを作成することで、ミニセットアップで新規ユーザー作成をスキップすることができます。

ただし、従来は応答ファイルの作成に Windows AIK (Windows Automated Installation Kit ) に収録されている Windows SIM (Windows System Image Manager) を使用しましたが、Windows 8 世代では、Windows ADK (Windows Assessment and Development Kit) に収録されている Windows SIM を使用ます。

※ Windows SIM 自体に大きな変更は無いようです。

WAIK, WADK を常備している人ならともかく、「新規ユーザ作成のスキップ」だけが目的でわざわざ WADK をインストールするのもアレなので、自分用に作った

Windows 8 Enterprise 日本語版 x86, x64 向けの応答ファイル

を公開します。

zip ファイルの内容は以下のとおりです。

  • Windows 8 Enterprise (ja, x64)
    • Windows
      • Panther
        • Unattend
          • Unattend.xml
      • Setup
        • SetupCompleted.cmd
  • Windows 8 Enterprise (ja, x86)
    • Windows
      • Panther
        • Unattend
          • Unattend.xml
      • Setup
        • SetupCompleted.cmd

sysprep を実施するマシンの Windows ディレクトリに、zip 中の Panther, Setup ディレクトリをコピーしてください。

その状態で sysprep を実行すると、sysprep のコマンドラインオプションで Unattend.xml を指定しなくても自動的に Unattend.xml の内容が反映されます。

SetupComplete.cmd では Administrator アカウントの有効化を行っています。

ミニセットアップの新規ユーザー作成をスキップしただけだと Administrator アカウントが無効化された状態でシステムがセットアップされてしまい、Administrator アカウントでログオンすることが出来ません。

SetupComplete.cmd で Administrator アカウントの有効化を行うことで、これを回避しています。

 

Written by hidori

8月 28th, 2012 at 3:21 pm

Posted in 未分類

どのリージョンにデプロイするべきか?

without comments

Windows Azure で、自分が使いそうなサービスがどのリージョンで利用可能なのか調べてみました。

Web Sites でごにょごにょするなら East US 以外に選択肢が無いことになります。

でも、今のところデータ同期のハブを East US に配置することができないんですよねぇ。。。

悩ましいなぁ。

Written by hidori

8月 20th, 2012 at 4:21 pm

Posted in 未分類

IIS7 以降のログファイルを PowerShell でもっとカッコよく処理する

without comments

IIS7 以降のログファイルを PowerShell でカッコよく処理する の続編。

IIS のログファイル名って、年月日が含まれているのはいいんだけど UTC なんだよね。

「2012/08/10 07:00~2012/08/12 16:00 のログファイルってどれとどれだっけ?」とか、ぱっと答えられないっしょ (ToT)

ということで

なんてのを書いてみました。

指定したフォルダ配下から、指定期間のログを含む(可能性のある)ログファイル名の一覧を出力します。

これを「IIS7 以降のログファイルを PowerShell でカッコよく処理する」で紹介した Get-IisLog.ps1 コマンドを組み合わせると、処理対象のログファイルだけを効率的に処理することができます。

書式

コマンドラインの書式は

Get-IisLogFile.ps1 [-Path] <string[]> [-Recurse] [-Utc] [-After <datetime>] [-Before <datetime>] [<CommonParameters>]

です。

各オプションの詳細は以下のとおりです。

-Path オプション

IIS ログファイルの検索対象となるフォルダ名を指定します。

フォルダ名には

Get-IisLogFile.ps1 –Path C:INetPublogsLogFilesW3SVC*

のようにワイルドカードが使用可能です。

また

Get-IisLogFile.ps1 –Path @(“\WEB1INetPublogsLogFilesW3SVC1”, “\WEB1INetPublogsLogFilesW3SVC1”)

のように、複数のフォルダ名を指定することも可能です。

-Recurse オプション

指定されたフォルダ配下を再帰的に検索するかどうかを制御します。

-Utc オプション

後述の –After オプションおよび –Before オプションで指定した時刻が UTC であるかどうかを制御します。

-After オプション

出力対象となる期間の開始日時を指定します。

-Before オプション

出力対象となる期間の終了日時を指定します。

出力

コマンドの出力形式はテキストです。

C:INetPublogsLogFilesW3SVC1 配下に

のようなファイル群がある場合

Get-IisLogFile.ps1 –Path C:INetPublogsLogFilesW3SVC1 –After “2012/08/10”

のような出力が得られます。

日本標準時 2012/08/10 00:00 = UTC 2012/08/09 15:00 以降のログを含む、ログファイル名の一覧が出力されるわけです。

「2012/08/10 07:00~2012/08/12 16:00 のログを含むログファイル名は

Get-IisLogFile.ps1 –Path C:INetPublogsLogFilesW3SVC1 –After “2012/08/10 7:00” –Before “2012/08/12 16:00”

というコマンドラインで取得することができます。

Written by hidori

8月 20th, 2012 at 2:28 pm

Posted in 未分類

IIS7 以降のログファイルを PowerShell でカッコよく処理する

with 2 comments

LogParser って大分更新されていないし、出力形式の自由度低いしってことで

なんてのを書いてみました。

IIS のログファイルから読み取った情報を PSObject の動的プロパティにセットして出力します。

出力形式がハッシュではなく PS カスタムオブジェクトであるため、PowerShell の機能を利用して出力を再加工することができます。

ちょっと検索すれば出てきそうな題材ですが、比較的頑張ってるので試してみる価値はあると思います。

書式

コマンドラインの書式は

Get-IisLog.ps1 [-Path] <string[]> [-Utc] [<CommonParameters>]

です。

各オプションの詳細は以下のとおりです。

-Path オプション

処理対象のログファイル名を与えます。

-Path オプションに渡すファイル名には

Get-IisLog.ps1 C:INetPublogsLogFilesW3SVC1*.log

のように、ワイルドカードを使用することができます。

また

Get-IisLog.ps1 –Path @(“\WEB1INetPublogsLogFilesu_ex120819.log”, “\WEB2INetPublogsLogFilesu_ex120819.log”)

のように、複数のログファイル名を指定することも可能です。(PowerShell なので、複数のログファイル名は string 型の配列として渡す)

-Utc オプション

後述のコマンド出力の DateTime プロパティの値を制御します。

-Utc オプションを指定すると、DateTime プロパティにログファイル中の Date, Time と同じ値(UTC)が出力されます。

-Utc オプションを省略すると、DateTime プロパティにログファイル中の Date, Time をローカル時刻に変換した値が出力されます。

出力

コマンドの出力形式は PS カスタムオブジェクトです。

のようなログファイルを入力として与えると

のような出力が得られます。

IIS のログは出力する列を選択することが出来るようになっていますが、それにも対応しています。

IIS のログファイルから読み取った情報以外に、付加情報として以下を出力しています。

Path ログファイルのフルパス名
FileName ログファイルのファイル名
LineNumber ログファイルの行番号
Line ログファイルの行
DateTme ログファイルの Date 列, Time 列から求めた日時

Written by hidori

8月 19th, 2012 at 7:10 pm

Posted in 未分類