【问题标题】:System-wide global variable / semaphore / mutex in C++/Linux?C ++ / Linux中的系统范围全局变量/信号量/互斥锁?
【发布时间】:2015-11-27 02:39:01
【问题描述】:

是否可以在 Linux 上的 C++ 中创建系统范围的全局变量/信号量/互斥锁?

原因如下:我的系统经常在不相关的数据上运行同一软件的多个副本。通常有 4 个作业,每个作业都运行相同的软件。该软件有一个小部分,它创建一个占用大量内存的巨大图形;在该部分之外,内存使用量适中。

有时会发生 2 个作业同时遇到同一个需要大量内存的部分并且整个系统开始交换。因此,我们希望通过在不同作业之间创建类似临界区互斥锁之类的东西来防止这种情况发生,这样一次只能分配一个以上的内存。

如果这些是同一个作业的线程,pthread 锁就可以完成这项工作。

在不同作业之间实现这种互斥锁的好方法是什么?

【问题讨论】:

    标签: c++ linux concurrency mutex semaphore


    【解决方案1】:

    如果您可以让所有进程就一个通用名称达成一致,您可以使用named semaphore

    命名信号量由表单的名称标识 /somename;也就是说,一个以空结尾的字符串最多 NAME_MAX-4(即 251)个字符,由一个首字母组成 斜线,后跟一个或多个字符,其中没有一个是 斜线。 两个进程可以在同一个名字上运行 通过将相同的名称传递给sem_open(3).

    【讨论】:

      【解决方案2】:

      互斥锁(互斥锁)可防止多个线程同时执行访问共享数据的关键代码段(即互斥锁用于序列化线程的执行)。所有互斥锁必须是全局的。通过mutex_lock() 成功调用互斥锁将导致另一个试图锁定同一个互斥锁的线程阻塞,直到所有者线程通过mutex_unlock() 解锁它。同一进程内或其他进程内的线程可以共享互斥体。

      互斥锁可以同步同一进程其他进程中的线程。如果互斥锁分配在可写内存中并在协作进程之间共享(参见mmap(2)),并且已为此任务初始化,则互斥锁可用于在进程之间同步线程。

      对于进程间同步,需要在这些进程之间共享的内存中分配互斥锁。由于必须动态分配此类互斥体的内存,因此需要使用mutex_init() 显式初始化互斥体。 此外,对于进程间同步,除了需要在共享内存中分配之外,互斥锁还必须使用属性PTHREAD_PROCESS_SHARED,否则从另一个进程访问互斥锁会导致未定义的行为(参见:linux.die.net/man/3/pthread_mutexattr_setpshared ): "进程共享属性设置为PTHREAD_PROCESS_SHARED 以允许任何有权访问分配互斥锁的内存的线程对互斥锁进行操作,即使互斥锁是在由多个共享的内存中分配的进程。”

      【讨论】:

      【解决方案3】:

      对于进程间互斥,可以使用文件锁定。使用 linux,代码就像通过调用 flock 来保护临界区一样简单。

      int fd_lock = open(LOCK_FILE, O_CREAT);
      
      flock(fd_lock, LOCK_EX);
      
      // do stuff
      
      flock(fd_lock, LOCK_UN);
      

      如果需要 POSIX 兼容性,可以使用fcntl

      【讨论】:

        【解决方案4】:

        您可以使 C++ 互斥锁在 Linux 上跨进程边界工作。但是,其中涉及一些黑魔法,使其不太适合生产代码。

        说明:

        标准库的std::mutexstd::shared_mutex 在底层使用pthread 的struct pthread_mutex_spthread_rwlock_tnative_handle() 方法返回一个指向这些结构之一的指针。

        缺点是某些细节从标准库中抽象出来并在实现中默认。例如,std::shared_mutex 通过将NULL 作为第二个参数传递给pthread_rwlock_init() 来创建其底层pthread_rwlock_t 结构。这应该是指向 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::mutexstd::shared_mutex对象。
          • 后面的进程使用reinterpret_cast<>() 来获取指向同一对象的类型化指针。
        • 进程现在循环调用 trylock()unlock() 间隔。您可以看到它们在 trylock() 前后和 unlock() 之前使用 printf() 相互阻塞。

        额外细节:我对 c++ 头文件或 pthreads 实现是否有问题感兴趣,所以我研究了pthread_rwlock_arch_t。您会发现__shared 属性为零,__flags 属性对于__PTHREAD_RWLOCK_INT_FLAGS_SHARED 表示的字段也为零。因此,默认情况下似乎不打算共享此结构,尽管它似乎无论如何都提供了此功能(截至 2019 年 7 月)。

        总结

        它似乎奏效了,虽然有些偶然。我建议在编写与文档相反的生产软件时要谨慎。

        【讨论】:

          【解决方案5】:

          我研究过使用 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;
          }
          

          【讨论】:

            猜你喜欢
            • 2011-10-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-07
            • 1970-01-01
            • 2023-03-09
            相关资源
            最近更新 更多