C++ std::threadについて

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

C++ std::threadについて

#1

投稿記事 by きたぐに » 5年前

この前、ReadDirectoryChangesWを別スレッドで動かす、という質問をしたものです。
結局解決できずに流れてしまったのですが、何となく時間を空けてコードを次のように改変したところうまくいきました。
1.変更されたファイル名を管理する構造体の動的な配列をあきらめた
2.同じように結果を返すものもひとつずつ返すようにした

コード:

//改変した部分
	//変更されたファイルの情報
	struct fileInfo
	{
		WCHAR      FileName[1<<8]; //ファイル名 ★
		actionType Type;           //その情報

		//コンストラクタ
		fileInfo( const WCHAR* Name, const size_t& Length, const DWORD& dwRawFileAction ) noexcept :
			FileName(), Type( static_cast<actionType>( dwRawFileAction ) )
		{
			if (Length >= std::size( FileName )) {
				fatal << L"File Name is too long";
				return;
			}
			memcpy( FileName, Name, sizeof( WCHAR )*( Length + 1 ) );
		}
		後略
//ソースコード
const watcher::fileInfo watcher::getModified( const WPARAM& Code, size_t& Where ) const //★
{
	std::lock_guard<std::mutex> _Lock( _get()._mtx );
	const auto _OldWhere = Where;

	++Where;
	if (Where >= _get()._thInfo[Code].Modified.size()) {
		Where = -1;
	}
	
	if (_OldWhere < _get()._thInfo[Code].Modified.size()) {
		return _get()._thInfo[Code].Modified[_OldWhere];
	}
	//データはすべて返した
	return fileInfo();
}
ここからが質問なのですが、なぜこれでうまくいったかがわかりません。
スレッドでは動的な配列は使うべきではないのでしょうか?
IDEはVS2017Community ,C++17です

結城紬
記事: 42
登録日時: 6年前

Re: C++ std::threadについて

#2

投稿記事 by 結城紬 » 5年前

きたぐにさん、こんにちは。
前の記事も見ましたが、そもそもソースの肝心な部分が省略されているので、よくわかりませんね。
(それぞれのメンバ変数の型は何なのか、前のソースからどこが変わったのかなど)
まずは問題が再現する最小限の部分を切り出してはいかがでしょうか。

きたぐに

Re: C++ std::threadについて

#3

投稿記事 by きたぐに » 5年前

すみませんでした。すべて乗っけておきます。
//エラーが起こらないコード

コード:

//ヘッダファイル
#ifndef WATCHER
#define WATCHER

namespace file
{
//ディレクトリの変更を監視する
class watcher
	: private singleton<watcher>
{
public:
	enum class actionType
	{
		file_invalid_flag = -1,
		file_added = FILE_ACTION_ADDED,
		file_removed = FILE_ACTION_REMOVED,
		file_modified = FILE_ACTION_MODIFIED,
		file_renamed_old = FILE_ACTION_RENAMED_OLD_NAME,
		file_renamed_new = FILE_ACTION_RENAMED_NEW_NAME
	};

	enum class status
	{
		run,
		ready_terminate,
		terminate,
	};

	//変更されたファイルの情報
	struct fileInfo
	{
		WCHAR      FileName[1<<8]; //ファイル名
		actionType Type;           //その情報

		//コンストラクタ
		fileInfo( const WCHAR* Name, const size_t& Length, const DWORD& dwRawFileAction ) noexcept :
			FileName(), Type( static_cast<actionType>( dwRawFileAction ) )
		{
			if (Length >= std::size( FileName )) {
				fatal << L"File Name is too long";
				return;
			}
			memcpy( FileName, Name, sizeof( WCHAR )*( Length + 1 ) );
		}
		//デフォルトコンストラクタ
		fileInfo() :
			fileInfo( L"", 0, -1 )
		{
		}
		//コピーコンストラクタ
		fileInfo( const fileInfo& Left ) :
			Type( Left.Type )
		{
			memcpy( FileName, Left.FileName, sizeof( FileName ) );
		}
		//ムーブコンストラクタ
		fileInfo( fileInfo&& Right ) noexcept :
			Type( Right.Type )
		{
			memcpy( FileName, Right.FileName, sizeof( FileName ) );
			ZeroMemory( Right.FileName, sizeof( FileName ) );
			Right.Type = actionType::file_invalid_flag;
		}

		fileInfo& operator=( const fileInfo& Left )
		{
			if (this == std::addressof( Left )) {
				return *this; //自己代入は禁止
			}
			memcpy( FileName, Left.FileName, sizeof( FileName ) );
			this->Type = Left.Type;

			return *this;
		}

		fileInfo& operator=( fileInfo&& Right )
		{
			if (this == std::addressof( Right )) {
				return *this;
			}

			memcpy( FileName, Right.FileName, sizeof( FileName ) );
			ZeroMemory( Right.FileName, sizeof( FileName ) );
			this->Type = Right.Type;
			Right.Type = actionType::file_invalid_flag;

			return *this;
		}
	};

private:
	struct thread_info
	{
		std::wstring          Path;
		DWORD                 Filter;
		status                Status;
		std::vector<fileInfo> Modified; //編集されたリスト
		std::thread           Thread;

		//デフォルトコンストラクタ
		thread_info() noexcept :
			Path(), Filter(), Status( status::terminate ), Modified(), Thread()
		{
		}
		//コンストラクタ
		template< class _Fn,class... _Args>
		thread_info( const WCHAR* lpszPath, const DWORD& dwFilter, _Fn&& Func, _Args&&... Args ) :
			Path( lpszPath ), Filter( dwFilter ), Status( status::run ), Modified(),
			Thread( std::forward<_Fn>( Func ), std::forward<_Args>( Args )... )
		{
		}
		//コピーは禁止
		thread_info( const thread_info& ) = delete;
		thread_info& operator=( const thread_info& ) = delete;
		//ムーブのみ
		thread_info( thread_info&& Right ) noexcept :
			Path( std::move( Right.Path ) ), Filter( Right.Filter ),Status(Right.Status),
			Modified( std::move( Right.Modified ) ), Thread( std::move( Right.Thread ) )
		{
			Right.Filter = 0;
			Right.Status = status::terminate;
		}
		thread_info& operator=( thread_info&& Right ) noexcept
		{
			if (this == std::addressof( Right )) {
				return *this; //何もしない
			}
			this->Path = std::move( Right.Path );
			this->Filter = Right.Filter;
			this->Status = Right.Status;
			this->Modified = std::move( Right.Modified );
			this->Thread = std::move( Right.Thread );

			Right.Filter = 0;
			Right.Status = status::terminate;
			return *this;
		}
		~thread_info()
		{
			if (Thread.joinable()) {
				warn << L"Thread is alive!";
				Thread.detach();
			}
		}
	};

	static void _main( const WPARAM& Code );
public:
	//デフォルトのフィルタ
	constexpr static DWORD dwDefaultFilter =
		FILE_NOTIFY_CHANGE_FILE_NAME |
		FILE_NOTIFY_CHANGE_DIR_NAME |
		FILE_NOTIFY_CHANGE_ATTRIBUTES |
		FILE_NOTIFY_CHANGE_SIZE |
		FILE_NOTIFY_CHANGE_LAST_WRITE;

	size_t terminate( const WPARAM& Code );
	void assign( const WCHAR* Path, const WPARAM& Param, const DWORD& dwFilter = dwDefaultFilter );
	const fileInfo getModified( const WPARAM& Code,size_t& Where ) const; //★、こいつがエラーを起こしていた
	void sethWnd( HWND hWnd ) noexcept;
	void detach_all();
private:
	std::map<WPARAM, thread_info> _thInfo;
	std::mutex _mtx;
	HWND   _hWnd;
};

}
#endif
ソースファイル

コード:

#include "stdafx.h"
#include "watcher.h"
#include "define.h"

namespace file
{
void watcher::_main( const WPARAM& Code )
{
	OVERLAPPED _OverLapped;
	std::vector<UCHAR> _Buffer( 1 << 14, 0 );
	DWORD _Size;
	FILE_NOTIFY_INFORMATION* _Info;
	
	//初期化
	CHANDLE _hDict = CreateFileW(_get()._thInfo[Code].Path.data(), FILE_LIST_DIRECTORY,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
		OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr );

	if (_hDict == INVALID_HANDLE_VALUE) {
		error << L"Invalid Handle Value detected " << _get()._thInfo[Code].Path.data() 
			<< L"Code = " << Code;
		std::lock_guard<std::mutex> _Lock( _get()._mtx );
		_get()._thInfo[Code].Status = status::terminate;
		return;
	}

	CHANDLE _hEvent = CreateEventW( nullptr, TRUE, FALSE, nullptr );

	auto _Initialize = [&]()
	{
		ResetEvent( _hEvent );

		_OverLapped = { 0 };
		_OverLapped.hEvent = _hEvent;

		//read only
		return ReadDirectoryChangesW( _hDict, _Buffer.data(), _Buffer.size(),
			TRUE, _get()._thInfo[Code].Filter, &_Size, &_OverLapped, nullptr ) == TRUE;
	};

	_Initialize();

	while (_get()._thInfo[Code].Status == status::run) {
		if (WaitForSingleObject( _hEvent, 500 ) == WAIT_TIMEOUT) {
			continue;
		}
		if (!GetOverlappedResult( _hDict, &_OverLapped, &_Size, FALSE )) {
			_Initialize();
			continue;
		}
		if (_Size == 0) {
			_Initialize();
			continue;
		}
		_Info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>( _Buffer.data() );
		_get()._mtx.lock();
		_get()._thInfo[Code].Modified.clear();
		
		while(true) {
			_get()._thInfo[Code].Modified.emplace_back( _Info->FileName,
				_Info->FileNameLength / sizeof( WCHAR ), _Info->Action );
			if (_Info->NextEntryOffset == 0) {
				break;
			}
			_Info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(
				reinterpret_cast<unsigned char*>( _Info ) + _Info->NextEntryOffset );
		}
		_Buffer.clear();

		_Initialize();
		_get()._mtx.unlock();

		SendMessageW( _get()._hWnd, WM_MODIFIED_FILE, Code, 0 );
	}
	_get()._thInfo[Code].Status = status::terminate;
}

size_t watcher::terminate( const WPARAM& Code )
{
	if (_get()._thInfo[Code].Status == status::run) {
		_get()._thInfo[Code].Status = status::ready_terminate;
		while (_get()._thInfo[Code].Status == status::ready_terminate) {
			std::this_thread::sleep_for( std::chrono::microseconds( 400 ) );
		}
		_get()._thInfo[Code].Thread.detach();
	}
	_get()._thInfo.erase( Code );

	return _get()._thInfo.size();
}

void watcher::assign( const WCHAR* Path, const WPARAM& Param, const DWORD& dwFilter )
{
	std::lock_guard<std::mutex> _Lock( _get()._mtx );

	if (_get()._thInfo.find( Param ) != _get()._thInfo.cend()) {
		//今動いているスレッドを止める
		terminate( Param );
	}
	
	info << L"新しいパス:" << Path << L" Code: " << Param;

	_get()._thInfo.emplace(
		std::piecewise_construct, std::forward_as_tuple( Param ),
		std::forward_as_tuple( Path, dwFilter, _main, Param ) );
}

const watcher::fileInfo watcher::getModified( const WPARAM& Code, size_t& Where ) const
{
	std::lock_guard<std::mutex> _Lock( _get()._mtx );
	const auto _OldWhere = Where;

	++Where;
	if (Where >= _get()._thInfo[Code].Modified.size()) {
		Where = -1;
	}
	
	if (_OldWhere < _get()._thInfo[Code].Modified.size()) {
		return _get()._thInfo[Code].Modified[_OldWhere];
	}
	//データはすべて返した
	return fileInfo();
}

void watcher::sethWnd( HWND hWnd ) noexcept
{
	_get()._hWnd = hWnd;
}

void watcher::detach_all()
{
	std::lock_guard<std::mutex> _Lock( _get()._mtx );
	size_t i = 0;

	while (terminate( _get()._thInfo.begin()->first ) > 0) {}
}

}
古い実装だと、変更されたファイルリストはwatcher::thread_info::ModifiedのものをCodeが一致していればそのまま返していました。(しかも内部で動的にメモリを確保するwstringを使っていました)
今の実装では少し手間がかかりますが、指定された要素を一つずつ返すようにしたところ

**_Pnext** が 0xFFFFFFFFFFFFFFFF でした。
HEAP CORRUPTION DETECTED: after Normal block (#1176) at 0x00000043142539C0. CRT detected that the application wrote to memory after end of heap buffer.
のようなエラーが起こらなくなったのですが、その理由がわからない(上のエラーは確かwstringのデストラクタで起きていました)ので質問した次第です。

結城紬
記事: 42
登録日時: 6年前

Re: C++ std::threadについて

#4

投稿記事 by 結城紬 » 5年前

HEAP CORRUPTION DETECTED は、ヒープの破損を検出したときに出るのであって、破損させた瞬間に発生するのではないので、getChanged で発生していたからと言って、必ずしもそこが原因とは言えないところがあります。
ちなみに、変更前の notify_info のコピーコンストラクタ等はどういう実装になっていますか?

きたぐに

Re: C++ std::threadについて

#5

投稿記事 by きたぐに » 5年前

既に破棄してしまっているので間違っているかもしれませんが、特にこれといった処理をしていた記憶はないです。

コード:

struct  notify_info
{
	notify_info(const notify_info& Left) :
		FileName(Left.FileName),Type(Left.Type)
	{
	}
	notify_info( notify_info&& Right ) :
		FileName(std::move(Right)),Type(Right.Type)
	{ //Typeに関する処理があったかもしれません
	}
}
返信遅れて申し訳ないです(スパム扱いされて間を空けていました)

結城紬
記事: 42
登録日時: 6年前

Re: C++ std::threadについて

#6

投稿記事 by 結城紬 » 5年前

試しに今のソースの WCHAR 配列を wstring に変えてみたらどうなるでしょうか?
多分発生しないのではないかと思います。もし発生しなければ、原因は wstring ではないということです。

変更前のソースが残っていないのであれば、もうこれ以上追及のしようがありませんね。

きたぐに

Re: C++ std::threadについて

#7

投稿記事 by きたぐに » 5年前

こんにちは。
結論から言いますと発生しました。
例外がスローされました:読み取りアクセス違反。
**_Pnext** が 0xFFFFFFFFFFFFFFFF でした。
呼び出し履歴は次のようになっています。
> .exe!file::watcher::thread_info::~thread_info() 行 178 C++
[外部コード]
.exe!file::watcher::fileInfo::~fileInfo() 行 122 C++
.exe!std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >::~basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >() 行 2460 C++
.exe!std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >::_Tidy_deallocate() 行 3986 C++
.exe!std::_String_alloc<std::_String_base_types<wchar_t,std::allocator<wchar_t> > >::_Orphan_all() 行 2024 C++
.exe!std::_Container_base12::_Orphan_all() 行 221 C++

ヘッダファイル

コード:

#ifndef WATCHER
#define WATCHER

namespace file
{
//ディレクトリの変更を監視する
class watcher
	: private singleton<watcher>
{
public:
	enum class actionType
	{
		file_invalid_flag = -1,
		file_added = FILE_ACTION_ADDED,
		file_removed = FILE_ACTION_REMOVED,
		file_modified = FILE_ACTION_MODIFIED,
		file_renamed_old = FILE_ACTION_RENAMED_OLD_NAME,
		file_renamed_new = FILE_ACTION_RENAMED_NEW_NAME
	};

	enum class status
	{
		run,
		ready_terminate,
		terminate,
	};

	//変更されたファイルの情報
	struct fileInfo
	{
#ifdef USE_WCHAR_ARRAY
		WCHAR      FileName[1<<8]; //ファイル名
#else
		std::wstring FileName;     //ファイル名
#endif
		actionType Type;           //その情報

		//コンストラクタ
		fileInfo( const WCHAR* Name, const size_t& Length, const DWORD& dwRawFileAction )
#ifdef USE_WCHAR_ARRAY
			: noexcept FileName(),
#else 
			: FileName(Name,Length),
#endif
			Type( static_cast<actionType>( dwRawFileAction ) )
		{
#ifdef USE_WCHAR_ARRAY
			if (Length + 1 >= std::size( FileName )) {
				fatal << L"File Name is too long";
				return;
			}
			memcpy( FileName, Name, sizeof( WCHAR )*( Length + 1 ) );
#endif
		}
		//デフォルトコンストラクタ
		fileInfo() :
			fileInfo( L"", 0, -1 )
		{
		}
		//コピーコンストラクタ
		fileInfo( const fileInfo& Left ) :
			Type( Left.Type )
#ifndef USE_WCHAR_ARRAY
			,FileName( Left.FileName )
		{

		}
#else
		{
			memcpy( FileName, Left.FileName, sizeof( FileName ) );
		}
#endif
		//ムーブコンストラクタ
		fileInfo( fileInfo&& Right ) noexcept :
			Type( Right.Type )
#ifndef USE_WCHAR_ARRAY
			,FileName(std::move(Right.FileName))
#endif
		{
#ifdef USE_WCHAR_ARRAY
			memcpy( FileName, Right.FileName, sizeof( FileName ) );
			ZeroMemory( Right.FileName, sizeof( FileName ) );
#endif
			Right.Type = actionType::file_invalid_flag;
		}

		fileInfo& operator=( const fileInfo& Left )
		{
			if (this == std::addressof( Left )) {
				return *this; //自己代入は禁止
			}
#ifdef USE_WCHAR_ARRAY
			memcpy( FileName, Left.FileName, sizeof( FileName ) );
#else
			FileName = Left.FileName;
#endif
			this->Type = Left.Type;

			return *this;
		}

		fileInfo& operator=( fileInfo&& Right )
		{
			if (this == std::addressof( Right )) {
				return *this;
			}

#ifdef USE_WCHAR_ARRAY
			memcpy( FileName, Right.FileName, sizeof( FileName ) );
			ZeroMemory( Right.FileName, sizeof( FileName ) );
#else
			FileName = std::move( Right.FileName );
#endif
			this->Type = Right.Type;
			Right.Type = actionType::file_invalid_flag;

			return *this;
		}
		~fileInfo()
		{

		}
	};

private:
	struct thread_info
	{
		std::wstring          Path;
		DWORD                 Filter;
		status                Status;
		std::vector<fileInfo> Modified; //編集されたリスト
		std::thread           Thread;

		//デフォルトコンストラクタ
		thread_info() noexcept :
			Path(), Filter(), Status( status::terminate ), Modified(), Thread()
		{
		}
		//コンストラクタ
		template< class _Fn,class... _Args>
		thread_info( const WCHAR* lpszPath, const DWORD& dwFilter, _Fn&& Func, _Args&&... Args ) :
			Path( lpszPath ), Filter( dwFilter ), Status( status::run ), Modified(),
			Thread( std::forward<_Fn>( Func ), std::forward<_Args>( Args )... )
		{
		}
		//コピーは禁止
		thread_info( const thread_info& ) = delete;
		thread_info& operator=( const thread_info& ) = delete;
		//ムーブのみ
		thread_info( thread_info&& Right ) noexcept :
			Path( std::move( Right.Path ) ), Filter( Right.Filter ),Status(Right.Status),
			Modified( std::move( Right.Modified ) ), Thread( std::move( Right.Thread ) )
		{
			Right.Filter = 0;
			Right.Status = status::terminate;
		}
		thread_info& operator=( thread_info&& Right ) noexcept
		{
			if (this == std::addressof( Right )) {
				return *this; //何もしない
			}
			this->Path = std::move( Right.Path );
			this->Filter = Right.Filter;
			this->Status = Right.Status;
			this->Modified = std::move( Right.Modified );
			this->Thread = std::move( Right.Thread );

			Right.Filter = 0;
			Right.Status = status::terminate;
			return *this;
		}
		~thread_info()
		{
			if (Thread.joinable()) {
				warn << L"Thread is alive!";
				Thread.detach();
			}
		}
	};

	static void _main( const WPARAM& Code );
public:
	//デフォルトのフィルタ
	constexpr static DWORD dwDefaultFilter =
		FILE_NOTIFY_CHANGE_FILE_NAME |
		FILE_NOTIFY_CHANGE_DIR_NAME |
		FILE_NOTIFY_CHANGE_ATTRIBUTES |
		FILE_NOTIFY_CHANGE_SIZE |
		FILE_NOTIFY_CHANGE_LAST_WRITE;

	size_t terminate( const WPARAM& Code );
	void assign( const WCHAR* Path, const WPARAM& Param, const DWORD& dwFilter = dwDefaultFilter );
	const fileInfo getModified( const WPARAM& Code,size_t& Where ) const;
	void sethWnd( HWND hWnd ) noexcept;
	void detach_all();
private:
	std::map<WPARAM, thread_info> _thInfo;
	std::mutex _mtx;
	HWND   _hWnd;
};

}


#endif

コード:

ソースファイルの変更はありません。

結城紬
記事: 42
登録日時: 6年前

Re: C++ std::threadについて

#8

投稿記事 by 結城紬 » 5年前

二重解放のようですね。
wstring に変更したなら、ヘッダの変更だけで済むはずはありません。
使用している場所の変更もあったはずですが、そちらはどうなっていますか?

伝わらなかったようなので強調しますが、ヒープのエラーの原因の大半は、エラーが発生した場所ではありません。
例えば、ご提示のコードと全く関係ない変数のバッファオーバーフローなどでも発生することがあります。
視野を狭くしていては、原因は見つかりません。

きたぐに

Re: C++ std::threadについて

#9

投稿記事 by きたぐに » 5年前

このサイトにアクセスできません
dixq.net からの応答時間が長すぎます。
次をお試しください:

接続を確認する
プロキシとファイアウォールを確認する
Windows ネットワーク診断ツールを実行する
ERR_TIMED_OUT
と言われて返信がリセットされてしまいましたので変更しなかった理由をは的に述べます。

ソースファイルから、fileinfoに変更を加えるような処理はすべてデストラクタ/ユーザー定義の代入演算子などヘッダファイル側から制御している関数でアクセスしているつもりです。

デストラクタの二重開放なんですがこういうコードは一応無事でした。

コード:

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );


	std::wstring _Stream( 1024, 0 );

	_Stream.~basic_string();
	_Stream.~basic_string();
}
エラーになっているのはstd::map::erase内でデストラクタが呼ばれているのですがそこで起きているようです

返信

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