【问题标题】:How to give priority to privileged thread in mutex locking?如何在互斥锁中优先考虑特权线程?
【发布时间】:2012-07-26 09:43:59
【问题描述】:

首先:我完全是互斥锁/多线程编程的新手,所以 如有任何错误,请提前抱歉...

我有一个运行多个线程的程序。线程(通常每个 cpu核心)做了很多 计算和“思考”,然后有时他们决定调用 更新某些统计信息的特定(共享)方法。 统计更新的并发性是通过使用互斥锁来管理的:

stats_mutex.lock();
common_area->update_thread_stats( ... );
stats_mutex.unlock();

现在解决问题。 在所有这些线程中,有一个特定的线程几乎需要
实时优先级,因为它是唯一实际运行的线程。

我的意思是“几乎实时优先”:

假设线程 t0 是“特权线程”,而 t1....t15 是普通线程 那些。现在发生的是:

  • 线程 t1 获得锁。
  • 线程 t2、t3、t0 调用 lock() 方法并等待它成功。
  • 线程 t1 调用 unlock()
  • 线程 t2、t3、t0 中的一个(据我所知是随机的)成功获取 锁,其他的继续等待。

我需要的是:

  • 线程 t1 获取锁。
  • 线程 t2、t3、t0 调用 lock() 方法并等待它成功。
  • 线程 t1 调用 unlock()
  • 线程 t0 获得锁,因为它具有特权

那么,做这件事最好的(可能是最简单的)方法是什么?

我在想的是有一个 bool 变量叫做 “privileged_needs_lock”。

但我认为我需要另一个互斥锁来管理对这个变量的访问......我不知道 知道这是否是正确的方法...

附加信息:

  • 我的线程使用 C++11(从 gcc 4.6.3 开始)
  • 代码需要同时在 Linux 和 Windows 上运行(但目前仅在 Linux 上测试)。
  • 锁定机制的性能不是问题(我的性能问题是内部线程计算,线程数总是很低,每个 cpu 核心最多一到两个)

任何想法都值得赞赏。 谢谢


以下解决方案有效(三种互斥方式):

#include <thread>
#include <iostream>
#include <mutex>
#include "unistd.h"

std::mutex M;
std::mutex N;
std::mutex L;

void lowpriolock(){
  L.lock();
  N.lock();
  M.lock();
  N.unlock();
}

void lowpriounlock(){
  M.unlock();
  L.unlock();
}

void highpriolock(){
  N.lock();
  M.lock();
  N.unlock();
}

void highpriounlock(){
  M.unlock();
}

void hpt(const char* s){
  using namespace std;
  //cout << "hpt trying to get lock here" << endl;
  highpriolock();
  cout << s << endl;
  sleep(2);
  highpriounlock();
}

void lpt(const char* s){
  using namespace std;
  //cout << "lpt trying to get lock here" << endl;
  lowpriolock();
  cout << s << endl;
  sleep(2);
  lowpriounlock();
}

int main(){
std::thread t0(lpt,"low prio t0 working here");
std::thread t1(lpt,"low prio t1 working here");
std::thread t2(hpt,"high prio t2 working here");
std::thread t3(lpt,"low prio t3 working here");
std::thread t4(lpt,"low prio t4 working here");
std::thread t5(lpt,"low prio t5 working here");
std::thread t6(lpt,"low prio t6 working here");
std::thread t7(lpt,"low prio t7 working here");
//std::cout << "All threads created" << std::endl;
t0.join();
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
t6.join();
t7.join();
return 0;
}

按照建议尝试了以下解决方案,但它不起作用(使用“ g++ -std=c++0x -o test test.cpp -lpthread”编译):

#include <thread>
#include <mutex>

#include "time.h"
#include "pthread.h"

std::mutex l;

void waiter(){
  l.lock();
  printf("Here i am, waiter starts\n");
  sleep(2);
  printf("Here i am, waiter ends\n");
  l.unlock();
}

void privileged(int id){
  usleep(200000);
  l.lock();
  usleep(200000);
  printf("Here i am, privileged (%d)\n",id);
  l.unlock();  
}

void normal(int id){
  usleep(200000);
  l.lock();
  usleep(200000);
  printf("Here i am, normal (%d)\n",id);
  l.unlock();    
}

int main(){
  std::thread tw(waiter);
  std::thread t1(normal,1);
  std::thread t0(privileged,0);
  std::thread t2(normal,2);

  sched_param sch;
  int policy; 

  pthread_getschedparam(t0.native_handle(), &policy, &sch);
  sch.sched_priority = -19;
  pthread_setschedparam(t0.native_handle(), SCHED_FIFO, &sch);

  pthread_getschedparam(t1.native_handle(), &policy, &sch);
  sch.sched_priority = 18;
  pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch);

  pthread_getschedparam(t2.native_handle(), &policy, &sch);
  sch.sched_priority = 18;
  pthread_setschedparam(t2.native_handle(), SCHED_FIFO, &sch);
  
  tw.join();
  t1.join();
  t0.join();
  t2.join();

  return 0;  
}

【问题讨论】:

    标签: c++ multithreading c++11 mutex


    【解决方案1】:

    我能想到三种仅使用线程原语的方法:

    三重互斥

    三个互斥锁可以在这里工作:

    • 数据互斥体 ('M')
    • 下一个访问互斥锁 ('N'),以及
    • 低优先级访问互斥锁 ('L')

    访问模式是:

    • 低优先级线程:lock L, lock N, lock M, unlock N, { do stuff }, unlock M, unlock L
    • 高优先级线程:lock N, lock M, unlock N, { do stuff }, unlock M

    这样可以保护对数据的访问,并且高优先级线程可以在访问数据方面领先于低优先级线程。

    互斥体、条件变量、原子标志

    执行此操作的原始方法是使用条件变量和原子:

    • 互斥体 M;
    • 康德瓦 C;
    • 原子布尔 hpt_waiting;

    数据访问模式:

    • 低优先级线程:lock M,while (hpt_waiting) wait C on M,{ do stuff },广播 C,解锁 M
    • 高优先级线程:hpt_waiting := true, lock M, hpt_waiting := false, { do stuff }, 广播 C, unlock M

    互斥体,条件变量,两个非原子标志

    或者,您可以将两个非原子布尔值与 condvar 一起使用;在这种技术中,mutex/condvar 保护标志,数据不是由互斥体而是由标志保护的:

    • 互斥体 M;

    • 康德瓦尔 C;

    • bool data_held, hpt_waiting;

    • 低优先级线程:lock M, while (hpt_waiting or data_held) wait C on M, data_held := true, unlock M, { do stuff }, lock M, data_held := false, broadcast C, unlock M

    • 高优先级线程:lock M, hpt_waiting := true, while (data_held) wait C on M, data_held := true, unlock M, { do stuff }, lock M, data_held := false, hpt_waiting := false ,广播C,解锁M

    【讨论】:

    • 两种方式(ecatmur 和 Martin James)都可以。 3互斥方式更简单,更容易实现,只需完成它就可以了。优先级队列更加灵活,但在编码时需要更多注意,因此只有在需要特定功能时才会实现它。
    • 在 3 种 ecatmur 方式中,我更喜欢使用第三种方式(更直观),但在我的实现中导致进程冻结(我想是死锁)。我认为实施 condvar 是我的错,但不确定。无论如何,第一个工作就像轻而易举。
    • 为什么不:高优先级:lock(M)-->{do stuff}-->unlock(M) 低优先级:lock(N)-->lock(M)-->{做事}-->unlock(M)-->unlock(N) ?
    • @Dori 如果M 是一个公平(FIFO)互斥体,则有效,否则无效;另一个低优先级任务可以锁定 N 并锁定 M 而无需授予高优先级任务访问权限。
    • 如果有多个高优先级线程,hpt_waiting 不应该是 atomic_int 而不是 atomic_bool
    【解决方案2】:

    将请求线程放在“优先队列”中。特权线程可以在空闲时首先获取数据。

    一种方法是使用 ConcurrentQueues[privilegeLevel] 数组、一个锁和一些事件。

    任何想要获取数据的线程都会进入锁。如果数据是空闲的,(布尔值),它会获取数据对象并退出锁。如果数据正在被另一个线程使用,请求线程会根据其权限级别将事件推送到并发队列中,退出锁并等待事件。

    当一个线程想要释放它对数据对象的所有权时,它会获得锁并从最高特权端向下迭代 ConcurrentQueues 数组,寻找一个事件,(即队列计数>0)。如果找到,则发出信号并退出锁,如果没有,则设置“dataFree”布尔值并退出锁。

    当等待事件以访问数据的线程准备就绪时,它可以访问数据对象。

    我认为这应该可行。请其他开发人员检查此设计,看看您是否可以想到任何种族等?在去 CZ 之后,我仍然有些“热情好客”的困扰。

    编辑 - 可能甚至不需要并发队列,因为它们都被显式锁定。任何旧队列都可以。

    【讨论】:

    • 我喜欢这个解决方案,它非常简单优雅,并且允许未来的扩展(例如不同的优先级)。对不起新手问题:你会使用什么线程信号?条件变量?标准的 unix 信号?
    • @d3k 好吧,在 Windows 上(我更熟悉),AutoRestEvent 可以作为线程类的数据成员,或者实际分配在这个“PrivilegeQueue”类中。您甚至可以将“备用” ARE 存储在另一个队列中,以节省不断进行系统调用来创建它们。在 Linux 上,我想 condvar 或 sema 会做 - 任何可以由一个线程等待并由另一个线程发出信号的东西。
    • @d3k - 你认为 condvar 是正确的做法是正确的。
    • @Flexo - 创建 condvar 是否昂贵?在队列中“汇集”剩余的 condvar 以供以后的线程在它们出现时使用,这是否是一个合理的优化?只是好奇,以防我自己想这样做。
    • @MartinJames - 我只使用一个 condvar 让“低优先级”客户端继续睡觉,你可以要求只唤醒一个。
    【解决方案3】:
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include <cassert>
    
    class priority_mutex {
      std::condition_variable cv_;
      std::mutex gate_;
      bool locked_;
      std::thread::id pr_tid_; // priority thread
    public:
      priority_mutex() : locked_(false) {}
      ~priority_mutex() { assert(!locked_); }
      priority_mutex(priority_mutex&) = delete;
      priority_mutex operator=(priority_mutex&) = delete;
    
      void lock(bool privileged = false) {
        const std::thread::id tid = std::this_thread::get_id();
        std::unique_lock<decltype(gate_)> lk(gate_);
        if (privileged)
          pr_tid_ = tid;
        cv_.wait(lk, [&]{
          return !locked_ && (pr_tid_ == std::thread::id() || pr_tid_ == tid);
        });
        locked_ = true;
      }
    
      void unlock() {
        std::lock_guard<decltype(gate_)> lk(gate_);
        if (pr_tid_ == std::this_thread::get_id())
          pr_tid_ = std::thread::id();
        locked_ = false;
        cv_.notify_all();
      }
    };
    

    注意:这个priority_mutex 提供了不公平的线程调度。如果特权线程频繁获取锁,其他非特权线程可能几乎没有调度。

    使用示例:

    #include <mutex>
    priority_mutex mtx;
    
    void privileged_thread()
    {
      //...
      {
        mtx.lock(true);  // acquire 'priority lock'
        std::unique_lock<decltype(mtx)> lk(mtx, std::adopt_lock);
        // update shared state, etc.
      }
      //...
    }
    
    void normal_thread()
    {
      //...
      {
        std::unique_lock<decltype(mtx)> lk(mtx);  // acquire 'normal lock'
        // do something
      }
      //...
    }
    

    【讨论】:

      【解决方案4】:

      在 linux 上你可以查看这个人:pthread_setschedparam 和 man sched_setscheduler

      pthread_setschedparam(pthread_t 线程,int 策略, const struct sched_pa​​ram *param);

      也检查一下 c++2011: http://msdn.microsoft.com/en-us/library/system.threading.thread.priority.aspx#Y78

      【讨论】:

      • 您能否详细说明设置调度优先级将如何帮助实时线程在共享访问上先行?
      • @RedX 如果不是“同步点后先行”,调度优先级是多少?
      【解决方案5】:

      pthreads 有线程优先级:

      pthread_setschedprio( (pthread_t*)(&mThreadId), wpri );
      

      如果多个线程在一个锁中休眠等待,调度器将首先唤醒最高优先级的线程。

      【讨论】:

      • 您能否引用 POSIX 或 C++ 中的说明?看起来很有趣,但我找不到任何参考。谢谢
      • 自 2008 年以来它就在 pthreads 的 Posix 标准中,在 2018 版中记录在这里:pubs.opengroup.org/onlinepubs/9699919799
      【解决方案6】:

      尝试以下方法。你可以让这个类成为一个线程安全的单例,你甚至可以让它成为一个仿函数。

      #include <pthread.h>
      #include <semaphore.h>
      #include <map>
      
      class ThreadPrioFun
      {
          typedef std::multimap<int, sem_t*> priomap_t;
      public:
          ThreadPrioFun()
          {
              pthread_mutex_init(&mtx, NULL);
          }
          ~ThreadPrioFun()
          {
              pthread_mutex_destroy(&mtx);
          }
          void fun(int prio, sem_t* pSem)
          {
              pthread_mutex_lock(&mtx);
              bool bWait = !(pm.empty());
              priomap_t::iterator it = pm.insert(std::pair<int, sem_t*>(prio, pSem) );
              pthread_mutex_unlock(&mtx);
      
              if( bWait ) sem_wait(pSem);
      
              // do the actual job
              // ....
              //
      
              pthread_mutex_lock(&mtx);
              // done, remove yourself
              pm.erase(it);
              if( ! pm.empty() )
              {
                   // let next guy run:
                  sem_post((pm.begin()->second));
              }
              pthread_mutex_unlock(&mtx);
          }
      private:
          pthread_mutex_t mtx;
          priomap_t pm;
      };
      

      【讨论】:

      • 我明白了你的想法,但我没有设法从你的班级创建一个线程......我的错(缺乏知识......):
      • main(){ ThreadPrioFun p; sem_t* s; std::thread t1(p.fun,1,s); std::thread t2(p.fun,2,s); std::thread t0(p.fun,0,s); std::thread t3(p.fun,3,s); t1.join(); t2.join(); t0.join(); t3.join();
      • 给出:test2.cpp:60:26:错误:没有匹配函数调用'std::thread::thread(,int,sem_t*&)'
      • 成员函数指针不能作为函数指针参数传递。将它包装成一个独立的函数,并将指向 ThreadPrioFun 的指针、prio 整数和指向信号量的指针传递给它,并从内部调用成员函数。尽管上面 ecatmur 发布的具有三个互斥锁的解决方案似乎要好得多。
      • 好的,设法让它工作包装到 myfun(int,sem_t*,ThreadPriofun*) 中,但正如你所写,我更喜欢其他解决方案,可能更易于使用
      【解决方案7】:

      由于线程优先级不适合您:

      创建 2 个互斥锁,一个常规锁和一个优先级锁。

      常规线程必须先锁定普通锁,然后再锁定优先级锁。优先级线程只需锁定优先级锁:

      Mutex mLock;
      Mutex mPriLock;
      
      
      doNormal()
      {
         mLock.lock();
         pthread_yield();
         doPriority();
         mLock.unlock();
      }
      
      doPriority()
      {
         mPriLock.lock();
         doStuff();
         mPriLock.unlock();
      }
      

      【讨论】:

      • 这仍然使您的“优先”线程在每次锁定可用时只有 50-50 的机会获得它。
      • 没有。假设您有 5 个低优先级线程等待 mLock,还有一个低优先级线程在 doStuff 中等待。随之而来的是 hi pri 线程,它锁定了 mPriLock。现在它正在 doStuff 中等待当前线程。该线程完成并解锁 mPriLock - 现在 hi-pri 线程对 mPriLock 的锁定成功。低优先级线程正在等待第二个解锁的 mLock。
      • 不能保证高优先级线程可以在低优先级线程同时获得两个锁之前获得一个锁。想象一下,在一个低优先级进程拥有它并且所有其他进程都在等待的单 CPU 机器上。当前所有线程都被阻塞,除了运行的低优先级。如果时间片足够粗糙(没有理由不这样做),那么当前持有者释放两个锁会立即发生。可运行线程集现在是所有线程,无论哪个线程(高优先级或低优先级)获得下一个 CPU 块,它们获得锁。
      • 仅仅因为一个线程在另一个线程之前处于可运行状态并不意味着它首先运行。您保证高优先级线程是第一个变得可运行的线程,但不能保证它是下一次上下文交换发生时唯一可运行的线程,或者它是第一个取得进展的线程。
      • yield 永远不会解决任何问题。它只不过是一种优化,可能会调整概率但不会强制执行任何规则,或改变任何整体特征。
      【解决方案8】:

      稍微修改了 ecatmur 答案,添加了第 4 个互斥锁以同时处理多个高优先级线程(请注意,这 在我原来的问题中不是必需的):

      #include <thread>
      #include <iostream>
      #include "unistd.h"
      
      std::mutex M; //data access mutex
      std::mutex N; // 'next to access' mutex
      std::mutex L; //low priority access mutex
      std::mutex H; //hptwaiting int access mutex
      
      int hptwaiting=0;
      
      void lowpriolock(){
        L.lock();
        while(hptwaiting>0){
          N.lock();
          N.unlock();
        }
        N.lock();
        M.lock();
        N.unlock();
      }
      
      void lowpriounlock(){
        M.unlock();
        L.unlock();
      }
      
      void highpriolock(){
        H.lock();
        hptwaiting++;
        H.unlock();
        N.lock();
        M.lock();
        N.unlock();
      }
      
      void highpriounlock(){
        M.unlock();
        H.lock();
        hptwaiting--;
        H.unlock();
      }
      
      void hpt(const char* s){
        using namespace std;
        //cout << "hpt trying to get lock here" << endl;
        highpriolock();
        cout << s << endl;
        usleep(30000);
        highpriounlock();
      }
      
      void lpt(const char* s){
        using namespace std;
        //cout << "lpt trying to get lock here" << endl;
        lowpriolock();
        cout << s << endl;
        usleep(30000);
        lowpriounlock();
      }
      
      int main(){
      std::thread t0(lpt,"low  prio t0  working here");
      std::thread t1(lpt,"low  prio t1  working here");
      std::thread t2(hpt,"high prio t2  working here");
      std::thread t3(lpt,"low  prio t3  working here");
      std::thread t4(lpt,"low  prio t4  working here");
      std::thread t5(lpt,"low  prio t5  working here");
      std::thread t6(hpt,"high prio t6  working here");
      std::thread t7(lpt,"low  prio t7  working here");
      std::thread t8(hpt,"high prio t8  working here");
      std::thread t9(lpt,"low  prio t9  working here");
      std::thread t10(lpt,"low  prio t10 working here");
      std::thread t11(lpt,"low  prio t11 working here");
      std::thread t12(hpt,"high prio t12 working here");
      std::thread t13(lpt,"low  prio t13 working here");
      //std::cout << "All threads created" << std::endl;
      t0.join();
      t1.join();
      t2.join();
      t3.join();
      t4.join();
      t5.join();
      t6.join();
      t7.join();
      t8.join();
      t9.join();
      t10.join();
      t11.join();
      t12.join();
      t13.join();
      return 0;
      }
      

      你怎么看?可以吗?确实,信号量可以更好地处理这类事情,但对我来说,互斥锁更容易管理。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-17
        • 1970-01-01
        • 2013-01-31
        • 1970-01-01
        • 2020-12-19
        • 2016-05-19
        • 2017-01-08
        • 1970-01-01
        相关资源
        最近更新 更多