1
0
mirror of https://github.com/loginov-dmitry/multithread.git synced 2024-11-24 16:53:48 +02:00

Create MutexObjectUnit.pas

This commit is contained in:
loginov-dmitry 2024-10-12 18:42:52 +03:00
parent c81a26284f
commit 3b2966ea48

View File

@ -0,0 +1,340 @@
{
Copyright (c) 2024, Loginov Dmitry Sergeevich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
unit MutexObjectUnit;
{$IFDEF FPC}{$MODE DELPHI}{$H+}{$CODEPAGE UTF8}{$ENDIF}
interface
uses
{$IFDEF MSWINDOWS}Windows,{$ENDIF}
{$IFDEF UNIX}Unix, BaseUnix, pthreads, TimeIntervals, unixtype, {$ENDIF}
Classes, SysUtils, SyncObjs;
type
EMutexError = class(Exception);
{ TMutexObject }
TMutexObject = class
private
FMutexName: string;
{$IFDEF MSWINDOWS}
FMutex: THandle; // Объект "мьютекс" будет создан через функцию CreateMutexShared
{$ELSE MSWINDOWS}
UsePthreadMutex: Boolean;
FMutex: pthread_mutex_t; // Используется если не указано имя мьютекса (только внутри процесса)
// по сути, та же критическая секция, только с таймаутом
FLockFD: cint; // Дескриптор файла блокировки (если указано имя мьютекса)
FLockCount: Integer; // Если блокировка установлена, то FLockCount > 0
FLockThreadId: UInt64; // ID потока, который установил блокировку (только он может снять блокировку)
function GetLockFileName: string;
{$ENDIF MSWINDOWS}
public
constructor Create(MutexName: string; aInitialOwner: Boolean = False);
destructor Destroy; override;
function WaitFor(TimeOutMs: DWORD): TWaitResult;
procedure ReleaseMutex;
end;
implementation
{$IfDef MSWINDOWS}
function CreateMutexShared(AName: string; LastError: PCardinal = nil): THandle;
var
SD:TSecurityDescriptor;
SA:TSecurityAttributes;
pSA: PSecurityAttributes;
begin
if not InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION) then
raise EMutexError.CreateFmt('CreateMutexShared: Error InitializeSecurityDescriptor: %s', [SysErrorMessage(GetLastError)]);
SA.nLength:=SizeOf(TSecurityAttributes);
SA.lpSecurityDescriptor:=@SD;
SA.bInheritHandle:=False;
if not SetSecurityDescriptorDacl(SA.lpSecurityDescriptor, True, nil, False) then
raise EMutexError.CreateFmt('CreateMutexShared: Error SetSecurityDescriptorDacl: %s', [SysErrorMessage(GetLastError)]);
pSA := @SA;
Result := CreateMutex(pSA, False, PChar('Global\' + AName)); // Пытаемся создать с директивой Global
if Result = 0 then
Result := CreateMutex(pSA, False, PChar(AName)); // Пытаемся создать без директивы Global
if Assigned(LastError) then
LastError^ := GetLastError;
if Result = 0 then
raise EMutexError.CreateFmt('CreateMutexShared: can not create mutex object: %s', [SysErrorMessage(GetLastError)]);
end;
{$EndIf MSWINDOWS}
{ TMutexObject }
{$IfDef UNIX}
function TMutexObject.GetLockFileName: string;
begin
Result := '/var/lock/' + FMutexName + '.lock';
end;
{$ENDIF}
constructor TMutexObject.Create(MutexName: string; aInitialOwner: Boolean);
{$IfDef LINUX}
var
Mattr: pthread_mutexattr_t;
I: Integer;
{$ENDIF}
begin
FMutexName := MutexName;
{$IfDef MSWINDOWS}
FMutex := CreateMutexShared(MutexName);
{$Else MSWINDOWS}
UsePthreadMutex := MutexName = '';
if UsePthreadMutex then
begin
CheckOSError(pthread_mutexattr_init(@Mattr));
try
CheckOSError(pthread_mutexattr_settype(@Mattr,Ord(PTHREAD_MUTEX_RECURSIVE)));
CheckOSError(pthread_mutex_init(@FMutex,@Mattr));
finally
pthread_mutexattr_destroy(@Mattr); // don't raise second error, it would hide the first
end;
end else
begin
// Удаляем все недопустимые символы из имени мьютекса
for I := 1 to Length(FMutexName) do
if AnsiChar(FMutexName[I]) in ['\', '/', ':', '*', '"', '?', '|', '<', '>'] then
FMutexName[I] := '_';
FMutexName := AnsiLowerCase(FMutexName);
end;
if aInitialOwner then
WaitFor(0);
{$EndIf MSWINDOWS}
end;
destructor TMutexObject.Destroy;
begin
{$IfDef MSWINDOWS}
if FMutex <> 0 then
begin
FileClose(FMutex);
//CloseHandle(HMutex);
FMutex := 0;
end;
{$ELSE MSWINDOWS}
if UsePthreadMutex then
pthread_mutex_destroy(@FMutex)
else
begin
if FLockFD > 0 then
begin
if FLockCount > 0 then
fpFlock(FLockFD, LOCK_UN); // Если блокировка установлена, то снимаем её
fpClose(FLockFD);
end;
end;
{$ENDIF MSWINDOWS}
inherited;
end;
{$IfDef LINUX}
function pthread_mutex_timedlock(__mutex:Ppthread_mutex_t; __abstime: Ptimespec):cint;cdecl; external libthreads;
procedure MSecsFromNow (tNow : Timeval; aTimeout : Integer; out tfuture: TTimespec);
var
td,tm : integer;
begin
td:=aTimeout div 1000;
tm:=aTimeout mod 1000;
tfuture.tv_sec:=tnow.tv_sec+td;
tfuture.tv_nsec:=tnow.tv_usec*1000+(tm*1000*1000);
end;
{$ENDIF LINUX}
function TMutexObject.WaitFor(TimeOutMs: DWORD): TWaitResult;
{$IfDef MSWINDOWS}
var
WinRes: Cardinal;
{$Else MSWINDOWS}
var
td,tm,Errno: Integer;
tnow: ttimeval;
Tmp: timespec;
ti: TTimeInterval;
TmpLockFD, RWMode: cint;
sLockFile: string;
{$EndIf MSWINDOWS}
begin
{$IfDef MSWINDOWS}
WinRes := WaitForSingleObject(FMutex, TimeOutMs);
case WinRes of
WAIT_OBJECT_0: Result := wrSignaled;
WAIT_TIMEOUT: Result := wrTimeout;
WAIT_ABANDONED: Result := wrAbandoned;
else
//WAIT_FAILED:
Result := wrError;
end;
{$Else MSWINDOWS}
if UsePthreadMutex then
begin
Result:=wrError;
if (TimeOutMs=0) then
begin
ErrNo:=pthread_mutex_trylock(@FMutex);
if ErrNo=0 then
Result:=wrSignaled
else if (Errno=ESysEAGAIN) then
Result:=wrTimeout
end
else if (TimeOutMs<>INFINITE) then
begin
fpgettimeofday(@tnow,Nil);
MsecsFromNow(tnow, TimeOutMs,tmp);
ErrNo:=pthread_mutex_timedlock(@FMutex,@tmp);
if ErrNo=0 then
Result:=wrSignaled
else if (Errno=ESysEBUSY) or (errNo=ESysETIMEDOUT) then
Result:=wrTimeout
end
else
begin
if (pthread_mutex_lock(@FMutex)=0) then
Result:=wrSignaled
end;
end else
begin
if (FLockCount > 0) and (GetCurrentThreadId = FLockThreadId) then
begin
Inc(FLockCount);
Result := wrSignaled;
Exit;
end;
sLockFile := GetLockFileName;
TmpLockFD := fpOpen(sLockFile, O_RDWR or O_CREAT);
if TmpLockFD <= 0 then
raise EMutexError.CreateFmt('can not open lock file "%s": %s ', [sLockFile, SysErrorMessage(GetLastOSError)]);
if TimeOutMs = 0 then
begin
if fpFlock(TmpLockFD, LOCK_EX or LOCK_NB) = 0 then
Result := wrSignaled
else
Result := wrTimeout;
end else if TimeOutMs = INFINITE then
begin
if fpFlock(TmpLockFD, LOCK_EX) = 0 then
Result := wrSignaled
else
Result := wrError;
end else
begin
ti.Start;
Result := wrTimeout;
while ti.ElapsedMilliseconds() < TimeOutMs do
begin
// К сожалению, в линуксе невозможно указать время ожидания снятия блокировки
// файла, поэтому организуем цикл ожидания снятия блокировки
if fpFlock(TmpLockFD, LOCK_EX or LOCK_NB) = 0 then
begin
Result := wrSignaled;
Break;
end else if ti.ElapsedMilliseconds > 5 then // Первые 5 миллисекунд НЕ используем Sleep
Sleep(10);
end;
if Result = wrTimeout then
if fpFlock(TmpLockFD, LOCK_EX or LOCK_NB) = 0 then
Result := wrSignaled;
end;
if Result = wrSignaled then // Если удалось захватить блокировку файла
begin
FLockThreadId := GetCurrentThreadId; // Внимание! Для Linux идентификаторы потоков задействуются повторно!!!
FLockCount := 1;
FLockFD := TmpLockFD; // Запоминаем дескриптор файла, чтобы сработал метод ReleaseMutex
RWMode := S_IRUSR or S_IRGRP or S_IROTH or S_IWUSR or S_IWGRP or S_IWOTH;
FpChmod(sLockFile, RWMode); // Настраиваем чтение/запись для всех пользователей
end else
begin
fpClose(TmpLockFD);
end;
end;
{$EndIf MSWINDOWS}
end;
procedure TMutexObject.ReleaseMutex;
{$IfDef UNIX}
var
TmpLockFD: cint;
{$ENDIF}
begin
{$IfDef MSWINDOWS}
Windows.ReleaseMutex(FMutex);
{$Else MSWINDOWS}
if UsePthreadMutex then
CheckOSError(pthread_mutex_unlock(@FMutex))
else
begin
if FLockCount > 0 then
begin
if GetCurrentThreadId <> FLockThreadId then
raise EMutexError.Create('TMutexWrapper: попытка вызова ReleaseMutex из другого потока для занятого мьютекса');
if FLockCount = 1 then
begin
TmpLockFD := FLockFD;
FLockFD := 0;
FLockThreadId := 0;
FLockCount := 0;
fpFlock(TmpLockFD, LOCK_UN);
fpClose(TmpLockFD);
Exit;
end;
Dec(FLockCount);
end;
end;
{$EndIf MSWINDOWS}
end;
end.