多线程 操作多文件 性能方案讨论与请教

hulose 2010-08-13 04:58:45
前不久刚完成了一个IOCP的服务器

现在碰到一个问题

就是该IOCP会操作很多的文件 (文件个数可能会是上万个) 其中可能会有100来个 更新会很频繁
然后做2种操作 一个是读 一个是创建文件 (已存在的文件可能会被重新创建)

现在问题就是 如果只用一个临界区做锁 那么 当多个线程都为读的时候 性能又会很差 因为不管是读多个文件还是读同一个文件 其实都是可以同时去读的 并不需要去锁

但更不可能为每个文件都去创建锁

如果取消锁 拼运气(读的频率会很高 写的频率相对会低很多) 那又感觉好像太不厚道了...


所以向大家请教与讨论 寻求更好的解决方案
...全文
158 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
野男孩 2010-08-16
  • 打赏
  • 举报
回复
ReadWrite.c

/*
* ReadWrit.c
*
* Sample code for "Multithreading Applications in Win32"
* This is from Chapter 7, various listings.
*
* Demonstrates an implementation of the
* Readers/Writers algorithm. This version
* gives preference to readers.
*/

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "ReadWrit.h"

// If we wait more than 2 seconds, then something is probably wrong!
#define MAXIMUM_TIMEOUT 2000

// Here's the pseudocode for what is going on:
//
// Lock for Reader:
// Lock the mutex
// Bump the count of readers
// If this is the first reader, lock the data
// Release the mutex
//
// Unlock for Reader:
// Lock the mutex
// Decrement the count of readers
// If this is the last reader, unlock the data
// Release the mutex
//
// Lock for Writer:
// Lock the data
//
// Unlock for Reader:
// Unlock the data

///////////////////////////////////////////////////////

BOOL MyWaitForSingleObject(HANDLE hObject)
{
DWORD result;

result = WaitForSingleObject(hObject, MAXIMUM_TIMEOUT);
// Comment this out if you want this to be non-fatal
if (result != WAIT_OBJECT_0)
FatalError("MyWaitForSingleObject - Wait failed, you probably forgot to call release!");
return (result == WAIT_OBJECT_0);
}

BOOL InitRWLock(RWLock *pLock)
{
pLock->nReaderCount = 0;
pLock->hDataLock = CreateSemaphore(NULL, 1, 1, NULL);
if (pLock->hDataLock == NULL)
return FALSE;
pLock->hMutex = CreateMutex(NULL, FALSE, NULL);
if (pLock->hMutex == NULL)
{
CloseHandle(pLock->hDataLock);
return FALSE;
}
return TRUE;
}

BOOL DestroyRWLock(RWLock *pLock)
{
DWORD result = WaitForSingleObject(pLock->hDataLock, 0);
if (result == WAIT_TIMEOUT)
return FatalError("DestroyRWLock - Can't destroy object, it's locked!");

CloseHandle(pLock->hMutex);
CloseHandle(pLock->hDataLock);
return TRUE;
}

BOOL AcquireReadLock(RWLock *pLock)
{
BOOL result = TRUE;

if (!MyWaitForSingleObject(pLock->hMutex))
return FALSE;

if(++pLock->nReaderCount == 1)
result = MyWaitForSingleObject(pLock->hDataLock);

ReleaseMutex(pLock->hMutex);
return result;
}

BOOL ReleaseReadLock(RWLock *pLock)
{
int result;
LONG lPrevCount;

if (!MyWaitForSingleObject(pLock->hMutex))
return FALSE;

if (--pLock->nReaderCount == 0)
result = ReleaseSemaphore(pLock->hDataLock, 1, &lPrevCount);

ReleaseMutex(pLock->hMutex);
return result;
}

BOOL AcquireWriteLock(RWLock *pLock)
{
return MyWaitForSingleObject(pLock->hDataLock);
}

BOOL ReleaseWriteLock(RWLock *pLock)
{
int result;
LONG lPrevCount;

result = ReleaseSemaphore(pLock->hDataLock, 1, &lPrevCount);
if (lPrevCount != 0)
FatalError("ReleaseWriteLock - Semaphore was not locked!");
return result;
}

BOOL ReadOK(RWLock *pLock)
{
// This check is not perfect, because we
// do not know for sure if we are one of
// the readers.
return (pLock->nReaderCount > 0);
}

BOOL WriteOK(RWLock *pLock)
{
DWORD result;

// The first reader may be waiting in the mutex,
// but any more than that is an error.
if (pLock->nReaderCount > 1)
return FALSE;

// This check is not perfect, because we
// do not know for sure if this thread was
// the one that had the semaphore locked.
result = WaitForSingleObject(pLock->hDataLock, 0);
if (result == WAIT_TIMEOUT)
return TRUE;

// a count is kept, which was incremented in Wait.
result = ReleaseSemaphore(pLock->hDataLock, 1, NULL);
if (result == FALSE)
FatalError("WriteOK - ReleaseSemaphore failed");
return FALSE;
}

///////////////////////////////////////////////////////

/*
* Error handler
*/
BOOL FatalError(char *s)
{
fprintf(stdout, "%s\n", s);
// Comment out exit() to prevent termination
exit(EXIT_FAILURE);
return FALSE;
}


测试程序

/*
* List.c
*
* Sample code for "Multithreading Applications in Win32"
* This is from Chapter 7, Listing 7-1
*
* Demonstrates an implementation of the
* Readers/Writers algorithm. This version
* gives preference to readers.
*/

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "ReadWrit.h"

///////////////////////////////////////////////////////

//
// Structure definition
//

typedef struct _Node
{
struct _Node *pNext;
char szBuffer[80];
} Node;

typedef struct _List
{
RWLock lock;
Node *pHead;
} List;

//
// Linked list prototypes
//

BOOL InitRWLock(RWLock *pLock);
BOOL DeleteList(List *pList);
BOOL AddHead(List *pList, Node *node);
BOOL DeleteHead(List *pList);
BOOL Insert(List *pList, Node *afterNode, Node *newNode);
Node *Next(List *pList, Node *node);

//
// Test functions prototypes
//

DWORD WINAPI LoadThreadFunc(LPVOID n);
DWORD WINAPI SearchThreadFunc(LPVOID n);
DWORD WINAPI DeleteThreadFunc(LPVOID n);

//
// Global variables
//

// This is the list we use for testing
List *gpList;

///////////////////////////////////////////////////////

List *CreateList()
{
List *pList = GlobalAlloc(GPTR, sizeof(List));
if (InitRWLock(&pList->lock) == FALSE)
{
GlobalFree(pList);
pList = NULL;
}
return pList;
}

BOOL DeleteList(List *pList)
{
AcquireWriteLock(&pList->lock);
while (DeleteHead(pList))
;
ReleaseWriteLock(&pList->lock);

DestroyRWLock(&gpList->lock);

GlobalFree(pList);

return TRUE;
}

BOOL AddHead(List *pList, Node *pNode)
{
if (!WriteOK(&pList->lock))
return FatalError("AddHead - not allowed to write!");

pNode->pNext = pList->pHead;
pList->pHead = pNode;
}

BOOL DeleteHead(List *pList)
{
Node *pNode;

if (!WriteOK(&pList->lock))
return FatalError("AddHead - not allowed to write!");

if (pList->pHead == NULL)
return FALSE;

pNode = pList->pHead->pNext;
GlobalFree(pList->pHead);
pList->pHead = pNode;
return TRUE;
}

BOOL Insert(List *pList, Node *afterNode, Node *newNode)
{
if (!WriteOK(&pList->lock))
return FatalError("Insert - not allowed to write!");

if (afterNode == NULL)
{
AddHead(pList, newNode);
}
else
{
newNode->pNext = afterNode->pNext;
afterNode->pNext = newNode;
}
}

Node *Next(List *pList, Node *pNode)
{
if (!ReadOK(&pList->lock))
{
FatalError("Next - Not allowed to read!");
return NULL;
}

if (pNode == NULL)
return pList->pHead;
else
return pNode->pNext;
}

///////////////////////////////////////////////////////

DWORD WINAPI ThreadFunc(LPVOID);

int main()
{
HANDLE hThrds[4];
int slot = 0;
int rc;
int nThreadCount = 0;
DWORD dwThreadId;

gpList = CreateList();
if (!gpList)
FatalError("main - List creation failed!");

hThrds[nThreadCount++] = CreateThread(NULL,
0, LoadThreadFunc, 0, 0, &dwThreadId );

hThrds[nThreadCount++] = CreateThread(NULL,
0, SearchThreadFunc, (LPVOID)"pNode", 0, &dwThreadId );

hThrds[nThreadCount++] = CreateThread(NULL,
0, SearchThreadFunc, (LPVOID)"pList", 0, &dwThreadId );

hThrds[nThreadCount++] = CreateThread(NULL,
0, DeleteThreadFunc, 0, 0, &dwThreadId );

/* Now wait for all threads to terminate */
rc = WaitForMultipleObjects(
nThreadCount,
hThrds,
TRUE,
INFINITE );

for (slot=0; slot<nThreadCount; slot++)
CloseHandle(hThrds[slot]);
printf("\nProgram finished.\n");

DeleteList(gpList);

return EXIT_SUCCESS;
}

/*
* Slowly load the contents of "List.c" into the
* linked list.
*/
DWORD WINAPI LoadThreadFunc(LPVOID n)
{
int nBatchCount;
Node *pNode;

FILE* fp = fopen("List.c", "r");
if (!fp)
{
fprintf(stderr, "ReadWrit.c not found\n");
exit(EXIT_FAILURE);
}

pNode = GlobalAlloc(GPTR, sizeof(Node));
nBatchCount = (rand() % 10) + 2;
AcquireWriteLock(&gpList->lock);

while (fgets(pNode->szBuffer, sizeof(Node), fp))
{
AddHead(gpList, pNode);

// Try not to hog the lock
if (--nBatchCount == 0)
{
ReleaseWriteLock(&gpList->lock);
Sleep(rand() % 5);
nBatchCount = (rand() % 10) + 2;
AcquireWriteLock(&gpList->lock);
}
pNode = GlobalAlloc(GPTR, sizeof(Node));
}

ReleaseWriteLock(&gpList->lock);
return 0;
}


/*
* Every so often, walked the linked list
* and figure out how many lines one string
* appears (given as the startup param)
*/
DWORD WINAPI SearchThreadFunc(LPVOID n)
{
int i;
char *szSearch = (char *)n;

for (i=0; i<20; i++)
{
int nFoundCount = 0;
Node *next = NULL;

AcquireReadLock(&gpList->lock);
next = Next(gpList, next);
while (next)
{
if (strstr(next->szBuffer, szSearch))
nFoundCount++;
next = Next(gpList, next);
}

ReleaseReadLock(&gpList->lock);

printf("Found %d lines with '%s'\n", nFoundCount, szSearch);
Sleep((rand() % 30));
}
return 0;
}


/*
* Every so often, delete some entries in the list.
*/
DWORD WINAPI DeleteThreadFunc(LPVOID n)
{
int i;

for (i=0; i<100; i++)
{
Sleep(1);
AcquireWriteLock(&gpList->lock);
DeleteHead(gpList);
DeleteHead(gpList);
DeleteHead(gpList);
ReleaseWriteLock(&gpList->lock);
}

return 0;
}

野男孩 2010-08-16
  • 打赏
  • 举报
回复
摘自 <MultiThreading Application In Win32>

网上有侯捷翻译的中文版<Win32多线程程序设计>

ReadWrite.h

/*
* ReadWrit.h
*
* Sample code for Multithreading Applications in Win32
* This is from Chapter 7, Listing 7-1
*
* Demonstrates an implementation of the
* Readers/Writers algorithm. This version
* gives preference to readers.
*/

///////////////////////////////////////////////////////

//
// Structure definition
//

typedef struct _RWLock
{
// Handle to a mutex that allows
// a single reader at a time access
// to the reader counter.
HANDLE hMutex;

// Handle to a semaphore that keeps
// the data locked for either the
// readers or the writers.
HANDLE hDataLock;

// The count of the number of readers.
// Can legally be zero or one while
// a writer has the data locked.
int nReaderCount;
} RWLock;

//
// Reader/Writer prototypes
//

BOOL InitRWLock(RWLock *pLock);
BOOL DestroyRWLock(RWLock *pLock);
BOOL AcquireReadLock(RWLock *pLock);
int ReleaseReadLock(RWLock *pLock);
BOOL AcquireWriteLock(RWLock *pLock);
int ReleaseWriteLock(RWLock *pLock);
BOOL ReadOK(RWLock *pLock);
BOOL WriteOK(RWLock *pLock);

BOOL FatalError(char *s);



lijianli9 2010-08-15
  • 打赏
  • 举报
回复
有种多线程技术叫:
单写多读锁。lz可以考虑这样实现吧。
xxd_qd 2010-08-15
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 hulose 的回复:]
我原先的思路是

1、如果有线程读的时候 那么 用一个[读引用计数] 来记录读个数 读操作完成的减计数
2、当有一个线程要写的时候 则禁止新的读进入线程做读操作 并进入互斥等待
3、当所有读操作都完成[读引用计数]=0的时候,写操作开始 (此时所有读操作都在与[写线程]互斥排队)
4、当写操作完成后, 所有读线程又进入第1步

这样子 在写的时候 性能等于单临界区 但读的时候又没有互斥
但可能处理起来比较复杂 手头事又比较多 就还没去实现了[/Quote]
你的这个思路我也考虑过,乍一想没什么问题,可如果你真要去这么实现,就会发现问题不是一般的多。比如一个读线程,它必须先检测是否存在写锁,如果有的话等待,没有的话要给读计数加一,然后又要检测有没有读锁,没有的话要新建一个(必须给写线程建立一个可以用来Wait的东西)。这些操作必须放入一个临界区,否则等你刚做完判断,还没来得及执行操作,别的线程就把你的判断依据给改了。这个临界区是所有线程共用的一个单一临界区(进临界区之前还没说明你要操作哪一个文件呢,所以任一线程对任一文件操作的时候,都得到这一个临界区排队),因此绝不能有线程在这个临界区里Wait。既然如此,那么你检测锁的操作,都只能是检测,比如读线程最初检测是不是有写锁存在的时候,如果检测到有的话,那么必须先退出临界区,然后再Wait写锁。可是等你Wait完了呢?还得再进临界区来设置读计数等等(还得记得释放写锁,否则后面的读线程就进不来了)!如果看到这里你还没晕的话,恭喜你,你可以再去考虑一下写线程的细节问题了。。。不出意外的话,当你想清楚写线程的问题后,你会发现前面关于读线程的思路尚需要进一步调整一下,嗯,确切地说,是进一步复杂化一下。。。
总之,除非你放弃读共享(即:不允许多个线程同时读同一个文件),否则的话,最合理的方式是建立一个专门的调度线程,用来控制所有其它线程在文件操作上的同步和互斥。单靠线程本身自发控制,就算你现在能彻底把这团乱麻想清楚,回头过几天你自己就又看不懂了。

其实,文件的读写共享问题,操作系统本身就可以处理得很好,只不过当共享冲突的时候,CreateFile并不等待,而是直接返回错误。而你现在的问题无非就是要解决这个等待吗?那你弄个循环,只要发生共享冲突的时候Sleep(50)就是了,虽然粗糙点,不过这实在是最简单的解决方案了。
maosher 2010-08-15
  • 打赏
  • 举报
回复
看下WINDOWS核心编程的读写锁
xxd_qd 2010-08-14
  • 打赏
  • 举报
回复
这个问题最大的难度不在于加锁,而在于允许多个线程同时读同一个文件(即读共享),此时一个试图写该文件的线程必须等待所有读该文件的线程全部退出之后才能开始写,而Windows却并没有提供这种等待机制,除非自己做线程调度。
hulose 2010-08-14
  • 打赏
  • 举报
回复
我想 也只能用信号量 来实现我上面的想法了

没有更好的办法 就算了
hulose 2010-08-14
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 oyljerry 的回复:]
有读写,就只能加锁了
如果写的时候,无所谓数据读的话,可以把数据内容放到共享内存,然后读的时候都读内存,只给写的加锁..
[/Quote]

因为文件太多 所以无法知道要读的是哪个文件 所以也就无法以内存缓冲形式来实现了

而且同样的 如果文件要更新内容 内存也是要更新内容的 所以同样存在互斥问题

=--------------------------
我原先的思路是

1、如果有线程读的时候 那么 用一个[读引用计数] 来记录读个数 读操作完成的减计数
2、当有一个线程要写的时候 则禁止新的读进入线程做读操作 并进入互斥等待
3、当所有读操作都完成[读引用计数]=0的时候,写操作开始 (此时所有读操作都在与[写线程]互斥排队)
4、当写操作完成后, 所有读线程又进入第1步

这样子 在写的时候 性能等于单临界区 但读的时候又没有互斥
但可能处理起来比较复杂 手头事又比较多 就还没去实现了


hulose 2010-08-14
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 xxd_qd 的回复:]
这个问题最大的难度不在于加锁,而在于允许多个线程同时读同一个文件(即读共享),此时一个试图写该文件的线程必须等待所有读该文件的线程全部退出之后才能开始写,而Windows却并没有提供这种等待机制,除非自己做线程调度。
[/Quote]

你的看法是最明白的

目前我也是没找到很完美的办法 因为有几个重点问题
1、是大量的文件 如果上万的文件 因此我不想建立上万个临界区
2、多线程也可能操作的时候(不管读还是写) 因为文件多 可能读写的重复率相对较低 只用一个临界区 肯定效率低

[Quote]
读写锁啊。。。。允许并发读,写的时候等待所有读操作结束
[/Quote]
我也想的到允许并发读 写的时候锁
正如2楼所讲 Windows没有提供现成的方法 你来展示一下完整的思路和解决方案吧 可以用伪代码描述一下
野男孩 2010-08-14
  • 打赏
  • 举报
回复
读写锁啊。。。。允许并发读,写的时候等待所有读操作结束
oyljerry 2010-08-13
  • 打赏
  • 举报
回复
有读写,就只能加锁了
如果写的时候,无所谓数据读的话,可以把数据内容放到共享内存,然后读的时候都读内存,只给写的加锁..

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧