lock_guard - mutex c++
¿Cómo dar prioridad al subproceso privilegiado en el bloqueo mutex? (8)
En Linux puede consultar este hombre: pthread_setschedparam y también man sched_setscheduler
pthread_setschedparam (pthread_t thread, int policy, const struct sched_param * param);
Verifique esto también para c ++ 2011: http://msdn.microsoft.com/en-us/library/system.threading.thread.priority.aspx#Y78
En primer lugar: soy completamente un novato en la programación mutex / multiproceso, por lo que lamento cualquier error de antemano ...
Tengo un programa que ejecuta varios hilos. Los hilos (generalmente uno por núcleo de cpu) realizan muchos cálculos y "pensamientos" y, a veces, deciden llamar a un método particular (compartido) que actualiza algunas estadísticas. La concurrencia en las actualizaciones de estadísticas se administra mediante el uso de un mutex:
stats_mutex.lock();
common_area->update_thread_stats( ... );
stats_mutex.unlock();
Ahora al problema. De todos esos hilos hay un hilo en particular que necesita casi
Prioridad en tiempo real, porque es el único hilo que realmente opera.
Con "prioridad casi en tiempo real" quiero decir:
Supongamos que el hilo t0 es el "privilegiado" y t1 .... t15 son los normales. Lo que sucede ahora es:
- El hilo t1 adquiere bloqueo.
- Thread t2, t3, t0 llama al método lock () y espera a que tenga éxito.
- Tema t1 llamadas desbloquear ()
- Uno (al azar, que yo sepa) de los hilos t2, t3, t0 logra adquirir el bloqueo, y los otros continúan esperando.
Lo que necesito es:
- El hilo t1 adquiere bloqueo.
- Thread t2, t3, t0 llama al método lock () y espera a que tenga éxito.
- Tema t1 llamadas desbloquear ()
- El hilo t0 adquiere bloqueo ya que está privilegiado
Entonces, ¿cuál es el mejor método (posiblemente el más simple) para hacer esto?
Lo que estaba pensando es tener una variable bool llamada "privileged_needs_lock".
Pero creo que necesito otro mutex para administrar el acceso a esta variable ... No sé si esta es la forma correcta ...
Información adicional:
- mis hilos usan C ++ 11 (a partir de gcc 4.6.3)
- el código debe ejecutarse tanto en Linux como en Windows (pero probado solo en Linux en este momento).
- el rendimiento en el mecanismo de bloqueo no es un problema (mi problema de rendimiento está en los cálculos de subprocesos internos, y el número de subprocesos siempre será bajo, uno o dos por núcleo de CPU como máximo)
Cualquier idea es apreciada. Gracias
La siguiente solución funciona (tres vías 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;
}
Intentó la solución a continuación como se sugiere, pero no funciona (compile con "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;
}
Intenta algo como lo siguiente. Podría hacer de la clase un singleton seguro para subprocesos e incluso podría convertirlo en un functor.
#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;
};
Ponga los hilos de solicitud en una ''cola de prioridad''. El subproceso privilegiado puede obtener primero ir a los datos cuando está libre.
Una manera de hacer esto sería con una matriz de ConcurrentQueues [privilegeLevel], un bloqueo y algunos eventos.
Cualquier hilo que quiera en los datos entra en el bloqueo. Si los datos están libres, (booleano), obtiene el objeto de datos y sale del bloqueo. Si los datos están en uso por otro subproceso, el subproceso que solicita inserta un evento en una de las colas concurrentes, dependiendo de su nivel de privilegio, sale del bloqueo y espera el evento.
Cuando un subproceso desea liberar su propiedad del objeto de datos, obtiene el bloqueo e itera la matriz de ConcurrentQueues desde el extremo de privilegio más alto hacia abajo, buscando un evento, (es decir, cuenta de cola> 0). Si encuentra uno, lo señala y sale del bloqueo, si no, configura el booleano ''dataFree'' y libera el bloqueo.
Cuando se prepara un subproceso que espera en un evento para acceder a los datos, puede acceder al objeto de datos.
Creo que debería funcionar. Por favor, otros desarrolladores, verifiquen este diseño y vean si pueden pensar en alguna carrera, etc. Todavía estoy sufriendo un poco de ''sobrecarga de hospitalidad'' después de un viaje a CZ ..
Editar: probablemente ni siquiera necesite colas concurrentes debido al bloqueo explícito en todas ellas. Cualquier cola vieja haría.
Puedo pensar en tres métodos usando solo primitivas de subprocesamiento:
Mutex triple
Tres mutexes funcionarían aquí:
- mutex de datos (''M'')
- mutex siguiente (''N''), y
- acceso mutex de baja prioridad (''L'')
Los patrones de acceso son:
- Subprocesos de baja prioridad: bloquear L, bloquear N, bloquear M, desbloquear N, {hacer cosas}, desbloquear M, desbloquear L
- Hilo de alta prioridad: bloquear N, bloquear M, desbloquear N, {hacer cosas}, desbloquear M
De esa manera, se protege el acceso a los datos, y el subproceso de alta prioridad puede adelantarse a los subprocesos de baja prioridad en el acceso.
Mutex, condición variable, bandera atómica
La forma primitiva de hacer esto es con una variable de condición y una atómica:
- Mutex M;
- Condvar C;
- atómico bool hpt_waiting;
Patrones de acceso a datos:
- Subproceso de baja prioridad: bloquear M, mientras (hpt_waiting) esperar C en M, {hacer cosas}, transmitir C, desbloquear M
- Subproceso de alta prioridad: hpt_waiting: = verdadero, bloqueo M, hpt_waiting: = falso, {hacer cosas}, transmisión C, desbloquear M
Mutex, variable de condición, dos banderas no atómicas
Alternativamente, puede usar dos bools no atómicos con un condvar; en esta técnica, el mutex / condvar protege las banderas, y los datos no están protegidos por un mutex, sino por una bandera:
- Mutex M;
- Condvar C;
bool data_held, hpt_waiting;
Subproceso de baja prioridad: bloquear M, mientras que (hpt_waiting o data_held) esperar C en M, data_held: = verdadero, desbloquear M, {hacer cosas}, bloquear M, data_held: = falso, transmitir C, desbloquear M
- Hilo de alta prioridad: bloqueo M, hpt_waiting: = verdadero, mientras que (data_held) espera C en M, data_held: = verdadero, {hacer cosas}, bloqueo M, data_held: = falso, hpt_waiting: = falso, transmisión C, desbloqueo M
Respuesta de Ecatmur ligeramente modificada, agregando un 4º mutex para manejar múltiples hilos de alta prioridad al mismo tiempo (tenga en cuenta que esto no era necesario en mi pregunta original):
#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;
}
¿Qué piensas? ¿Está bien? Es cierto que un semáforo podría manejar mejor este tipo de cosas, pero los mutexes son mucho más fáciles de manejar para mí.
Ya que las prioridades del hilo no están funcionando para ti:
Cree 2 mutexes, un bloqueo regular y un bloqueo de prioridad.
Las hebras normales primero deben bloquear el bloqueo normal y luego el bloqueo de prioridad. El hilo de prioridad solo tiene que bloquear el bloqueo de prioridad:
Mutex mLock;
Mutex mPriLock;
doNormal()
{
mLock.lock();
pthread_yield();
doPriority();
mLock.unlock();
}
doPriority()
{
mPriLock.lock();
doStuff();
mPriLock.unlock();
}
pthreads tiene prioridades de hilo:
pthread_setschedprio( (pthread_t*)(&mThreadId), wpri );
Si hay varios subprocesos inactivos esperando en un bloqueo, el programador activará primero el subproceso de mayor prioridad.
#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();
}
};
AVISO: este priority_mutex
proporciona una programación injusta de subprocesos. Si el subproceso privilegiado adquiere el bloqueo con frecuencia, es posible que otros subprocesos no privilegiados no estén programados.
Ejemplo de uso:
#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
}
//...
}