web-dev-qa-db-ja.com

ミューテックスロックで特権スレッドを優先する方法は?

まず第一に:私はミューテックス/マルチスレッドプログラミングの完全な初心者なので、事前にエラーをお詫びします...

複数のスレッドを実行するプログラムがあります。スレッド(通常はCPUコアごとに1つ)は多くの計算と「思考」を行い、統計を更新する特定の(共有)メソッドを呼び出すことを決定する場合があります。統計更新の同時実行性は、ミューテックスを使用して管理されます。

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

今問題に。それらすべてのスレッドの中で、ほとんどを必要とする1つの特定のスレッドがあります
リアルタイムの優先度。これは、実際に動作する唯一のスレッドだからです。

「ほぼリアルタイムの優先順位」とは、次のことを意味します。

スレッドt0が「特権スレッド」であり、t1 .... t15が通常のスレッドであると仮定します。

  • スレッドt1がロックを取得します。
  • スレッドt2、t3、t0はlock()メソッドを呼び出し、それが成功するのを待ちます。
  • スレッドt1はunlock()を呼び出します
  • スレッドt2、t3、t0の1つ(私が知る限りランダムに)はロックの取得に成功し、他のスレッドは待機を続けます。

私が必要なのは:

  • スレッドt1がロックを取得します。
  • スレッドt2、t3、t0はlock()メソッドを呼び出し、成功するのを待ちます。
  • スレッドt1はunlock()を呼び出します
  • スレッドt0は特権があるため、ロックを取得します

それで、これを行うための最良の(おそらく最も簡単な)方法は何ですか?

私が考えていたのは、「privileged_needs_lock」というブール変数を持つことです。

しかし、この変数へのアクセスを管理するには、別のミューテックスが必要だと思います...これが正しい方法かどうかはわかりません...

追加情報:

  • 私のスレッドはC++ 11を使用しています(gcc 4.6.3以降)
  • コードはLinuxとWindowsの両方で実行する必要があります(ただし、現時点ではLinuxでのみテストされています)。
  • ロックメカニズムのパフォーマンスは問題ではありません(私のパフォーマンスの問題は内部スレッドの計算にあり、スレッド数は常に少なく、CPUコアごとに最大で1つまたは2つです)

どんなアイデアでも大歓迎です。ありがとう


以下の解決策は機能します(3つのミューテックスの方法):

#include <thread>
#include <iostream>
#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;  
}
24
d3k

スレッドプリミティブのみを使用する3つの方法を考えることができます。

トリプルミューテックス

ここでは、3つのミューテックスが機能します。

  • データミューテックス( 'M')
  • 次にアクセスするミューテックス(「N」)、および
  • 低優先度アクセスミューテックス(「L」)

アクセスパターンは次のとおりです。

  • 優先度の低いスレッド:ロックL、ロックN、ロックM、ロック解除N、{do stuff}、ロック解除M、ロック解除L
  • 優先度の高いスレッド:ロックN、ロックM、ロック解除N、{作業}、ロック解除M

これにより、データへのアクセスが保護され、優先度の高いスレッドが優先度の低いスレッドよりも先にアクセスできるようになります。

ミューテックス、条件変数、アトミックフラグ

これを行うための基本的な方法は、条件変数とアトミックを使用することです。

  • ミューテックスM;
  • Condvar C;
  • アトミックブールhpt_waiting;

データアクセスパターン:

  • 優先度の低いスレッド:Mをロックし、(hpt_waiting)CをMで待機し、{do stuff}、Cをブロードキャストし、Mのロックを解除します
  • 優先度の高いスレッド:hpt_waiting:= true、ロックM、hpt_waiting:= false、{do stuff}、ブロードキャストC、ロック解除M

ミューテックス、条件変数、2つの非アトミックフラグ

または、condvarで2つの非アトミックboolを使用することもできます。この手法では、ミューテックス/ condvarがフラグを保護し、データはミューテックスではなくフラグによって保護されます。

  • ミューテックスM;
  • Condvar C;
  • bool data_held、hpt_waiting;

  • 優先度の低いスレッド:Mをロックし、(hpt_waitingまたはdata_held)がMでCを待機している間、data_held:= true、Mのロックを解除、{do stuff}、Mのロック、data_held:= false、Cのブロードキャスト、Mのロック解除

  • 優先度の高いスレッド:ロックM、hpt_waiting:= true、(data_held)待機C on M、data_held:= true、{do stuff}、ロックM、data_held:= false、hpt_waiting:= false、ブロードキャストC、ロック解除M
37
ecatmur

要求スレッドを「優先キュー」に配置します。特権スレッドは、データが空いているときに最初にデータを取得できます。

これを行う1つの方法は、ConcurrentQueues [privilegeLevel]の配列、ロック、およびいくつかのイベントを使用することです。

データを必要とするスレッドはすべてロックに入ります。データが空いている場合(ブール値)、データオブジェクトを取得し、ロックを終了します。データが別のスレッドによって使用されている場合、要求元のスレッドは、特権レベルに応じて、イベントを同時キューの1つにプッシュし、ロックを終了してイベントを待機します。

スレッドがデータオブジェクトの所有権を解放したい場合、スレッドはロックを取得し、ConcurrentQueuesの配列を最高特権の端から下に繰り返し、イベントを探します(つまり、キュー数> 0)。見つかった場合は、それを通知してロックを終了します。見つからなかった場合は、「dataFree」ブール値を設定してロックを終了します。

データへのアクセスをイベントで待機しているスレッドの準備が整うと、データオブジェクトにアクセスする場合があります。

私はそれがうまくいくはずだと思います。他の開発者の皆さん、このデザインをチェックして、レースなどを思いつくことができるかどうかを確認してください。 CZへの旅行の後、私はまだ「おもてなしの過負荷」にいくらか苦しんでいます。

編集-それらすべてに明示的なロックがあるため、おそらく並行キューさえ必要ありません。古いキューなら何でもかまいません。

7
Martin James
#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
  }
  //...
}
3
yohjp

Linuxでは、次の男を確認できます:pthread_setschedparamおよびman sched_setscheduler

pthread_setschedparam(pthread_t thread、int policy、const struct sched_pa​​ram * param);

C++ 2011についてもこれを確認してください: http://msdn.Microsoft.com/en-us/library/system.threading.thread.priority.aspx#Y78

2
Sam

次のようなものを試してください。クラスをスレッドセーフなシングルトンにすることも、ファンクターにすることもできます。

#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;
};

pthreadsにはスレッドの優先順位があります:

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

複数のスレッドがロックで待機している場合、スケジューラーは最も優先度の高いスレッドを最初にウェイクアップします。

1
Rafael Baptista

スレッドの優先順位が機能していないため:

通常ロックと優先ロックの2つのミューテックスを作成します。

通常のスレッドは、最初に通常のロックをロックし、次に優先ロックをロックする必要があります。優先スレッドは、優先ロックをロックするだけで済みます。

Mutex mLock;
Mutex mPriLock;


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

doPriority()
{
   mPriLock.lock();
   doStuff();
   mPriLock.unlock();
}
0
Rafael Baptista

わずかに変更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;
}

どう思いますか?大丈夫ですか?セマフォがこの種のことをよりうまく処理できることは事実ですが、ミューテックスは私にとってはるかに管理しやすいです。

0
d3k