Pro vzájemné vyloučení mezi procesy můžete použít zamykání souborů. V linuxu je kód stejně jednoduchý jako ochrana kritické sekce voláním flock
.
int fd_lock = open(LOCK_FILE, O_CREAT);
flock(fd_lock, LOCK_EX);
// do stuff
flock(fd_lock, LOCK_UN);
Pokud potřebujete kompatibilitu s POSIX, můžete použít fcntl
.
Můžete použít pojmenovaný semafor, pokud můžete dosáhnout toho, aby se všechny procesy dohodly na společném názvu.
Pojmenovaný semafor je identifikován jménem ve tvaru
/somename
; to znamená řetězec zakončený nulou o délce až NAME_MAX-4 (tj. 251) znaků sestávající z počátečního lomítka, po němž následuje jeden nebo více znaků, přičemž žádný z nich není lomítko. Dva procesy mohou fungovat na stejném namesemaforu předáním stejného názvu dosem_open(3)
.
Podíval jsem se na použití řešení shared-pthread-mutex, ale nelíbilo se mi v něm logické závody. Napsal jsem tedy třídu, která to udělá pomocí vestavěných atomů
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
using std::string;
//from the command line - "ls /dev/shm" and "lsof /dev/shm/<name>" to see which process ID has access to it
template<typename PAYLOAD>
class InterprocessSharedVariable
{
protected:
int mSharedMemHandle;
string const mSharedMemoryName;
bool mOpenedMemory;
bool mHaveLock;
pid_t mPID;
// this is the shared memory structure
typedef struct
{
pid_t mutex;
PAYLOAD payload;
}
tsSharedPayload;
tsSharedPayload* mSharedData;
bool openSharedMem()
{
mPID = getpid();
// The following caters for the shared mem being created by root but opened by non-root,
// giving the shared-memory 777 permissions.
int openFlags = O_CREAT | O_RDWR;
int shareMode = S_IRWXU | S_IRWXG | S_IRWXO;
// see https://stackoverflow.com/questions/11909505/posix-shared-memory-and-semaphores-permissions-set-incorrectly-by-open-calls
// store old
mode_t old_umask = umask(0);
mSharedMemHandle = shm_open (mSharedMemoryName.c_str(), openFlags, shareMode);
// restore old
umask(old_umask);
if (mSharedMemHandle < 0)
{
std::cerr << "failed to open shared memory" << std::endl;
return false;
}
if (-1 == ftruncate(mSharedMemHandle, sizeof(tsSharedPayload)))
{
std::cerr << "failed to resize shared memory" << std::endl;
return false;
}
mSharedData = (tsSharedPayload*) mmap (NULL,
sizeof(tsSharedPayload),
PROT_READ | PROT_WRITE,
MAP_SHARED,
mSharedMemHandle,
0);
if (MAP_FAILED == mSharedData)
{
std::cerr << "failed to map shared memory" << std::endl;
return false;
}
return true;
}
void closeSharedMem()
{
if (mSharedMemHandle > 0)
{
mSharedMemHandle = 0;
shm_unlink (mSharedMemoryName.c_str());
}
}
public:
InterprocessSharedVariable () = delete;
InterprocessSharedVariable (string const&& sharedMemoryName) : mSharedMemoryName(sharedMemoryName)
{
mSharedMemHandle = 0;
mOpenedMemory = false;
mHaveLock = false;
mPID = 0;
}
virtual ~InterprocessSharedVariable ()
{
releaseSharedVariable ();
closeSharedMem ();
}
// no copying
InterprocessSharedVariable (InterprocessSharedVariable const&) = delete;
InterprocessSharedVariable& operator= (InterprocessSharedVariable const&) = delete;
bool tryLockSharedVariable (pid_t& ownerProcessID)
{
// Double-checked locking. See if a process has already grabbed the mutex. Note the process could be dead
__atomic_load (&mSharedData->mutex, &ownerProcessID, __ATOMIC_SEQ_CST);
if (0 != ownerProcessID)
{
// It is possible that we have started with the same PID as a previous process that terminated abnormally
if (ownerProcessID == mPID)
{
// ... in which case, we already "have ownership"
return (true);
}
// Another process may have the mutex. Check whether it is alive.
// We are specifically looking for an error returned with ESRCH
// Note that if the other process is owned by root, "kill 0" may return a permissions error (which indicates the process is running!)
int processCheckResult = kill (ownerProcessID, 0);
if ((0 == processCheckResult) || (ESRCH != errno))
{
// another process owns the shared memory and is running
return (false);
}
// Here: The other process does not exist ((0 != processCheckResult) && (ESRCH == errno))
// We could assume here that we can now take ownership, but be proper and fall into the compare-exchange
ownerProcessID = 0;
}
// It's possible that another process has snuck in here and taken ownership of the shared memory.
// If that has happened, the exchange will "fail" (and the existing PID is stored in ownerProcessID)
// ownerProcessID == 0 -> representing the "expected" value
mHaveLock = __atomic_compare_exchange_n (&mSharedData->mutex,
&ownerProcessID, //"expected"
mPID, //"desired"
false, //"weak"
__ATOMIC_SEQ_CST, //"success-memorder"
__ATOMIC_SEQ_CST); //"fail-memorder"
return (mHaveLock);
}
bool acquireSharedVariable (bool& failed, pid_t& ownerProcessID)
{
if (!mOpenedMemory)
{
mOpenedMemory = openSharedMem ();
if (!mOpenedMemory)
{
ownerProcessID = 0;
failed = true;
return false;
}
}
// infrastructure is working
failed = false;
bool gotLock = tryLockSharedVariable (ownerProcessID);
return (gotLock);
}
void releaseSharedVariable ()
{
if (mHaveLock)
{
__atomic_store_n (&mSharedData->mutex, 0, __ATOMIC_SEQ_CST);
mHaveLock = false;
}
}
};
Příklad použití – zde jej jednoduše používáme, abychom zajistili, že bude spuštěna pouze jedna instance aplikace.
int main(int argc, char *argv[])
{
typedef struct { } tsEmpty;
InterprocessSharedVariable<tsEmpty> programMutex ("/run-once");
bool memOpenFailed;
pid_t ownerProcessID;
if (!programMutex.acquireSharedVariable (memOpenFailed, ownerProcessID))
{
if (memOpenFailed)
{
std::cerr << "Failed to open shared memory" << std::endl;
}
else
{
std::cerr << "Program already running - process ID " << ownerProcessID << std::endl;
}
return -1;
}
... do stuff ...
return 0;
}
Můžete zajistit, aby mutexy C++ fungovaly přes hranice procesů v Linuxu. Je zde však obsažena určitá černá magie, která jej činí méně vhodným pro produkční kód.
Vysvětlení:
Standardní knihovna je std::mutex
a std::shared_mutex
použijte struct pthread_mutex_s
pthread a pthread_rwlock_t
pod kapotou. native_handle()
metoda vrací ukazatel na jednu z těchto struktur.
Nevýhodou je, že určité detaily jsou abstrahovány ze standardní knihovny a v implementaci jsou výchozí. Například std::shared_mutex
vytvoří základní pthread_rwlock_t
strukturu předáním NULL
jako druhý parametr na pthread_rwlock_init()
. Toto má být ukazatel na pthread_rwlockattr_t
struktura obsahující atribut, který určuje politiku sdílení.
public:
__shared_mutex_pthread()
{
int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
...
Teoreticky by měl obdržet výchozí atributy. Podle manuálových stránek pro pthread_rwlockattr_getpshared()
:
Výchozí hodnota atributu sdíleného procesu je PTHREAD_PROCESS_PRIVATE.
To znamená, že obě std::shared_mutex
a std::mutex
pracovat napříč procesy. Používám Clang 6.0.1 (x86_64-unknown-linux-gnu / model vlákna POSIX). Zde je popis toho, co jsem zkontroloval:
-
Vytvořte oblast sdílené paměti s
shm_open
. -
Zkontrolujte velikost oblasti pomocí
fstat
k určení vlastnictví. Pokud.st_size
je nula, pakftruncate()
on a volající ví, že je to proces vytváření regionu. -
Zavolejte na číslo
mmap
na to.- Proces tvůrce používá umístění -
new
vytvořitstd::mutex
nebostd::shared_mutex
objekt ve sdílené oblasti. - Pozdější procesy používají
reinterpret_cast<>()
získat typizovaný ukazatel na stejný objekt.
- Proces tvůrce používá umístění -
-
Procesy nyní při volání
trylock()
opakují aunlock()
v intervalech. Můžete je vidět, jak se navzájem blokují pomocíprintf()
před a zatrylock()
a předunlock()
.
Další podrobnosti:Zajímalo mě, zda byly chybné hlavičky c++ nebo implementace pthreads, a tak jsem se pustil do pthread_rwlock_arch_t
. Najdete __shared
atribut, který je nula a __flags
atribut, který je také nulový pro pole označené __PTHREAD_RWLOCK_INT_FLAGS_SHARED
. Zdá se tedy, že ve výchozím nastavení není tato struktura určena ke sdílení, i když se zdá, že tuto možnost stejně poskytuje (od července 2019).
Shrnutí
Zdá se, že to funguje, i když trochu náhodou. Doporučil bych opatrnost při psaní produkčního softwaru, který funguje v rozporu s dokumentací.