Как отдать приоритет привилегированной теме при блокировке мьютекса?
Прежде всего: я полностью новичок в мьютексе/многопоточном программировании, поэтому
извините за любую ошибку заранее...
У меня есть программа, которая запускает несколько потоков. Нити (обычно один за
ядро 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;
}
Как вы думаете? Это нормально? Это правда, что семафор может справиться с этим лучше, но мьютексы намного легче справляются со мной.