1000个线程,写某个内存池,每个线程都写,然后开n线程读池,会不会脏读,需要读写锁吗?(200分)

  • 主题发起人 主题发起人 bsense
  • 开始时间 开始时间
B

bsense

Unregistered / Unconfirmed
GUEST, unregistred user!
function MyThreadFunc(P: pointer): Longint; stdcall;
var pp: pblock;
begin
pp := mp.AllocBlock;//取得空闲内存
pp.FileId := 'a';//写数据,比如会有4kb数据
end;

procedure TForm1.Button4Click(Sender: TObject);
var
hThread: Thandle; //定义一个句柄
ThreadID: DWord;
i:integer;
p: pblock;
begin
for i:= 0 to 1000 do
begin

hthread := CreateThread(nil, 0, @MyThreadfunc, nil, 0, ThreadID);
if hThread = 0 then
messagebox(Handle, '建立线程失败', nil, MB_OK);
end;
end;

procedure TForm1.Button5Click(Sender: TObject);
var i:integer;
p: pblock;
begin
for i := 0 to mp.GetCount - 1 do
begin
p := mp.GetBlock(i);
if assigned(p) then
if p.IsUse then
memo1.Lines.Add(p^.FileId);//这里读,会不会脏读,死锁?需要读写锁吗?
end;

其中mp是一个内存池
 
读写都加临界区
 
最好采用数珠,并在写时用变亮1..n(1000)判断空间是否被占用,没有占用就写,就不会有
错误的数据!
 
比如我用一个iocp控件,receive里面收到数据,写到一个内存池里面去
此内存池是否可以取出里面的块数据????安全吗?
unit MemoryPool;

interface
uses Windows, Classes, SysUtils;

const
BUFFER_SIZE = 4096; //接收缓冲区大小
DEFAULTBLOCKCOUNT = 4096;
//默认开4096个内存块 ,假设同时32个客户端,发送128个块(128 * 4096=524288字节)

type
EMemoryPool = class(Exception);
//TMemoryPool = class;

//实际将用到的内存块,可以扫描,找出相同文件的块,然后组成文件,减少数据库交互
PBlock = ^TBlock;
TBlock = packed record //相当于一个PBLOCK开辟的内存块=4096+n个字节
FileId:string[20];//文件ID 4区域Id + 2客户ID + 8日期 + 6序列号,每日可传输100万个文件,此信息在Data里面又包含有
Data: array[0..BUFFER_SIZE - 1] of Byte;
IsUse: Boolean;
//需要增加读写锁?因为需要扫描其他块,并合并,如果去读,而
//其他线程需要写怎么办,如果某线程正在写,又需要读,会不会读错误
end;

//内存池类
TMemoryPool = class
private
FList: TList;
FLock: TRTLCriticalSection;
function FGetCount: Integer; //取得内存池块数量
function FGetBlock(const Index: Integer): PBlock; //取得某块
protected
property Count: Integer read FGetCount; //块数量
property Blocks[const Index: Integer]: PBlock read FGetBlock; //取得某块
public
constructor Create(); overload;//初始化,默认块数
constructor Create(BlockCount: Integer); overload;//指定块数初始化
destructor Destroy; override;//释放
function AllocBlock: PBlock; //获得一个可用块
procedure RemoveBlock(Block: PBlock);//释放一个块
//自己扩展的
function GetCount: Integer; //取得内存池块数量
function GetBlock(const Index: Integer): PBlock; //取得某块
//published//改为开放的属性
// property Count: Integer read GetCount; //块数量
// property Blocks[const Index: Integer]: PBlock read GetBlock; //取得某块
end;

const
BLOCKSIZE: Word = SizeOf(TBlock);

implementation

//创建默认大小池
constructor TMemoryPool.Create();
begin
Create(DEFAULTBLOCKCOUNT);
end;

//创建指定大小池
constructor TMemoryPool.Create(BlockCount: Integer);
var
I: Integer;
P: PBlock;
begin
inherited Create;
InitializeCriticalSection(FLock);
FList := TList.Create;
for I := 0 to BlockCount - 1 do
begin
New(P);
FillChar(P^, BLOCKSIZE, 0);
FList.Add(P);
end;
end;

//释放池对象
destructor TMemoryPool.Destroy;
var
I: Integer;
begin
try
EnterCriticalSection(FLock);

for I := FList.Count - 1 downto 0 do
FreeMem(FList);
FList.Free;
finally
LeaveCriticalSection(FLock);
DeleteCriticalSection(FLock);
end;
inherited Destroy;
end;

//获取一个可用块
function TMemoryPool.AllocBlock: PBlock;
var
I: Integer;
begin
EnterCriticalSection(FLock);//进入临界区
try
Result := nil;

for I := FList.Count - 1 downto 0 do
begin
Result := FList;
if not Result.IsUse then
break;//找到可用空闲块
end;
if not Assigned(Result) or Result.IsUse then//最后也没有找到或者被释放了
begin
New(Result);//申请新的块
FList.Add(Result);//加入到列表
end;
FillChar(Result^.Data, SizeOf(Result^.Data), 0);//清空为0
Result^.IsUse := True;//使用标志
finally
LeaveCriticalSection(FLock);//离开临界区
end;
end;

//回收内存块到内存池中,可以继续使用
procedure TMemoryPool.RemoveBlock(Block: PBlock);
begin
EnterCriticalSection(FLock);
try
Block.IsUse := False;//只设置不使用标志
finally
LeaveCriticalSection(FLock);
end;
end;

//获得块数量
function TMemoryPool.FGetCount: Integer;
begin
Result := FList.Count;
end;

//获得一个数据块的指针,没有临界区控制,可以用来读取数据,写入也是可以的
//如果下标超过界限,保错误
function TMemoryPool.FGetBlock(const Index: Integer): PBlock;
begin
if (Index >= Count) or (Index <= -1) then
raise EMemoryPool.CreateFmt('需要不正确', [Index])
else
Result := FList[Index];
end;

//获得块数量
//加入临界区保护
function TMemoryPool.GetCount: Integer;
begin
EnterCriticalSection(FLock);
try
Result := FGetCount;
finally
LeaveCriticalSection(FLock);
end;
end;

//获得一个数据块的指针,没有临界区控制,可以用来读取数据,写入也是可以的
//如果下标超过界限,保错误
//加入临界区保护,因为 获得此 块指针 是线程安全的,所以读写也是?????
//*********************************************************************
//????????????????????????????????????????????????????????????????这个获得的
//指针读写是否安全???? 打算将tcp 收到的数据进行组包,扫描此内存池
function TMemoryPool.GetBlock(const Index: Integer): PBlock;
begin
EnterCriticalSection(FLock);
try
result := FGetBlock(Index);
finally
LeaveCriticalSection(FLock);
end;
end;
end.
 
建议你提名请教本论坛隐居高手张无忌!
 
1、加入临界区保护,因为 获得此 块指针 是线程安全的,所以读写也是?????
答:是的。
2、这个获得的//指针读写是否安全????
答:这个获得指针是安全的。如果你打算将tcp 收到的数据进行组包,扫描此内存池。把对这个指针读写的代码放到临界区,就OK了。

给你的一点建议:
1、取消FGetCount函数,将它与GetCount合并,出于安全性考虑,这可以防止类内部因大意而直接调用FGetCount产生的不同步的问题。
2、FGetBlock与GetBlock也存在同样问题。
3、如果我没猜错这个代码,应该在服务器端的,应该是无人执守的状态下运行。所以
raise EMemoryPool.CreateFmt('需要不正确', [Index])
这样代码就不应该有。这个函数大概应该是这样:

function TMemoryPool.GetBlock(const Index: Integer;var PB:PBlock): Boolean;
begin
EnterCriticalSection(FLock);
try
result := (index < FList) and (index > -1) ;
if result then
PB := FList[Index]
else
PB := Nil ;
finally
LeaveCriticalSection(FLock);
end;
end;

疑惑:如果你是用IOCP技术来做的。为什么是开这么多线程?
 
读写都加临界区
 
谢谢回答
 
IOCP是做的接收,开多个线程那个只是我测试 内存池的代码,和IOCP接收服务器 没有关系.
 
后退
顶部