マルチスレッドのファイル書き込み

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
C言語です

マルチスレッドのファイル書き込み

#1

投稿記事 by C言語です » 15年前

WinXP(SP3)でVC2008Expressを使っています。
マルチスレッドのアプリケーションで、動作ログをファイルに記録するために
下記のテストプログラムを作ってみたのですが、うまく動作せず困っています。

スレッド関数の中から、ファイルを排他制御して書き込みして、終了するだけ
のプログラムのつもりですが、実行しても終了せず止まってしまいます。
たまにちゃんと終了してくれる時もありますが・・。

デバッグ実行して、停止したプログラムを中断すると、

「プロセスはデッドロックされているか、ユーザーモードコードがどれも実行
 されていません。すべてのスレッドが中止されました。」

というメッセージが出ます。ソースコードのカーソルを見ると、LockFileExの
ところで止まっているように見えるのですが、なぜここで止まるのか、原因が
まったくわかりません。

なにか間違えているのでしょうか・・。お助けください。m(_ _)m

─────────────< main.c >────────────────
#include <windows.h>
#include <process.h>
#include <stdio.h>

#define THREAD_NUM 16

HANDLE thread[THREAD_NUM];
HANDLE hFile;

unsigned __stdcall ThreadProc( void *p )
{
UINT id = (UINT)p;
char buf[128];
DWORD dwWrite;
OVERLAPPED ov;

_snprintf( buf,128,"スレッド%dです。\r\n",id );

ZeroMemory( &ov, sizeof(OVERLAPPED) );

// ロックして追記書き込み
if( LockFileEx( hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ov ) )
{
SetFilePointer( hFile, 0, NULL, FILE_END );
WriteFile( hFile, buf, strlen(buf), &dwWrite, NULL );
UnlockFileEx( hFile, 0, 1, 0, &ov );
}

_endthreadex(0);
return 0;
}

void main( void )
{
int i;

hFile = CreateFile( "out.txt", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( hFile==INVALID_HANDLE_VALUE )
{
puts("CreateFileエラー");
return;
}

for( i=0; i<THREAD_NUM; i++ )
{
thread = (HANDLE)_beginthreadex( NULL, 0, ThreadProc, (void*)i, 0, NULL );
if( !thread )
puts("_beginthreadexエラー");
}

WaitForMultipleObjects( THREAD_NUM, thread, TRUE, INFINITE );

CloseHandle( hFile );
}

シエル

Re:マルチスレッドのファイル書き込み

#2

投稿記事 by シエル » 15年前

beginthreadexのことを知らないので何ともいえませんが、
↓って、16個も同じスレッドを作成しているってことでしょうか?
もしそうであれば、作成したスレッドが終了していることを確認してから、
作成した方がいいのではないでしょうか?
まったく検討違いのことを言っていたらすみません。

for( i=0; i<THREAD_NUM; i++ )
{
thread = (HANDLE)_beginthreadex( NULL, 0, ThreadProc, (void*)i, 0, NULL );
if( !thread )
puts("_beginthreadexエラー");
}

Mist

Re:マルチスレッドのファイル書き込み

#3

投稿記事 by Mist » 15年前

> シエルさん

排他制御のテストをしたいのですから、終わるの待ってから起動していたのでは意味が無いですよ。

toyo

Re:マルチスレッドのファイル書き込み

#4

投稿記事 by toyo » 15年前

LockFileExをブロッキングしないようにしたらどうでしょう
while ( LockFileEx( hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov ) == 0) ;
これでも無限ループの可能性はありますがループ回数を制限するとか拡張エラー情報を取得して処理するとかでできないでしょうか

御津凪

Re:マルチスレッドのファイル書き込み

#5

投稿記事 by 御津凪 » 15年前

一見正しく動作しているようですが、LockFileExで止まっているということは
そこでマルチスレッド特有のバグが発生しています。

マルチスレッドでは、ある変数に同時に書き込む処理があるのはタブーです。
つまり、 LockFileEx はスレッドセーフではない(複数のスレッドで同時に呼び出してはいけない)ということです。

なので、このような排他制御を行うのであれば、クリティカルセクションを使って制御した方がいいでしょう。

ファイルの指定領域をロックするLockFileExは、あくまでその領域をロックする機能で、スレッドの実行タイミングを制御するものではありません。

シエル

Re:マルチスレッドのファイル書き込み

#6

投稿記事 by シエル » 15年前

Mistさん。すみません。その通りですね。

ファイルの排他制御のテストをしたいのは分かりますが、ThreadProcのスレッドを連続で呼び出して良いんでしょうか?
違う名前の関数を呼び出して、一つのファイルにアクセスして排他制御のテストをするのは分かりますが、
ThreadProcという一つの関数を終了するのを確認せずに、連続で呼び出すのはどうかと思いまして。
勉強不足なんであまり分かってないんですけどね;
変なこと言ってたら完全スルーして下さい。

御津凪

Re:マルチスレッドのファイル書き込み

#7

投稿記事 by 御津凪 » 15年前

> ThreadProcという一つの関数を終了するのを確認せずに、連続で呼び出すのはどうかと思いまして。

ThreadProc は一つの関数ですが、複数のスレッドから呼び出されること自体は問題ありませんよ。
各スレッドはそれぞれがスタック領域をが持っている(グローバル領域は共有)ので、
同じ関数を複数のスレッドに起動時に実行する関数として指定しても全く問題ありません。

マルチスレッドで一番問題となるのは、一つの変数(データ)領域に同時に書き込む状況がある場合がほとんどです。
それを防ぐために排他制御が必要になります。

シエル

Re:マルチスレッドのファイル書き込み

#8

投稿記事 by シエル » 15年前

そうなんですね!非常に勉強になりました!
スレ汚し失礼致しました!

お豆

Re:マルチスレッドのファイル書き込み

#9

投稿記事 by お豆 » 15年前

OVERLAPPED ov;
//初期化
ZeroMemory( &ov, sizeof(ov) );
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = NULL;

//オーバラップ構造体を使う指定 6番目の引数にFILE_FLAG_OVERLAPPED
hFile = CreateFile( "out.txt", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL );
(追記)よくよく考えると、WriteFileを非同期にするわけでもないので、初期化だけでいいですね
あと本件には関係ないですが、_beginthreadexで作ったスレッドのハンドルは自動でとじられないので自分でやってあげてください
繰り返し~ CloseHandle(thread)

見た感じこれで駄目なら私にはわかりません。 画像

C言語です

Re:マルチスレッドのファイル書き込み

#10

投稿記事 by C言語です » 15年前

みなさま、コメントありがとうございます!
いろいろ試してみまして、期待通り動きました!ありがとうございました。
まとめて返信で失礼致します。

>シエルさん、Mistさん
コメントありがとうございました!同じスレッド関数を使ってスレッド作ってもいい
のかな?という点は少し不安だったので、問題ないとのことで、助かりました~。


>toyoさん
LockFileEx()にLOCKFILE_FAIL_IMEDIATELYフラグ追加したところ、ロックできない
場合すぐ返ってきて、止まらず最後まで走るようにはなりました。ただ、ほとんど
のスレッドでロックできず、ファイルに書き込めるのは2スレッド前後でした。

そこで、ロック失敗後にまたロックするよう無限リトライしたところ、全スレッド
でファイル書き込みができました!ありがとうございました~。

retry:
if( LockFileEx( hFile, LOCKFILE_EXCLUSIVE_LOCK |LOCKFILE_FAIL_IMMEDIATELY, ...
{
:
}
else
goto retry;

ただ、無限ループになってしまっている点が、少々無駄にCPUを使っているんじゃない
だろうか・・?と気になってしまいます・・。


>御津凪さん
ご指摘のとおり、MSDNのLockFileExのページを見ると「領域をロックすると、他の
プロセスがその領域にアクセスすることを防止できます。」と書いてあるだけで、
「スレッドでも大丈夫」とは書いてありませんでした。これは「プロセス間排他は
いいけどスレッド間排他はダメ」ということでしょうか。

LockFileEx をやめて、クリティカルセクションを使ってみたところ、期待通り動作
しました!

EnterCriticalSection(&cs);
SetFilePointer( hFile, 0, NULL, FILE_END );
WriteFile( hFile, buf, len, &dwWrite, NULL );
LeaveCriticalSection(&cs);

なるほど、こういう時に使うんですね。このやり方がいいのかな・・。
勉強になりました。ありがとうございました!


>お豆さん
スレッドのハンドルをCloseHandleする処理はすっかり抜け落ちていました。ご指摘
ありがとうございました!
OVERLAPPED 構造体の初期化を、ZeroMemory だけでなくちゃんとやってみました。が、
特に挙動に変化は見られないようでした。
また、CreateFile() で FILE_FLAG_OVERLAPPED フラグを使ってみたところ、なんと
LockFileEx でブロックせずに、ロックできないとすぐに戻ってくるようになりました。
これは LOCKFILE_FAIL_IMMEDIATELY を指定した時の挙動と似ています。意外でした。
こんな動作になるんですね・・勉強になりました。ありがとうございました~。



趣味で画像処理アプリを作っていたのですが、巨大画像の処理を速くしたくて、マルチ
スレッドにしてみようと、手を加え始めた矢先につまづいていましたが、これで前に
進めそうです!みなさま本当にありがとうございました!

最終的なソースは以下のようになりました。クリティカルセクションを使っています。
─────────────< main.c >────────────────
#include <windows.h>
#include <process.h>
#include <stdio.h>

#define THREAD_NUM 16

HANDLE hThread[THREAD_NUM];
HANDLE hFile;
CRITICAL_SECTION cs;

unsigned __stdcall ThreadProc( void *p )
{
UINT id = (UINT)p;
char buf[128];
DWORD dwWrite;
OVERLAPPED ov;

_snprintf( buf,128,"スレッド%dです。\r\n",id );

ZeroMemory( &ov, sizeof(OVERLAPPED) );
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = NULL;

// クリティカルセクションでスレッド間排他制御して追記書き込み
EnterCriticalSection( &cs );

SetFilePointer( hFile, 0, NULL, FILE_END );
WriteFile( hFile, buf, strlen(buf), &dwWrite, NULL );

LeaveCriticalSection( &cs );

_endthreadex(0);
return 0;
}

void main( void )
{
int i;

InitializeCriticalSection( &cs );

hFile = CreateFile( "out.txt", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( hFile==INVALID_HANDLE_VALUE )
{
puts("CreateFileエラー");
return;
}

for( i=0; i<THREAD_NUM; i++ )
{
hThread = (HANDLE)_beginthreadex( NULL, 0, ThreadProc, (void*)i, 0, NULL );
if( !hThread )
puts("_beginthreadexエラー");
}

WaitForMultipleObjects( THREAD_NUM, hThread, TRUE, INFINITE );

for( i=0; i<THREAD_NUM; i++ ) if( hThread ) CloseHandle( hThread );
CloseHandle( hFile );
} 画像

閉鎖

“C言語何でも質問掲示板” へ戻る