プロセス間の相互排除には、ファイル ロックを使用できます。 Linux では、コードは flock
の呼び出しでクリティカル セクションを保護するのと同じくらい簡単です。 .
int fd_lock = open(LOCK_FILE, O_CREAT);
flock(fd_lock, LOCK_EX);
// do stuff
flock(fd_lock, LOCK_UN);
POSIX 互換性が必要な場合は、fcntl
を使用できます .
すべてのプロセスが共通の名前に同意できる場合は、名前付きセマフォを使用できます。
<ブロック引用>
名前付きセマフォは、/somename
の形式の名前で識別されます。;つまり、頭文字のスラッシュとそれに続く 1 つまたは複数の文字で構成される最大 NAME_MAX-4 (つまり、251) 文字のヌル終了文字列で、いずれもスラッシュは使用されません。 sem_open(3)
に同じ名前を渡すことにより、2 つのプロセスが同じ名前付きセマフォで動作できます。 .
私は shared-pthread-mutex ソリューションの使用を検討しましたが、ロジックの競合が気に入らなかったのです。そこで、アトミック組み込み関数を使用してこれを行うクラスを作成しました
#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;
}
}
};
使用例 - ここでは、アプリケーションのインスタンスが 1 つだけ実行されるようにするために使用しています。
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;
}
Linux では、プロセスの境界を越えて C++ ミューテックスを機能させることができます。ただし、いくつかのブラック マジックが関与しているため、プロダクション コードにはあまり適していません。
説明:
標準ライブラリの std::mutex
および std::shared_mutex
pthread の struct pthread_mutex_s
を使用 と pthread_rwlock_t
フードの下。 native_handle()
メソッドは、これらの構造体の 1 つへのポインターを返します。
欠点は、特定の詳細が標準ライブラリから抽象化され、実装でデフォルト設定されることです。例:std::shared_mutex
基礎となる pthread_rwlock_t
を作成します NULL
を渡して構造化 pthread_rwlock_init()
の 2 番目のパラメータとして .これは pthread_rwlockattr_t
へのポインタであるはずです 共有ポリシーを決定する属性を含む構造。
public:
__shared_mutex_pthread()
{
int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
...
理論的には、デフォルトの属性を受け取る必要があります。 pthread_rwlockattr_getpshared()
のマニュアルページによると :
プロセス共有属性のデフォルト値は PTHREAD_PROCESS_PRIVATE です。
とはいえ、どちらも std::shared_mutex
そして std::mutex
とにかくプロセス全体で動作します。 Clang 6.0.1 (x86_64-unknown-linux-gnu / POSIX スレッド モデル) を使用しています。私が確認したことの説明は次のとおりです。
-
shm_open
で共有メモリ領域を作成します . -
fstat
で領域のサイズを確認します 所有権を決定します。.st_size
の場合 ゼロの場合、ftruncate()
それと呼び出し元は、それがリージョンの作成プロセスであることを認識しています。 -
mmap
に電話する- クリエーター プロセスはプレースメントを使用します -
new
std::mutex
を構築する またはstd::shared_mutex
共有領域内のオブジェクト。 - 後のプロセスでは
reinterpret_cast<>()
を使用します 同じオブジェクトへの型指定されたポインターを取得します。
- クリエーター プロセスはプレースメントを使用します -
-
プロセスは
trylock()
の呼び出しでループするようになりました とunlock()
間隔で。printf()
を使用して、それらが互いにブロックしているのを見ることができますtrylock()
の前後 そしてunlock()
の前 .
追加情報:C++ ヘッダーまたは pthreads の実装に問題があるかどうかに興味があったので、pthread_rwlock_arch_t
を掘り下げました。 . __shared
が見つかります ゼロで __flags
の属性 __PTHREAD_RWLOCK_INT_FLAGS_SHARED
で示されるフィールドでもゼロである属性 .したがって、デフォルトでは、この構造は共有されることを意図していないようですが、とにかくこの機能を提供しているようです (2019 年 7 月現在)。
まとめ
多少偶然ですが、うまくいくようです。ドキュメントに反して動作する製品ソフトウェアを作成する際には注意が必要です。