【问题标题】:How to allow certain threads to have priority in locking a mutex use PTHREADS如何允许某些线程在锁定互斥锁时具有优先权使用 PTHREADS
【发布时间】:2011-03-17 21:07:19
【问题描述】:

假设以下代码由 10 个线程执行。

pthread_mutex_lock(&lock)
Some trivial code
pthread_mutex_unlock(&lock)

为了便于解释,假设线程是 T1、T2、T3.....T10。 我的要求是,只要 T1 或 T2 或 T3(即 T1、T2 或 T3 中的任何一个)正在等待获取锁,它 T4、T5、T6 .....T10 的其他线程应该无法获取锁,即 T1、T2 和 T3 应该优先于其他线程获取锁。

我想这可以通过增加线程T1、T2和T3的优先级来完成

这里是伪代码

if this thread is T1 or T2 or T3
increase its priority 
pthread_mutex_lock(&lock)
Some trivial code
pthread_mutex_unlock(&lock)
if this thread is T1 or T2 or T3 decrease it priority to normal

请注意,我想要一个适用于 Linux 平台并且应该使用 pthreads 的解决方案。我真的不关心任何其他平台。

另外请注意,我真的不想让这 3 个线程成为实时线程,我希望它们展示它们的默认行为(调度和优先级),除了在上面提到的一小段代码中我希望它们始终具有优先权在获取锁。

我已经阅读了一些关于 Linux 中调度策略和调度优先级的手册页,但无法真正理解:(

这行得通吗?您能帮我了解完成上述任务所需的确切 pthread API 吗?

问候 拉里

【问题讨论】:

  • 考虑添加标签“c”,因为只有少数开发人员会查看没有编程语言标签的问题。
  • 非常感谢,是的,它类似于读写锁的实现。谢谢大家:)

标签: c linux multithreading pthreads


【解决方案1】:

这是我的实现。低优先级线程使用prio_lock_low()prio_unlock_low()进行锁定和解锁,高优先级线程使用prio_lock_high()prio_unlock_high()

设计很简单。高优先级线程保存在临界区互斥体->cs_mutex,低优先级线程保存在条件变量中。条件变量 mutex 仅在对共享变量的更新和条件变量的信号传递时保持。

#include <pthread.h>

typedef struct prio_lock {
    pthread_cond_t cond;
    pthread_mutex_t cv_mutex; /* Condition variable mutex */
    pthread_mutex_t cs_mutex; /* Critical section mutex */
    unsigned long high_waiters;
} prio_lock_t;

#define PRIO_LOCK_INITIALIZER { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }

void prio_lock_low(prio_lock_t *prio_lock)
{
    pthread_mutex_lock(&prio_lock->cv_mutex);
    while (prio_lock->high_waiters || pthread_mutex_trylock(&prio_lock->cs_mutex))
    {
        pthread_cond_wait(&prio_lock->cond, &prio_lock->cv_mutex);
    }
    pthread_mutex_unlock(&prio_lock->cv_mutex);
}

void prio_unlock_low(prio_lock_t *prio_lock)
{
    pthread_mutex_unlock(&prio_lock->cs_mutex);

    pthread_mutex_lock(&prio_lock->cv_mutex);
    if (!prio_lock->high_waiters)
        pthread_cond_signal(&prio_lock->cond);
    pthread_mutex_unlock(&prio_lock->cv_mutex);
}

void prio_lock_high(prio_lock_t *prio_lock)
{
    pthread_mutex_lock(&prio_lock->cv_mutex);
    prio_lock->high_waiters++;
    pthread_mutex_unlock(&prio_lock->cv_mutex);

    pthread_mutex_lock(&prio_lock->cs_mutex);
}

void prio_unlock_high(prio_lock_t *prio_lock)
{
    pthread_mutex_unlock(&prio_lock->cs_mutex);

    pthread_mutex_lock(&prio_lock->cv_mutex);
    prio_lock->high_waiters--;
    if (!prio_lock->high_waiters)
        pthread_cond_signal(&prio_lock->cond);
    pthread_mutex_unlock(&prio_lock->cv_mutex);
}

【讨论】:

  • 这应该可行,但与使用信号量实现的相同逻辑相比非常冗长
  • @ULysses:我想,但至少它只使用 pthreads 原语并且它会隐藏在一个实现模块中 - 使用它的代码将简单地在其关键部分周围执行prio_lock_*(&amp;lock); /* ... */ prio_unlock_*(&amp;lock);。跨度>
  • 如果遵循while (prio_lock-&gt;high_waiters 路径,则此处存在错误,您不会锁定cs_mutex,但您会解锁它。我几乎可以肯定这很糟糕。
  • @xryl669:要完成while() 循环,|| 必须计算为0,这意味着必须调用pthread_mutex_trylock() 并返回0 - 即互斥锁已被锁定。
  • high_waiters 为0,在这种情况下,函数返回好像锁定已被获取,但事实并非如此。稍后,用户调用unlock,代码调用pthread_mutex_unlock 对未锁定的互斥体。
【解决方案2】:

据我了解,您真正能保证这一点的唯一方法是自己编写一个这样工作的锁。但是 @xryl669's answer 建议使用线程优先级和优先级继承,如果它适用于您的用例,当然值得考虑。

要自己实现它,您需要条件变量和等待低/高优先级线程数的计数。

就您需要的概念和 API 而言,它与实现读/写锁相对相似(但显然,您需要的语义完全不同 - 但如果您了解 r/w 锁的工作原理,你就会明白如何实现你想要的)。

您可以在此处查看读写锁的实现:

http://ptgmedia.pearsoncmg.com/images/0201633922/sourcecode/rwlock.c

在较低优先级的线程中,您需要等待高优先级的线程完成,就像读取器等待写入器完成一样。

(上面的代码取自这本书,顺便说一句,这本书也是一本很棒的 posix 线程书,http://www.informit.com/store/product.aspx?isbn=0201633922

【讨论】:

    【解决方案3】:

    本机方法是为您的互斥锁启用优先级继承(使用 pthread_mutex_attr),并使用 pthread 的线程优先级来执行您需要的操作。 它只需要很少的代码行,而且您无需重新发明轮子。 从好的方面来说,它也可以与 RT 或 FIFO 调度程序一起使用,而您的自制版本则不能。

    然后,每当具有高优先级的线程等待由低优先级线程获取的互斥锁时,内核“提升”低优先级线程,以便可以调度它来代替高优先级线程,从而给它释放锁的时间片。一旦锁被释放,高优先级的线程就会被调度。这是自从它在内核中完成以来您可以获得的最低延迟。

    【讨论】:

      【解决方案4】:

      或者,您可以只为更高优先级的线程引入另一个锁。考虑以下伪代码(我不熟悉 pthread 语义,但我相信将代码映射到所需的调用并不难)

      编辑(感谢 JosephH)

      引入 exec 信号量设置为 3(高优先级线程数) 请注意,pend(exec,3); 表示此挂起将休眠,直到所有 3 个插槽都可用并将它们全部消耗完

      
      
      //init
      exec = semaphore(3,3);
      
      //========================
      
      if this is NOT thread (t1,t2,t3)
          lock(low_prio);
          sem_pend(exec,3);
      else
          sem_pend(exec,1);
      lock(high_prio);
      //...
      unlock(high_prio);
      if this is NOT thread (t1,t2,t3)
          sem_release(exec,3);
          sleep(0); //yield();  //ensures that sem_pend(exec,1) is executed
          unlock(low_prio);
      else
          sem_release(exec,1);
      

      【讨论】:

      • 抱歉,我真的不明白它是如何工作的。如果 t1 和 t4 都尝试在 t2 拥有锁的情况下获取锁,则 t1 和 t4 最终都会在锁处等待(high_prio),并且不能保证哪个会先唤醒。
      • 是的,你说得对,我错过了。不过,我相信有办法,考虑编辑..
      • 我认为这也行不通;两个锁方法可以工作,但你必须让“高优先级”线程有机会超越“低优先级”线程(因此它应该作为两步锁实现)。
      • 我不认为使用 sleep() 是个好主意;它可能不能保证做你想做的事。也相当挑剔, semaphore() 不是 pthreads 的一部分,原始海报明确表示他想要一个 pthread 解决方案。 (我还没有时间充分考虑您的解决方案。)
      • @JosephH,yield 或 sleep 是一种预防措施。但我同意pthreads 中没有sem_wait() 实现,它采用另一个参数来告知要获取多少个插槽。这样我的回答就不是很有用了。
      【解决方案5】:

      (前两次尝试有错误,请跳转到EDIT2)

      也许这行得通?

      if NOT this thread is T1 or T2 or T3
          pthread_mutex_lock(&lock1) // see note below
          pthread_mutex_lock(&lock2)
          Some trivial code
          pthread_mutex_unlock(&lock2)
          pthread_mutex_unlock(&lock1)
      else
          pthread_mutex_lock(&lock2)
          Some trivial code
          pthread_mutex_unlock(&lock2)        
      end if
      

      推理: 一些线程将竞争两个锁,因此具有较低的优先级,而一些线程将只竞争一个锁,因此具有较高的优先级。 差异可能仍然很小,然后解决方案是在获取第一个锁和为更高优先级线程尝试第二个锁之间引入一些延迟,在此期间,更高优先级的线程将有机会获得 lock2。
      (免责声明:在这方面我是新手)

      编辑: 另一种尝试/方法

      if NOT (this thread is T1 or T2 or T3)  
          pthread_mutex_lock(&lock1)
          if pthread_mutex_trylock(&lock2) == 0  // low priority threads will not get queued
              Some trivial code
              pthread_mutex_unlock(&lock2)
          end if
          pthread_mutex_unlock(&lock1)
      else 
          if (this thread is T1 or T2 or T3)
              pthread_mutex_lock(&lock2)
              Some trivial code
              pthread_mutex_unlock(&lock2)        
          end if
      end if
      

      EDIT2:另一个尝试(试图在这里学习一些东西)

      if NOT (this thread is T1 or T2 or T3)  
          pthread_mutex_lock(&lock1)
          while !(pthread_mutex_trylock(&lock2) == 0)
              pthread_yield()
          Some trivial code
          pthread_mutex_unlock(&lock2)
          pthread_mutex_unlock(&lock1)
      else 
          if (this thread is T1 or T2 or T3)
              pthread_mutex_lock(&lock2)
              Some trivial code
              pthread_mutex_unlock(&lock2)        
          end if
      end if
      

      【讨论】:

      • 我怀疑你想测试一个线程 is NOT t1 or t2 or t3 是否是低优先级。这种方法有一个我的第一个版本存在的缺陷:如果 hp 线程正在执行,那么可能还有另外两个 hp 和 lp 线程在 lock2 上等待,你无法判断哪个会先获取它
      • @ULysses,是的,不是有意的(已编辑)。至于等待lock2的几个线程,我想你是对的。我提到引入滞后,但似乎不足以保证优先级,如果我理解正确,你的信号量方法将保证它。我会再做一次编辑...
      • 现在你拥有的是一个 lp 线程,但万一 hp 线程正在执行
      • @Ulysses,while !(pthread_mutex_lock(&amp;lock1) AND pthread_mutex_try(&amp;lock2)) 呢?有点锁定低优先级并尝试锁定高优先级,如果失败再做一次。
      • 不,这样你在循环的第二次迭代中遇到了 lock1 的问题:你要么死锁,要么总是出错,要么增加锁计数,或者更糟糕的是,有一个未定义的行为。见opengroup.org/onlinepubs/007908775/xsh/pthread_mutex_lock.html
      【解决方案6】:

      要使用 pthread 实现这一点,您需要 N 个列表,每个线程优先级一个。列表将包含指向线程的 pthread_cond_t 变量的指针。

      未经测试的示意图元代码:

      /* the main lock */
      pthread_mutex_t TheLock = PTHREAD_MUTEX_INITIALIZER;
      
      /* service structures: prio lists and the lock for them */
      pthread_mutex_t prio_list_guard = PTHREAD_MUTEX_INITIALIZER;
      pthread_cond_t *prio_lists[MY_MAX_PRIO][MY_MAX_THREAD]; /* 0 == highest prio */
      
      /* lock */
      void
      prio_lock(int myprio)
      {
          pthread_cond_t x;
      
          pthread_mutex_lock( &prio_list_guard );
      
          if (0 == pthread_mutex_trylock( &TheLock )) {
              pthread_mutex_unlock( &prio_list_guard );
              return 0;
          }
      
          pthread_cond_init( &x, 0 );
          LIST_ADD( prio_lists[myprio], &x )
      
          while(1)    /* handle spurious wake-ups */
          {
              pthread_cond_wait( &prio_list_guard, &x );
              if (0 == pthread_mutex_trylock( &TheLock )) 
              {
                  LIST_REMOVE( prio_lists[myprio], &x );
                  pthread_mutex_unlock( &prio_list_guard );
                  return 0;
              }
          }
      }
      
      /* unlock */
      void
      prio_unlock()
      {
          int i;
          pthread_cond_t *p;
      
          pthread_mutex_lock( &prio_list_guard );
      
          for (i=0; i<MY_MAX_PRIO; i++)
          {
              if ((p = LIST_GETFIRST( prio_lists[i] )))
              {
                  pthread_cond_signal( p );
                  break;
              }
          }
      
          pthread_mutex_unlock( &TheLock );
      
          pthread_mutex_unlock( &prio_list_guard );
      }
      

      该代码还处理来自pthread_cond_wait() 的虚假唤醒,但坦率地说,我从未见过这种情况发生。

      编辑1。请注意,上面的prio_listspriority queue 的原始形式。

      【讨论】:

      • 元代码不应该意味着脏代码。在prio_lock()你锁定prio_list_guard两次,在prio_unlock()你应该先解锁TheLock,然后给列表发信号,否则你总是会有一个'虚假'在一个循环中唤醒
      • @ULysses:已修复。到“第一次解锁 TheLock” - 你可以做到,但这样它是 IMO 更清洁。这是一个品味问题。 man pthread_cond_wait 用于条件互斥锁的隐含锁定。只有在解锁线程释放 prio_list_guard 锁后,锁定线程才会唤醒。
      • @Dummy,好的,没有使用 pthread 库的经验。这段代码现在看起来不错。
      • @ULysses:感谢您的评论;)这与我在生产代码中所做的非常接近(并重用了其中的想法)。
      • 没有必要为每个进程使用一个条件变量——每个优先级一个就足够了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-07
      • 2014-10-19
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      相关资源
      最近更新 更多