Untuk pengecualian timbal balik antarproses, Anda dapat menggunakan penguncian file. Dengan linux, kodenya sesederhana melindungi bagian penting dengan panggilan ke flock
.
int fd_lock = open(LOCK_FILE, O_CREAT);
flock(fd_lock, LOCK_EX);
// do stuff
flock(fd_lock, LOCK_UN);
Jika Anda memerlukan kompatibilitas POSIX, Anda dapat menggunakan fcntl
.
Anda dapat menggunakan semaphore bernama jika Anda bisa membuat semua proses menyetujui nama umum.
Semaphore bernama diidentifikasi dengan nama form
/somename
; yaitu, string yang diakhiri null hingga NAME_MAX-4 (yaitu, 251) karakter yang terdiri dari garis miring awal, diikuti oleh satu atau beberapa karakter, tidak ada yang berupa garis miring. Dua proses dapat beroperasi pada semafor bernama sama dengan meneruskan nama yang sama kesem_open(3)
.
Saya melihat menggunakan solusi shared-pthread-mutex tetapi tidak menyukai perlombaan logika di dalamnya. Jadi saya menulis kelas untuk melakukan ini menggunakan atom builtin
#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;
}
}
};
Contoh penggunaan - di sini kami hanya menggunakannya untuk memastikan bahwa hanya satu contoh aplikasi yang berjalan.
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;
}
Anda dapat membuat mutex C++ berfungsi melintasi batas proses di Linux. Namun, ada beberapa sihir hitam yang membuatnya kurang sesuai untuk kode produksi.
Penjelasan:
std::mutex
pustaka standar dan std::shared_mutex
gunakan struct pthread_mutex_s
pthread dan pthread_rwlock_t
Dibawah tenda. native_handle()
metode mengembalikan pointer ke salah satu struktur ini.
Kelemahannya adalah detail tertentu diabstraksikan dari pustaka standar dan gagal dalam implementasinya. Misalnya, std::shared_mutex
membuat pthread_rwlock_t
yang mendasarinya struktur dengan meneruskan NULL
sebagai parameter kedua untuk pthread_rwlock_init()
. Ini seharusnya menjadi penunjuk ke pthread_rwlockattr_t
struktur yang berisi atribut yang menentukan kebijakan berbagi.
public:
__shared_mutex_pthread()
{
int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
...
Secara teori, itu harus menerima atribut default. Menurut halaman manual untuk pthread_rwlockattr_getpshared()
:
Nilai default atribut proses bersama adalah PTHREAD_PROCESS_PRIVATE.
Konon, keduanya std::shared_mutex
dan std::mutex
tetap bekerja di seluruh proses. Saya menggunakan Clang 6.0.1 (model utas x86_64-unknown-linux-gnu / POSIX). Berikut deskripsi tentang apa yang saya lakukan untuk memeriksa:
-
Buat wilayah memori bersama dengan
shm_open
. -
Periksa ukuran wilayah dengan
fstat
untuk menentukan kepemilikan. Jika.st_size
adalah nol, makaftruncate()
itu dan penelepon tahu bahwa itu adalah proses pembuatan wilayah. -
Hubungi
mmap
di atasnya.- Proses pembuat menggunakan penempatan -
new
untuk membuatstd::mutex
ataustd::shared_mutex
objek dalam wilayah bersama. - Proses selanjutnya menggunakan
reinterpret_cast<>()
untuk mendapatkan penunjuk yang diketik ke objek yang sama.
- Proses pembuat menggunakan penempatan -
-
Proses sekarang berulang kali memanggil
trylock()
danunlock()
pada interval. Anda dapat melihat mereka memblokir satu sama lain menggunakanprintf()
sebelum dan sesudahtrylock()
dan sebelumunlock()
.
Detail ekstra:Saya tertarik pada apakah header c++ atau implementasi pthreads salah, jadi saya menggali pthread_rwlock_arch_t
. Anda akan menemukan __shared
atribut yang nol dan __flags
atribut yang juga nol untuk bidang yang dilambangkan dengan __PTHREAD_RWLOCK_INT_FLAGS_SHARED
. Jadi, tampaknya secara default struktur ini tidak dimaksudkan untuk dibagikan, meskipun tampaknya tetap menyediakan fasilitas ini (per Juli 2019).
Ringkasan
Tampaknya berhasil, meskipun agak kebetulan. Saya akan menyarankan agar berhati-hati dalam menulis perangkat lunak produksi yang bekerja bertentangan dengan dokumentasi.