Sei sulla pagina 1di 20

Grupo de Arquitectura de Computadores,

Comunicaciones y Sistemas

ARQUITECTURA DE
COMPUTADORES
ISO C++ Threads

2 Hilos

 Mecanismo básico para programación de máquinas


con memoria compartida.

 Enfoque tradicional:
 Bibliotecas de funciones.
 Ligadas a una o pocas plataformas.

 Baja portabilidad.

 Ejemplos:
 Pthreads, Windows Threads, …

Arquitectura de Computadores - Curso 2011


3 ISO C++ Threads

 Objetivo: Portabilidad a cualquier plataforma que


ofrezca C++.
 Parte del estándar internacional ISO/IEC 14882:201?
(C++0x).

 Ofrece un modelo de hilos independiente de la


plataforma.
 Toda la semántica especificada en el lenguaje de
programación y su sistema de soporte.
 Podría ofrecerse incluso en plataformas sin SO.

 Influencia sobre el modelo de hilos de C (ISO/IEC


9899:201?)

Arquitectura de Computadores - Curso 2011

4 Un hola mundo con hilos

#include <iostream> #include <iostream>


#include <thread>
int main() {
std::cout << “Hola” <<std::endl; void hola() {
return 0; std::cout << “Hola” <<std::endl;
} }

int main() {
std::thread t(hola);
t.join();
return 0;
}

Arquitectura de Computadores - Curso 2011


5 ¿Qué se puede lanzar?

 Un hilo se arranca suministrándole una función.


 Cualquier función se puede invocar.
 No hay necesidad de hacer conversiones (casts).

 Se puede suministrar al constructor de thread


cualquier objeto llamable.
 Un objeto llamable redefine el operador ().

Arquitectura de Computadores - Curso 2011

6 Un objeto llamable

class trabajo { int main() {


public: trabajo w(10);
trabajo(int i) : n(i) {} std::thread t1(w);
void operator()() { std::thread t2(trabajo(12));
for (int k=0;k<n;k++) { t1.join();
tarea(); t2.join();
} }
}
private:
int n;
};

Arquitectura de Computadores - Curso 2011


7 Terminación y join()

 Espera a que la función asociada al hilo termine.


 Control de grano grueso sobre terminación.
 Libera los recursos asociados al hilo.
 Solamente se puede hacer un join() sobre un hilo.
 Se puede interrogar a un hilo con la función
joinable().
 Alternativa: thread::detach().

Arquitectura de Computadores - Curso 2011

8 join() y excepciones

void f() { void f() {


std::thread t(tarea); std::thread t(tarea);
otra_cosa(); try {
t.join(); otra_cosa();
} Excepción }
catch(…) {
t.join();
throw;
}
t.join();
}
Arquitectura de Computadores - Curso 2011
9 join() y excepciones

class control_hilo {
private:
std::thread & t;
public:
explicit control_hilo(std::thread & h) : t(h) {}
~control_hilo() {
if (t.joinable()) { t.join(); }
}
control_hilo(control_hilo const &) = delete;
control_hilo & operator=(control_hilo&) = delete;
};
Arquitectura de Computadores - Curso 2011

10 join() y excepciones

void f() {
std::thread t(tarea);
control_hilo c(t);
otra_cosa();
Hilo se libera en destructor de
} objeto c

Arquitectura de Computadores - Curso 2011


11 detach()

 Deja el hilo ejecutándose y permite destruir el


objeto std::thread.
 Si no se hace join() o detach() y se destruye el objeto
std::thread, el programa completo termina.
 Ya no es posible hacer join() sobre el hilo.
 El hilo se ejecuta en background.
 Usos:
 Hilos que se ejecutan durante toda la vida del
programa  demonios.
 Ejecución de tareas tipo fire and forget.

Arquitectura de Computadores - Curso 2011

12 detach

void editar_documento(std::string const & archivo)


abrir_documento(archivo);
while(!fin_edicion()) {
orden_usuario orden = obtener_entrada();
if (orden.tipo==ABRIR_DOCUMENTO) {
std::string const nombre= pedir_nombre_usuario();
std::thread t(editar_documento, nombre); Hilo con
t.detach(); parámetros
}
else {
procesar_entrada(orden);
}
}
}

Arquitectura de Computadores - Curso 2011


13 Paso de parámetros

 El constructor de std::thread puede tomar cualquier


número de parámetros.
 Los parámetros se pasan a la función u objeto
llamable.
 Los parámetros se pasan como una copia.
 Incluso aunque la función los reciba por referencia.
 Si se quiere pasar un parámetro por referencia hay que
usar std::ref().

Arquitectura de Computadores - Curso 2011

14 Paso de parámetros

void f(int i, double j, std::string c);


void g(int & n, double j, std::string & c);

void h() {
int a=10;
double b=1.0;
string n=“hola”;
std::thread t1(f, a, b, n);
std::thread t2(g, std::ref(a), b, std::ref(n));

}
Arquitectura de Computadores - Curso 2011
15 Paso de parámetros

class X {
public:
void realizar_tarea();

};

int main() {
X a;
std::thread t(&X::realizar_tarea, &a);
t.join();

}
Arquitectura de Computadores - Curso 2011

16 Paso de parámetros y movimientos

 En C++ existen ciertos tipos que no permiten copia.


 Tipos de sólo movimiento:
 Permiten el movimiento pero no la copia.
 Ejemplo: unique_ptr
 unique_ptr<matriz_enorme> d = std::move(c);
 La cadena se mueve a d y c queda vacío.

 Es útil para transferir propiedad de parámetros a


un hilo:
 std::thread t(f,std::move(m));

Arquitectura de Computadores - Curso 2011


17 Transferencia de hilos

 std::thread no es copiable pero tiene soporte de


movimiento.
 Se pueden mover hilos entre variables de tipo std::thread.
 Ejemplos:
std::thread t1(f);
std::thread t2 = std::move(t1); // t1 ya no tiene el thread
t1 = std::thread(g); //t1 tiene asociado un nuevo thread
std::thread t3; // t3 no tiene asociado ningún hilo de
ejecución
t3 = std::move(t2); // control de t2 pasa a t3
t1 = std::move(t3);
 Cuando se pasan parámetros o se devuelven valores
de tipo std::thread se aplica la semántica de
movimiento.
Arquitectura de Computadores - Curso 2011

18

std::thread f() {
return std::thread(tarea1);
}

std::thread g() {
std::thread t(tarea2, 100);
return t;
}

int main() {
std::thread t1 = std::move(f());
std::thread t2 = std::move(g());

}

Arquitectura de Computadores - Curso 2011


19 Un nuevo control_hilo

class control_hilo {
private:
std::thread t; //Sin & t
public:
explicit control_hilo(std::thread & h) : t(std::move(h)) {
if (!t.joinable()) throw std::logic_error(“No hay hilo”);
}
~control_hilo() { t.join(); }
control_hilo(control_hilo const &) = delete;
control_hilo & operator=(control_hilo&) = delete;
};
Arquitectura de Computadores - Curso 2011

20 El nuevo control_hilo

void f() {
control_hilo c(std::thread(tarea));
otra_cosa();
}

Ahora se puede pasar un


objeto temporal

Arquitectura de Computadores - Curso 2011


21 Gestión de una colección de hilos

void f(int n) {
std::vector<std::thread> hilos; //Vector vacío
for (int i=0;i<n;i++) {
hilos.push_back(std::thread(tarea, i));
}
for_each(hilos.begin(), hilos.end(), [](std::thread & t) {
t.join(); });
}

Arquitectura de Computadores - Curso 2011

22 Mutex

 Es el mecanismo básico para controlar el acceso a


secciones críticas.
 Funciones lock() y unlock().

 Problema:
 Facilidad de olvidar unlock().

 Solución:
 RAII: Resource Acquisition Is Initialization.
 Clase lock_guard.

Arquitectura de Computadores - Curso 2011


23 Mutex y lock_guard

#include <list> bool tiene(int v) {


#include <mutex> std::lock_guard<std::mutex> guard(mimtx);
#include <algorithm> return
std::find(lista.begin(),lista.end(),v) !=
std::list<int> lista; some_list.end();

std::mutex mimtx; }

void agrega(int v) {
std::lock_guard<std::mutex>
guard(mimtx);
lista.push_back(v);
}

Arquitectura de Computadores - Curso 2011

24 Ejemplo: Una pila concurrente

template <typename T>


class pila_concurrente {
private:
std::stack<T> p;
mutable std::mutex m;
public:
pila_concurrente() {}
pila_concurrente(const pila_concurrente & q) {
std::lock_guard<std::mutex> milock(q.m);
p =q.p;
}
pila_concurrente & operator=(const pila_concurrente&) =
delete;

Arquitectura de Computadores - Curso 2011


25 Ejemplo: Pila concurrente

void push(T nuevovalor) {


std::lock_guard<std::mutex> milock(m);
p.push(nuevovalor);
}

std::shared_ptr<T> pop() {
std::lock_guard<std::mutex> milock(m);
if (p.empty()) throw logic_error(“Pila vacía”);
std::share_ptr<T> const r(std::make_shared<T>(data.top()));
data.pop();
return r;
}

Arquitectura de Computadores - Curso 2011

26 Ejemplo: Pila concurrente

void pop(T & v) {


std::lock_guard<std::mutex> milock(m);
if (data.empty()) throw logic_error(“Pila vacía”);
v = data.top();
data.pop();
}

bool empty() const {


std::lock_guard<std::mutex> milock(m);
return data.empty();
}
};
Arquitectura de Computadores - Curso 2011
27 Tipos de mutex en ISO C++

 mutex.
 No recursivo.
 Semántica de propiedad exclusiva.
 recursive_mutex.
 Recursivo: El hilo que tiene adquirido un mutex puede volver a
adquirirlo.
 Semántica de propiedad exclusiva.
 timed_mutex.
 No recursivo.
 Semántica de propiedad exclusiva.
 Especificación de tiempo máximo (try_lock_for/try_lock_until).
 recursive_timed_mutex.
 Recursivo.
 Semántica de propiedad exclusiva.
 Especificación de tiempo máximo (try_lock_for/try_lock_until).

Arquitectura de Computadores - Curso 2011

28 Variables condición

 condition_variable.
 Primitivas de sincronización para bloquear un hilo hasta
que otro hilo envíe una notificación.
 Notificación:
 notify_one()
 notify_all()

 Espera (con predicado opcional):


 wait()
 wait_until()
 wait_for()

Arquitectura de Computadores - Curso 2011


29 Productor/Consumidor

std::mutex m; void consumidor() {


std::queue<peticion> q; for (;;) {
std::condition_variable cv; std::unique_lock<std::mutex>
milk(m);
void productor(pecicion p) { cv.wait(milk, []{ return !q.empty();});
std::lock_guard<std::mutex> l(m); peticion p = q.front();
q.push(p); q.pop();
cv.notify_one(); milh.unlock();
} procesar(p);
if (ultimo(p)) break;
}
}

Arquitectura de Computadores - Curso 2011

30 Productor/Consumidor (Alternativa)

std::mutex m; void consumidor() {


std::queue<peticion> q; for (;;) {
std::condition_variable cv; peticion p;
{
void productor(pecition p){ std::unique_lock<std::mutex> l(m);
std::lock_guard<std::mutex> l(m); cv.wait(l, []{ return !q.empty();});
q.push(p); p = q.front();
cv.notify_one(); q.pop();
} }
procesar(p);
if (ultimo(p)) break;
}
}

Arquitectura de Computadores - Curso 2011


31 Una cola concurrente

template <typename T>


class cola_concurrente {
private:
std::mutex m;
std::queue<T> q;
std::condition_variable cv;
public:
void pon(T v) {
std::lock_guard<std::mutex> l(m);
q.push(v);
cv.notify_one();
}

Arquitectura de Computadores - Curso 2011

32 Una cola concurrente

void obten(T & v) {


std::unique_lock<std::mutex> l(m);
cv.wait(l, [this]{ return !q.empty(); });
v = q.front();
q.pop();
}
};

Arquitectura de Computadores - Curso 2011


33 Productor/consumidor

cola_concurrente<peticion> q; void consumidor() {


void productor() { for (;;) {
while (mas_datos()) { peticion p;
peticion p = genera_peticion(); q.obten(p);
q.pon(p); procesar(p);
} if (ultimo(p)) break;
} }
}

Arquitectura de Computadores - Curso 2011

34 Tareas asíncronas y futuros

 Una tarea asíncrona permite el lanzamiento simple


de la ejecución de una tarea:
 En otro hilo de ejecución (opción por defecto).
 Como una tarea diferida.

 Un futuro es un objeto que permite que un hilo


pueda devolver un valor a la sección de código
que lo invocó.

Arquitectura de Computadores - Curso 2011


35 Asíncronos y futuros

#include <future>
#include <iostream>

int main() {
std::future<int> r = std::async(tarea, 1, 10);
otra_tarea();
std::cout << “Resultado= “ << r.get() << std::endl;
return 0;
}

Arquitectura de Computadores - Curso 2011

36 Tareas y futuros

 std::packaged_task<>
 Asocia la llamada a una función con un resultado.
 Permite pasar tareas a hilos y recuperar después el
resultado.
 std::packaged_task<int(int,int)> tarea;
 Encapsula una función que toma dos enteros
 void operator()(int,int)
 Permite obtener el resultado en un futuro.
 std::future<int> get_future()

Arquitectura de Computadores - Curso 2011


37 Uso de tareas empaquetadas

std::mutex m; void procesador() {


std::queue<std::package_task< while (!fin_aplicacion()) {
int(int,int)>> tareas; hacer_algo();
std::packaged_task<int(int,int)> t;
template <typename F> {
std::future<int> nueva_funcion(F f) { std::lock_guard<std::mutex> l(m);
std::packaged_task<int(int,int)> t(f); if (tareas.empty()) continue;
std::future<int> rf = t.get_future(); t = std::move(tareas.front());
std::lock_guard<std::mutex> l(m); tareas.pop();
tareas.push_back(std::move(t)); }
return rf; t();
} }

Arquitectura de Computadores - Curso 2011

38 Promesas

 promise<R>
 Es el vehículo para pasar un valor de retorno (o una
excepción) entre dos hilos.
 Es una abstracción de paso de mensajes entre hilos.
 Envío de mensaje:
 set_value
 set_exception
 Recepción de mensaje:
 Consulta del futuro asociado.

Arquitectura de Computadores - Curso 2011


39 Uso de promesas


promise<int> p;
void func(promise<int> p) { unique_future<int> f =
int r; p.get_future();
try { std::thread t(func, std::move(p));
r = calcular_valor(); hacer_algo();
p.set_value(r) int r = f.get();
} …
catch (…) {
p.set_exception( std::curent_exception());
}
}

Arquitectura de Computadores - Curso 2011

40 Promesas y tareas empaquetadas

 async se puede implementar en términos de


promesas y tareas empaquetadas.

 Mediante otros usos de promesas y tareas


empaquetadas se puede implementar otros
mecanismos.

Arquitectura de Computadores - Curso 2011

Potrebbero piacerti anche