INI是Windows系统下人们喜闻乐见的一种配置存储方式。Windows提供了一套简单的接口操作INI文件,但它们并不是线程安全的,对于这一点,这些函数比如WritePrivateProfileString的文档中并没有提到。 据这篇文章介绍:

WritePrivateProfileString:

  • WritePrivateProfileString内部使用NtCreateFile访问文件,共享方式设置为:FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE。使用NtLockFile,FailImmediately设置为False,ExlusiveLock设置为True来锁定文件。
  • 这意味着WritePrivateProfileString是非线程安全的,是进程安全的(非远程机器)。

我们目前的软件有大量的并发操作INI的行为,没出现过什么问题只能说是幸运了(亦或是不幸)。同事并不太相信以上结论,让我们用事实证明,写一段并发操作INI的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "stdafx.h"
#include <Windows.h>
#include <vector>
#include <string>
#include <process.h>
#include <cassert>

std::wstring GetIniName()
{
	std::wstring strIniName;
	strIniName.resize(MAX_PATH);
	strIniName.resize(GetModuleFileName(NULL, const_cast<TCHAR*>(strIniName.data()), strIniName.size()));
	strIniName += _T(".ini");
	return strIniName;
}

void WriteIniInThread(void* pText)
{
	const TCHAR *pSec = (TCHAR*)pText;
	assert(pSec);

	std::wstring strVal;
	std::wstring strIni = GetIniName();
	for (int i=0; i<1000; ++i)
	{
		strVal = std::to_wstring(_Longlong(i));
		WritePrivateProfileString(pSec, strVal.data(), strVal.data(), strIni.data());
	}

}

int _tmain(int argc, _TCHAR* argv[])
{
	std::vector<std::wstring> threadTexts;
	std::vector<HANDLE> threadHandles;
	for (int i = 0; i<60; ++i)
	{
		std::wstring strText = _T("Thread");
		strText += std::to_wstring(_Longlong(i));
		threadTexts.push_back(strText);
		threadHandles.push_back(HANDLE(_beginthread(WriteIniInThread, 0, (void*)threadTexts[i].data())));
	}

	WaitForMultipleObjects(threadHandles.size(), threadHandles.data(), TRUE, INFINITE);
	return 0;
}

代码很简单,开60个线程同时往一个INI文件里写东西,让我们对比一下多线程操作INI和非多线程操作的结果,左侧是不使用多线程操作的结果(只贴出前50行):

image

image

结果一目了然。(多线程同时操作INI时,每次的结果可能都不一样的)。