【问题标题】:pthread_mutex_lock how to not lock when it is the same threadpthread_mutex_lock 当它是同一个线程时如何不锁定
【发布时间】:2017-02-16 10:05:32
【问题描述】:

我正在使用 pthread_mutex_t 进行锁定。

pthread_mutex_t m_lock;

void get1() {
    cout<<"Start get 1"<<endl;
    pthread_mutex_lock(&m_lock);
    get2();
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 1"<<endl;
}

void get2() {
    cout<<"Start get 2"<<endl;
    pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 2"<<endl;
}

// The thread call to run function
void* run(void* p) {
    get1();
}

假设我只有一个调用 run 函数的线程,所以: get1 锁定 m_lock 并调用 get2,但是当它试图锁定 m_lock 时,它会等待锁被解锁(不会发生的事情)并且我们遇到了死锁。

我的问题是,当锁定 get1 中的锁的同一个线程不需要等待 get2 中的锁定(因为它是同一个线程)时,我该如何避免这种情况?

例如,在 Java 中,当您使用同步时,这种情况永远不会发生。

public Test implements Runnable {
    public void get1() {
        System.out.println("Start get 1");
        synchronized (this) {
            get2();
        }
        System.out.println("End get 1");
    }

    public void get2() {
        System.out.println("Start get 2");
        synchronized (this) {

        }
        System.out.println("End get 2");
    }

    @Override
    public void run() {
        get1();
    }
}

这里没有死锁。

请在我的 C 代码中得到相同的结果。

谢谢。

【问题讨论】:

  • 只要把get2中的锁取出来。只有当您拥有专有权时,您才能达到这一点。我不知道java是做什么的,但是mutex意味着互斥。因此,当您拥有锁时,可以肯定没有其他线程将在其他关键区域上工作。如果中间有部分可以并行运行。您只需在需要时解锁并再次锁定。代码本身永远不会独立于使用的线程数而工作。

标签: c++ c multithreading mutex


【解决方案1】:

正如 Kami Kaze 在 cmets 中所指出的,如果这是您的完整示例,那么这不是问题:只有一条路径通向 get2,并且该路径已经获取了互斥锁;只需省略第二次获取它。

但是,通常情况下,可能会想到不太清楚的场景。在这种情况下,您可以将互斥锁设为recursive/reentrant

在计算机科学中,可重入互斥锁(recursive mutex,递归锁)是一种特殊类型的互斥(mutex)设备,可以被同一个进程/线程多次锁定,而不会导致死锁。

在您的设置中,这将通过pthread_mutexattr_settype

pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);

【讨论】:

    【解决方案2】:

    有了这个:

    pthread_mutex_lock(&m_lock);
    get2();
    pthread_mutex_unlock(&m_lock);
    

    您已锁定整个get2()。因此,在 get2() 函数中再次使用 same 锁是没有意义的。 只需从get2() 中删除锁定和解锁代码即可。

    如果只有get2() 中的代码部分需要加锁,那么从get1() 函数中去掉加锁和解锁。

    例如,在 Java 中,当您使用 同步。

    在您的代码中,同步 区域没有相互关联。因此,对于类似的比较,您需要在get2() 函数中使用不同的互斥体

    【讨论】:

    【解决方案3】:

    这称为锁递归。

    pthread_mutex_init 的最后一个参数是一个属性结构。您可以设置属性以允许使用pthread_mutexattr_settype(..., PTHREAD_MUTEX_RECURSIVE) 进行递归锁定。

    但是,我必须在这里添加一些编辑内容。我非常坚信锁递归几乎总是一个错误。否则会导致程序生命周期后期无法调试错误。

    锁定操作可以被解释为“当锁定函数返回时,受锁定保护的对象处于已知状态,并且在调用解锁函数之前该状态不会改变”。这意味着如果get1 已开始修改您使用锁保护的对象,然后get2 递归该锁,则此合约被破坏两次。首先是因为get2 在对象不处于已知状态时成功获取了锁,其次是因为在get1 认为自己拥有锁的情况下修改了对象。

    当然,我们经常会做这样的事情而侥幸,但这是一种糟糕的做法。重新设计您的程序以不使用递归锁。执行此操作的标准方法是实现一个名为get2_locked 的函数,get2 获得锁并调用get2_locked,而get1 已经知道它拥有锁并会调用get2_locked

    【讨论】:

      【解决方案4】:

      我假设get1 真的不仅仅是获取锁并调用get2?不然get1有什么意义?

      如果是这种情况,您可以通过使用 get3 函数来解决它,该函数执行 get2 的主要部分(您未在此处显示的部分)并且不锁定。然后从get1 调用该新函数(当然也从get 调用):

      void get1()
      {
          // Do something here
      
          cout<<"Start get 1"<<endl;
          pthread_mutex_lock(&m_lock);
          get3();  // <-- Note call get3 instead here
          pthread_mutex_unlock(&m_lock);
          cout<<"End get 1"<<endl;
      
          // Do something more here
      }
      
      void get2()
      {
          cout<<"Start get 2"<<endl;
          pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
          get3();  // <-- Note call to get3 here
          pthread_mutex_unlock(&m_lock);
          cout<<"End get 2"<<endl;
      }
      
      void get3()
      {
          // Do the actual work of get2 here...
          // Note: No locking here
      }
      

      【讨论】:

        猜你喜欢
        • 2021-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-08
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多