【问题标题】:Semaphore signalling vs mutex信号量信号与互斥量
【发布时间】:2017-08-24 23:30:02
【问题描述】:

我正在查看一些描述互斥量和二进制信号量之间区别的主题。在许多主题中,都指出信号量充当信号量机制,即如果一个线程锁定了一个信号量,那么另一个线程可以解锁(释放)信号量(充当信号)。 但是在互斥锁的情况下,只有锁定互斥锁的线程才能解锁它。它不能被任何其他线程解锁,如果其他线程试图解锁它,这将返回错误。

我尝试编写使用互斥锁进行同步的代码。在代码中,我将互斥锁锁定在一个线程中,并成功解锁了另一个线程中的互斥锁。 有人可以解释这是怎么发生的吗?根据其他线程应该无法解锁互斥锁的答案。

欢迎提出任何建议。

【问题讨论】:

  • 您似乎对互斥体语义有错误的理解;如果您在将互斥锁锁定在 same 线程中之前解锁了互斥锁,那么您的代码就是完全错误的。
  • 感谢 FooF 纠正我。实际上,在将互斥锁锁定在同一个线程之前,我并没有解锁它。但是在一个线程中我锁定了它,而在另一个线程中我正在解锁它。
  • 互斥锁的语义是你锁定它,然后用互斥锁保护的资源做你的事情,然后解锁互斥锁。 (始终保持互斥锁的锁定时间最短——除非您使用几乎不应该使用的递归互斥锁。)
  • 如果您想在线程之间发出信号,您通常使用与互斥锁关联的条件变量。网上有很多例子,或者获取 Butenhof 关于 Pthreads 的优秀书籍。
  • for unlock() ... "互斥锁必须被当前执行线程锁定,否则,行为未定义。"这意味着任何事情都可能发生,包括它甚至可能看起来正在运行。另请注意,我从 mutex::unlock() 内部看到了一些类似断言的报告,但并不像我预期的那样频繁。

标签: linux synchronization pthreads mutex semaphore


【解决方案1】:

典型的 POSIX 线程实现不执行任何检查或验证解锁互斥锁的线程是否与锁定该互斥锁的线程相同(或者甚至可能首先锁定互斥锁)。这不是互斥锁的使用方式。 “互斥”一词意味着互斥;该概念的目的是为多个线程提供一种机制来安全地访问或修改资源(例如数据结构),而无需其他线程干扰,并且在完成修改后,资源在解锁互斥锁之前处于一致状态。

在运行时捕获此类编程错误可能会很好(就像捕获对数组的越界写入一样),但默认情况下提供此类簿记和守卫在以下方面过于昂贵表现;互斥锁和解锁操作应该为您提供最小的开销以使它们可用。这就是您成功从错误线程解锁互斥锁的原因。在标准的 Linux pthread 实现中,您可以使用“错误检查”互斥锁,当您尝试从错误的线程解锁互斥锁时,它会给您带来错误,但同样,这种检查只是为了捕获编程错误。

如果没有更好地了解您的信号需求的性质,我无法以简洁的形式向您提供有关使用什么以及如何实施信号的建议。

【讨论】:

    【解决方案2】:

    欢迎提出任何建议。

    在 std::thread 和 posix 中使用这个 Posix Process Semaphore 封装没有任何问题。

    //
    #include <semaphore.h>  // posix semaphore
    
    class PPLSem_t // Posix Process Semaphore, set to Local mode (unnamed, unshared)
    {
    public: //           not shared-between-process-v  v--initial-value-unlocked
       PPLSem_t()   { int stat = ::sem_init(&m_sem, 0, 1);  assert(0 == stat); } // ctor
       ~PPLSem_t()  { int stat = ::sem_destroy(&m_sem);     assert(0 == stat); } // dtor
    
       int lock()   { return (::sem_wait(&m_sem)); }   // returns 0 when success, else -1
       int unlock() { return (::sem_post(&m_sem)); }   // returns 0 when success, else -1
    private:
       ::sem_t m_sem;
    };
    

    没有限制哪个线程可以发出信号。

    【讨论】:

    • 注:ubuntu 15.10, g++ v5.2.1
    【解决方案3】:

    根据 pthread_mutex_lock 手册页:如果互斥锁类型为 PTHREAD_MUTEX_DEFAULT,如果互斥锁未被调用线程锁定,则尝试解锁互斥锁会导致未定义的行为。如果互斥锁未锁定,则尝试解锁它会导致未定义的行为。

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <string.h>
    #include <errno.h>
    #include <syscall.h>
    
    pthread_mutex_t mutex;
    
    void *thread(void *ptr)
    {
        int ret = pthread_mutex_unlock(&mutex);
    
        if (!ret)
        {
            printf("error unlocking: %s", strerror(errno));
        }
    
        printf("tid: %u - owner: %u\n", syscall(SYS_gettid), mutex.__data.__owner);
    
        return NULL;
    }
    
    int main()
    {
        pthread_t thread1;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
        pthread_mutex_init(&mutex, &attr);
        pthread_mutexattr_destroy(&attr);
    
        pthread_mutex_lock(&mutex);
    
        printf("tid: %u - owner: %u\n", syscall(SYS_gettid), mutex.__data.__owner);
    
        pthread_create(&thread1, NULL, thread, NULL);
    
        pthread_join(thread1, NULL);
    
        return 0;
    }
    

    为我输出:

    tid: 13987 - owner: 13987
    tid: 13992 - owner: 13987
    

    互斥量和二进制信号量之间的唯一区别是所有者概念。 Mutex 有一个所有者,因此您“必须”在同一个线程中锁定和解锁它,但您可以在不同的线程中发布或等待信号量。

    【讨论】:

      猜你喜欢
      • 2012-09-01
      • 2011-01-05
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-10
      • 1970-01-01
      相关资源
      最近更新 更多