Как отдать приоритет привилегированной теме при блокировке мьютекса?

Прежде всего: я полностью новичок в мьютексе/многопоточном программировании, поэтому извините за любую ошибку заранее...

У меня есть программа, которая запускает несколько потоков. Нити (обычно один за ядро cpu) делают много вычисления и "мышления", а затем иногда они решают позвонить конкретный (общий) метод, который обновляет некоторые статистические данные. concurrency для обновлений статистики управляется с помощью мьютекса:

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

Теперь к проблеме. Из всех этих потоков есть один конкретный поток, который требует почти приоритет в реальном времени, потому что это единственный поток, который фактически работает.

С "почти приоритетом в реальном времени" я имею в виду:

Предположим, что поток t0 является "привилегированным", а t1.... t15 - нормальным Что происходит сейчас:

  • Поток t1 получает блокировку.
  • Потоки t2, t3, t0 вызовите метод lock() и дождитесь его успешного завершения.
  • Тема t1 вызывает unlock()
  • Один (случайно, насколько я знаю) потоки t2, t3, t0 успешно приобретает блокировка и другие продолжают ждать.

Что мне нужно:

  • Thread t1 получает блокировку.
  • Потоки t2, t3, t0 вызовите метод lock() и дождитесь его успешного завершения.
  • Тема t1 вызывает unlock()
  • Тема t0 получает блокировку, поскольку она привилегирована

Итак, какой лучший (возможно, самый простой) способ сделать это?

То, что я думал, это иметь переменную bool, называемую "Privileged_needs_lock".

Но я думаю, мне нужен другой мьютекс, чтобы управлять доступом к этой переменной... Я не знайте, правильно ли это...

Дополнительная информация:

  • мои потоки используют С++ 11 (с gcc 4.6.3)
  • код должен запускаться как на Linux, так и на Windows (но тестируется только на Linux в данный момент).
  • Производительность по механизму блокировки не является проблемой (моя проблема с производительностью связана с вычислениями внутреннего потока, а число потоков всегда будет низким, один или два на процессорное ядро ​​максимум)

Любая идея ценится. Благодаря


Ниже работает решение (три пути mutex):

#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 = С++ 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;  
}

Ответ 1

Я могу думать о трех методах, используя только примитивы с потоками:

Тройной мьютекс

Здесь будут работать три мьютекса:

  • данные mutex ('M')
  • мьютекс next-to-access ('N') и
  • Мьютекс доступа с низким приоритетом ('L')

Шаблоны доступа:

  • Низкоприоритетные потоки: блокировка L, блокировка N, блокировка M, разблокировка N, {do stuff}, разблокировка M, разблокировка L
  • Высокоприоритетный поток: блокировка N, блокировка M, разблокировка N, {do stuff}, разблокировка M

Таким образом, доступ к данным защищен, а высокоприоритетный поток может опережать низкоприоритетные потоки при доступе к нему.

Mutex, переменная условия, атомный флаг

Простейший способ сделать это - с переменной условия и атомом:

  • Mutex M;
  • Кондвар C;
  • atomic bool hpt_waiting;

Шаблоны доступа к данным:

  • Низкоприоритетный поток: блокировка M, while (hpt_waiting) wait C on M, {do stuff}, трансляция C, разблокировать M
  • Высокоприоритетный поток: hpt_waiting: = true, lock M, hpt_waiting: = false, {do stuff}, broadcast C, unlock M

Mutex, переменная условия, два неатомных флага

В качестве альтернативы вы можете использовать два неатомных bools с condvar; в этом методе mutex/condvar защищает флаги, а данные защищены не мьютексом, а флагом:

  • Mutex M;
  • Кондвар C;
  • bool data_held, hpt_waiting;

  • Низкоприоритетный поток: блокировка M, while (hpt_waiting или data_held) wait C on M, data_held: = true, unlock M, {do stuff}, lock M, data_held: = false, broadcast C, unlock М

  • Высокоприоритетный поток: блокировка M, hpt_waiting: = true, while (data_held) wait C on M, data_held: = true, {do stuff}, lock M, data_held: = false, hpt_waiting: = false, broadcast C, разблокировать M

Ответ 2

Поместить запросы на потоки в "очередь приоритетов". Привилегированный поток может получить первый доступ к данным, когда он свободен.

Один из способов сделать это будет с массивом ConcurrentQueues [privilegeLevel], блокировкой и некоторыми событиями.

Любой поток, который хочет, чтобы данные вошли в блокировку. Если данные свободны, (логически), он получает объект данных и выходит из блокировки. Если данные используются другим потоком, запрашивающий поток подталкивает событие к одной из параллельных очередей, в зависимости от его уровня привилегий, выходит из блокировки и ждет события.

Когда поток хочет освободить свою собственность на объект данных, он получает блокировку и выполняет итерацию массива ConcurrentQueues с наивысшей привилегии в конце вниз, ища событие (например, количество очередей > 0). Если он находит один, он сигнализирует об этом и выходит из замка, если нет, он устанавливает логику "dataFree" и выходит из блокировки.

Когда поток, ожидающий события для доступа к данным, готов к работе, он может получить доступ к объекту данных.

I thnk, который должен работать. Пожалуйста, другие разработчики, проверьте этот дизайн и посмотрите, можете ли вы думать о любых гонках и т.д.? Я по-прежнему страдает от "перегрузки гостеприимства" после поездки в ЧР..

Изменить - возможно, даже не нужны параллельные очереди из-за явного блокировки всех них. Любая старая очередь будет делать.

Ответ 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_param * param);

Проверьте это также на С++ 2011: http://msdn.microsoft.com/en-us/library/system.threading.thread.priority.aspx#Y78

Ответ 5

Попробуйте следующее: Вы можете сделать класс потокобезопасным синглтоном, и вы даже можете сделать его функтором.

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

Ответ 6

pthreads имеет приоритеты потоков:

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

Если несколько потоков спадают в ожидании блокировки, планировщик сначала пробудит поток с наивысшим приоритетом.

Ответ 7

Поскольку приоритеты потоков не работают для вас:

Создайте 2 мьютекса, обычный замок и блокировку приоритета.

Обычные потоки должны сначала заблокировать нормальную блокировку, а затем блокировку приоритета. Приоритетный поток должен блокировать блокировку приоритета:

Mutex mLock;
Mutex mPriLock;


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

doPriority()
{
   mPriLock.lock();
   doStuff();
   mPriLock.unlock();
}

Ответ 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;
}

Как вы думаете? Это нормально? Это правда, что семафор может справиться с этим лучше, но мьютексы намного легче справляются со мной.