是否可以在Linux上用C ++创建系统范围的全局变量/信号量/互斥量?
原因如下:我有一个系统经常在不相关的数据上运行同一软件的多个副本。通常有4个作业,每个作业运行相同的软件。该软件有一个小部分,它创建一个占用大量内存的巨大图形;在该部分之外,内存使用率适中。
有时会发生两个作业同时遇到同样占用大量内存的部分并且整个系统开始交换。因此,我们希望通过在不同作业之间创建类似关键部分互斥体的东西来防止这种情况,这样一次就不会有多个内存会分配大量内存。
如果这些是相同工作的线程,则pthread锁会完成这项工作。
在不同的工作之间实现这种互斥的好方法是什么?
答案 0 :(得分:7)
如果您可以让所有流程同意公用名,则可以使用named semaphore。
命名信号量由表单名称标识
/somename
;也就是说,一个以null结尾的字符串 NAME_MAX-4(即251)字符由一个首字母组成 斜杠,后跟一个或多个字符,其中没有一个 斜杠。 两个进程可以对同一个命名进行操作 信号量通过将同一名称传递给sem_open(3)
。
答案 1 :(得分:4)
对于进程间互斥,您可以使用文件锁定。使用linux,代码就像通过调用flock
保护关键部分一样简单。
int fd_lock = open(LOCK_FILE, O_CREAT);
flock(fd_lock, LOCK_EX);
// do stuff
flock(fd_lock, LOCK_UN);
如果您需要POSIX兼容性,可以使用fcntl
。
答案 2 :(得分:0)
您可以使C ++互斥锁在Linux上跨进程边界工作。但是,由于涉及到一些黑魔法,因此不太适合生产代码。
标准库的std::mutex
和std::shared_mutex
在后台使用pthread的struct pthread_mutex_s
和pthread_rwlock_t
。 native_handle()
方法返回一个指向这些结构之一的指针。
缺点是某些细节从标准库中提取出来并在实现中默认。例如,std::shared_mutex
通过将pthread_rwlock_t
作为第二个参数传递到NULL
来创建其基础的pthread_rwlock_init()
结构。这应该是指向pthread_rwlockattr_t
结构的指针,该结构包含确定共享策略的属性。
public:
__shared_mutex_pthread()
{
int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
...
理论上,它应该接收默认属性。根据{{1}}的手册页:
进程共享属性的默认值为PTHREAD_PROCESS_PRIVATE。
也就是说,pthread_rwlockattr_getpshared()
和 std::shared_mutex
都可以跨进程工作。我正在使用Clang 6.0.1(x86_64-unknown-linux-gnu / POSIX线程模型)。这是我要检查的内容的说明:
使用std::mutex
创建一个共享内存区域。
使用shm_open
检查区域的大小以确定所有权。如果fstat
为零,则.st_size
为零,并且呼叫者知道这是该区域的创建过程。
在其上致电ftruncate()
。
mmap
在共享区域内构造new
或std::mutex
对象。std::shared_mutex
获取指向同一对象的类型化指针。现在,进程将按间隔调用reinterpret_cast<>()
和trylock()
循环。您可以看到它们在unlock()
之前和之后以及printf()
之前使用trylock()
相互阻塞。
更多细节:我对c ++头文件或pthreads实现是否有问题很感兴趣,因此我研究了unlock()
。对于用pthread_rwlock_arch_t
表示的字段,您会发现一个__shared
属性为零,并且一个__flags
属性也为零。因此,似乎默认情况下不打算共享此结构,尽管它似乎仍然提供此功能(截至2019年7月)。
这似乎可行,尽管有些偶然。在编写与文档相反的生产软件时,我建议您谨慎行事。
答案 3 :(得分:0)
我看过使用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;
}
}
};
用法示例-在这里我们只是使用它来确保仅运行该应用程序的一个实例。
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;
}
答案 4 :(得分:-2)
互斥锁(互斥锁)可防止多个线程同时执行访问共享数据的关键代码段(即,互斥锁用于序列化线程的执行)。所有互斥锁都必须是全局的。通过mutex_lock()
成功调用互斥锁将导致另一个线程也试图锁定相同的互斥锁,直到所有者线程通过mutex_unlock()
解锁它。同一进程内或其他进程内的线程可以共享互斥锁。
互斥锁可以在相同进程或 其他进程 中同步线程。如果互斥锁在可写内存中分配并在协作进程之间共享(请参阅mmap(2)
),并且已针对此任务进行了初始化,则互斥锁可用于在进程之间同步线程。
对于进程间同步,需要在这些进程之间共享的内存中分配互斥锁。由于必须动态分配此类互斥锁的内存,因此需要使用mutex_init()
显式初始化互斥锁。
此外,对于进程间同步,除了要在共享内存中分配的要求外,互斥锁还必须使用属性PTHREAD_PROCESS_SHARED
,否则从其创建者之外的其他进程访问互斥锁会导致未定义的行为(请参阅:{ {3}}):“进程共享属性设置为PTHREAD_PROCESS_SHARED
,以允许任何可以访问分配互斥锁的内存的线程对互斥锁进行操作,即使互斥锁是在由多个进程共享的内存。“