10.4.5. Almacenamiento local al hilo

Una segunda forma de eliminar el problema de colisión de tareas sobre recursos compartidos es la eliminación de las variables compartidas, lo cual puede realizarse mediante la creación de diferentes almacenamientos para la misma variable, uno por cada hilo que use el objeto. De esta forma, si tiene cinco hilos que usan un objeto con una variable x, el almacenamiento local al hilo genera automáticamente cinco porciones de memoria distintas para almacenar x. Afortunadamente, la creación y gestión del almacenamiento local al hilo la lleva a cabo una plantilla de ZThread llamada ThreadLocal, tal y como se puede ver aquí:

//: C11:ThreadLocalVariables.cpp {RunByHand}
// Automatically giving each thread its own storage.
//{L} ZThread
#include <iostream>
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/ThreadedExecutor.h"
#include "zthread/Cancelable.h"
#include "zthread/ThreadLocal.h"
#include "zthread/CountedPtr.h"
using namespace ZThread;
using namespace std;

class ThreadLocalVariables : public Cancelable {
  ThreadLocal<int> value;
  bool canceled;
  Mutex lock;
public:
  ThreadLocalVariables() : canceled(false) {
    value.set(0);
  }
  void increment() { value.set(value.get() + 1); }
  int get() { return value.get(); }
  void cancel() {
    Guard<Mutex> g(lock);
    canceled = true;
  }
  bool isCanceled() {
    Guard<Mutex> g(lock);
    return canceled;
  }
};

class Accessor : public Runnable {
  int id;
  CountedPtr<ThreadLocalVariables> tlv;
public:
  Accessor(CountedPtr<ThreadLocalVariables>& tl, int idn)
  : id(idn), tlv(tl) {}
  void run() {
    while(!tlv->isCanceled()) {
      tlv->increment();
      cout << *this << endl;
    }
  }
  friend ostream&
    operator<<(ostream& os, Accessor& a) {
    return os << "#" << a.id << ": " << a.tlv->get();
  }
};

int main() {
  cout << "Press <Enter> to quit" << endl;
  try {
    CountedPtr<ThreadLocalVariables>
      tlv(new ThreadLocalVariables);
    const int SZ = 5;
    ThreadedExecutor executor;
    for(int i = 0; i < SZ; i++)
      executor.execute(new Accessor(tlv, i));
    cin.get();
    tlv->cancel(); // All Accessors will quit
  } catch(Synchronization_Exception& e) {
    cerr << e.what() << endl;
  }
} ///:~

Listado 10.23. C11/ThreadLocalVariables.cpp


Cuando crea un objeto ThreadLocal instanciando la plantilla, únicamente puede acceder al contenido del objeto utilizando los métodos set() y get(). El método get() devuelve una copia del objeto que está asociado a ese hilo, y set() inserta su argumento dentro del objeto almacenado para ese hilo, devolviendo el objeto antiguo que se encontraba almacenado. Puede comprobar que esto se utiliza en increment() y get() de ThreadLocalVariables.

Ya que tlv se comparte en múltiples objetos Accessor, está escrito como un Cancelable, por lo que los Accessors puede recibir señales cuando queramos parar el sistema.

Cuando ejecute este programa se evidenciará que se reserva para cada hilo su propio almacenamiento.