DLL 関数スレッド内ファイル書き込みの排他制御について

[戻る]
なおぞう 2016/02/26(金) 15:43:20
お世話になります。
VS2013 標準Windowsライブラリを使用、Unicode文字セットを使用、共通言語ランタイム
サポートをしない の環境でDLLを作成しております。

排他制御を考えないで、DLL内の関数と、そこで生成したスレッド1(スレッド1の中で
さらにスレッド2を生成)3つの関数の中でそれぞれログ出力させています。
スレッド1の最初のログ出力処理でファイルオープンエラーが発生し、それ以降スレッド
関数の処理が停止していることに気がつき、排他処理を入れればよいと思い、調べたので
すが、

APIの同期オブジェクトは
・ミューテックス
・セマフォ
・タイマー
・イベント
・クリティカルセクション

いろいろあるようで何を使うのが良いのか分からず、サンプルを探しても、
@ファイルに書き込もうとして、他のが書きこんでいたら待たせておく
A終わったら書きこませる
というものが見つからなくてアドバイスをいただきたく書きこませていただいておりま
す。よろしくお願いします。


----排他制御が組み込まれていないテストプログラム-----
unsigned __stdcall ThreadTest(PVOID p)
{
    FILE* fp;
    TCHAR acMsg[512];
    memset(acMsg, '\0', sizeof(acMsg));
    swprintf_s(acMsg, 512, TEXT("C:\\TestLog\\DllThread.txt"));

    for (int i = 0; i < 10; i++){
        _wfopen_s(&fp, acMsg, TEXT("a+"));    
        if (fp == NULL){ printf("THREAD  FILE POINTER IS NULL\n"); }
        fwprintf_s(fp, L"THREAD %s\n", acMsg);
        fclose(fp);
    }
    return 1;
}


EXPORT DWORD WINAPI LOGPRINT()
{
    FILE* fp;
    TCHAR acMsg[512];
    memset(acMsg, '\0', sizeof(acMsg));
    swprintf_s(acMsg, 512, TEXT("C:\\TestLog\\DllThread.txt"));

    //処理スレッドを起動する(スレッドの生成)
    unsigned int threadID;
    DWORD exitcode;
    HANDLE hThread = (HANDLE)_beginthreadex(0, 0U, ThreadTest, (void*)0, 0, &threadID);

    for (int i = 0; i < 10; i++){
        _wfopen_s(&fp, acMsg, TEXT("a+"));    
        if (fp == NULL){ printf("MAIN FILE POINTER IS NULL\n"); }
        fwprintf_s(fp, L"MAIN %s\n", acMsg);
        fclose(fp);
    }
    GetExitCodeThread(hThread, &exitcode);
    CloseHandle(hThread);
    return 1;
}

-------------------
上記プログラムでは、LOGPRINT()関数のファイル出力処理と、ThreadTest()でのファイ
ル出力処理が競合してしまい、ThreadTest()の方がファイルオープンエラーで終わってし
まうようです。

このコードをどのように改造すれば排他制御ができ、ThreadTestでのログ出力ができるよ
うになるのでしょうか。
ファイルオープンの仕方からしてまるっきり変えることになるのでしょうか?

教えていただけますようお願いします。

tetrapod 2016/02/26(金) 16:14:38
そういう「ログ」機能を実装する際には、

・出力したいログを受け付けるルーチンは、メッセージを FIFO に登録するだけ
(ここで排他する)

・ FIFO からファイルへの出力は1箇所で行う(排他不要)

という感じで実装するものだと思う。

# 何度も指摘してるけどスレッドが終わる前に GetExitCodeThread しても意味がない。

瀬戸っぷ [E-Mail] 2016/02/26(金) 16:14:58
>@ファイルに書き込もうとして、他のが書きこんでいたら待たせておく
>A終わったら書きこませる
>というものが見つからなくてアドバイスをいただきたく書きこませていただいておりま
>す。よろしくお願いします。

クリティカルセクションでいいんじゃないですか?
「待たせておく」が簡単にできるようですし。

https://msdn.microsoft.com/ja-jp/library/cc429095.aspx
>指定されたクリティカルセクションオブジェクトの所有権を取得するまで待機します。

AR 2016/02/26(金) 17:02:30
 設計の考え方としては、tetrapodさんの意見を私も推しますね。

 タイマーとイベントに関しては論外なので省略します。
 クリティカルセクションと、ミューテックスと、セマフォの3択になりますが、ログを
吐く程度の用途ならセマフォは候補から外れます。
 クリティカルセクションとミューテックスの使い分けは、同期オブジェクトのインスタ
ンス数で考えるので、どちらでも実現可能です。
 クリティカルセクションの方が実装は簡単ですが、プロセスをまたいだりということは
不可能になります。

 ログ出力関数がDLLの中にあって、別exeから呼ばれるならミューテックスということに
なります。
 さっくりまとめられてるサイトがありましたのでどうぞ。
http://tecopark.blogspot.jp/2012/04/winapi.html

なおぞう 2016/02/26(金) 19:55:54
>みなさま

いろいろとアドバイスありがとうございました。
あまり美しくない方法で解決させてしまいました。
_wfopen_s(&fp, acMsg, TEXT("a+"));     が、共有不可でOPENしているので、競合する
とfpがNULLになります。
fp==NULLの間だけwhile(1)でまわして!=NULLになるまでopenをする。
という方法です。(タイムアウト設定あり)
原始的というべきでしょうか。

FIFOを使ってみたいと思ったのですが、どうやったら良いのか分からず時間もなくてこの
ような対応になってしまいました。

>tetrapod 様
># 何度も指摘してるけどスレッドが終わる前に GetExitCodeThread しても意味がない。

スレッドの終了を待たない仕様なので、GetExitCodeThreadは意味が無いのですがなんと
なく書いてしまうのです。

なおぞう 2016/02/26(金) 19:56:14
[[解決]]
クローズ忘れました。

瀬戸っぷ [E-Mail] 2016/02/27(土) 10:45:45
ちなみにスレッド生成の手順は?
以前にどなたかが指摘していたと思いますので大丈夫だとは思いますが。

なおぞう 2016/02/29(月) 11:06:43
>瀬戸っぷさま

以下のように書いてますが…間違ってますか。

DWROD Function1(){

unsigned int threadID;
DWORD exitcode;
HANDLE hThread = (HANDLE)_beginthreadex(0, 0U, TreadTest, (PVOID)FUNC, 0,&threadID);
if (hThread == 0)
{
  return 0;            
}

GetExitCodeThread(hThread, &exitcode); //←これが意味無しですが。
CloseHandle(hThread);


return 1;//スレッドの終了を待たないで処理を返す
}


unsigned __stdcall TreadTest(PVOID p)
{

PFUNC pfunc = (PFUNC)p;
  :
 処理(さらにスレッド立ち上げたり)
  :
pfunc(error);
return error;
}

瀬戸っぷ [E-Mail] 2016/02/29(月) 11:48:04
_beginthreadex()なら問題ないです。

CreateThread()だと駄目でしたけど。

なおぞう 2016/02/29(月) 12:06:50
>瀬戸っぷ様

最初はCreateThreadを使っていたのですが、余りよろしくないという書き込みを見て
_beginthreadex()に変更しました。

わざわざ確認していただきありがとうございます。

古いスレッドには追加発言できません。