Für den gegenseitigen Ausschluss zwischen Prozessen können Sie Dateisperren verwenden. Unter Linux ist der Code so einfach wie das Schützen des kritischen Abschnitts mit einem Aufruf von flock
.
int fd_lock = open(LOCK_FILE, O_CREAT);
flock(fd_lock, LOCK_EX);
// do stuff
flock(fd_lock, LOCK_UN);
Wenn Sie POSIX-Kompatibilität benötigen, können Sie fcntl
verwenden .
Sie können ein benanntes Semaphor verwenden, wenn Sie alle Prozesse dazu bringen können, sich auf einen gemeinsamen Namen zu einigen.
Eine benannte Semaphore wird durch einen Namen der Form /somename
identifiziert; das heißt, eine nullterminierte Zeichenfolge von bis zuNAME_MAX-4 (d. h. 251) Zeichen, bestehend aus einem anfänglichen Schrägstrich, gefolgt von einem oder mehreren Zeichen, von denen keines Schrägstriche sind. Zwei Prozesse können auf demselben benannten Semaphor operieren, indem sie denselben Namen an sem_open(3)
übergeben .
Ich habe mir die Shared-pthread-Mutex-Lösung angeschaut, aber das logische Rennen darin hat mir nicht gefallen. Also habe ich eine Klasse geschrieben, um dies mit den atomaren Builtins
zu tun#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;
}
}
};
Beispielverwendung - hier verwenden wir es einfach, um sicherzustellen, dass nur eine Instanz der Anwendung ausgeführt wird.
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;
}
Sie können dafür sorgen, dass C++-Mutexe unter Linux über Prozessgrenzen hinweg funktionieren. Es ist jedoch etwas schwarze Magie involviert, was es für Produktionscode weniger geeignet macht.
Erklärung:
std::mutex
der Standardbibliothek und std::shared_mutex
Verwenden Sie struct pthread_mutex_s
von pthread und pthread_rwlock_t
unter der Haube. Die native_handle()
Methode gibt einen Zeiger auf eine dieser Strukturen zurück.
Der Nachteil besteht darin, dass bestimmte Details aus der Standardbibliothek abstrahiert und in der Implementierung vorgegeben werden. Beispiel:std::shared_mutex
erstellt seinen zugrunde liegenden pthread_rwlock_t
Struktur durch Übergabe von NULL
als zweiten Parameter an pthread_rwlock_init()
. Dies soll ein Zeiger auf pthread_rwlockattr_t
sein Struktur, die ein Attribut enthält, das die Freigaberichtlinie bestimmt.
public:
__shared_mutex_pthread()
{
int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
...
Theoretisch sollte es Standardattribute erhalten. Gemäß den Manpages für pthread_rwlockattr_getpshared()
:
Der Standardwert des prozessgemeinsamen Attributs ist PTHREAD_PROCESS_PRIVATE.
Das heißt, beide std::shared_mutex
und std::mutex
sowieso prozessübergreifend arbeiten. Ich verwende Clang 6.0.1 (x86_64-unknown-linux-gnu / POSIX-Thread-Modell). Hier ist eine Beschreibung dessen, was ich getan habe, um Folgendes zu überprüfen:
-
Erstellen Sie eine gemeinsam genutzte Speicherregion mit
shm_open
. -
Überprüfen Sie die Größe der Region mit
fstat
Eigentum zu bestimmen. Wenn.st_size
Null ist, dannftruncate()
es und der Anrufer weiß, dass es der Schöpfungsprozess der Region ist. -
Rufen Sie
mmap
an drauf.- Der Erstellungsprozess verwendet Platzierung -
new
um einenstd::mutex
zu konstruieren oderstd::shared_mutex
Objekt innerhalb der gemeinsam genutzten Region. - Spätere Prozesse verwenden
reinterpret_cast<>()
um einen typisierten Zeiger auf dasselbe Objekt zu erhalten.
- Der Erstellungsprozess verwendet Platzierung -
-
Die Prozesse laufen jetzt beim Aufruf von
trylock()
in einer Schleife undunlock()
in Intervallen. Sie können sehen, wie sie sich gegenseitig mitprintf()
blockieren vor und nachtrylock()
und vorunlock()
.
Zusätzliches Detail:Ich war daran interessiert, ob die c++-Header oder die pthreads-Implementierung schuld waren, also habe ich mich mit pthread_rwlock_arch_t
beschäftigt . Sie finden einen __shared
Attribut, das Null und ein __flags
ist -Attribut, das für das mit __PTHREAD_RWLOCK_INT_FLAGS_SHARED
bezeichnete Feld ebenfalls Null ist . Es scheint also, dass diese Struktur standardmäßig nicht dafür vorgesehen ist, gemeinsam genutzt zu werden, obwohl sie diese Möglichkeit trotzdem zu bieten scheint (Stand:Juli 2019).
Zusammenfassung
Es scheint zu funktionieren, wenn auch eher zufällig. Ich rate zur Vorsicht beim Schreiben von Produktionssoftware, die der Dokumentation zuwiderläuft.