Как стать автором
Обновить

Многопоточные классы

Время на прочтение5 мин
Количество просмотров19K
Доброго времени суток. Хочу поведать уважаемому хабрачитателю одну интересную вещь с которой пришлось столкнуться в недавнем времени. В одной из задач, стоявших передо мной, требовалось реализовать класс. В рамках этого класса должны выполняться некоторые вычисления. Для простоты, а также в целях сохранения работоспособности основного кода, решено было использовать потоки. Но все оказалось не так тривиально.

Первоначальный вариант был таков:
// структура параметров передаваемых в поток
struct ArgsThread
{
  int *tmp1;
  char *tmp2;
  int indata1;
  char indata2;
};

// непосредственно поток выполняющий вычисления
unsigned long CalculationThread(void *arg)
{
  ThreadArgs* args = reinterpret_cast<ThreadArgs*>(arg); // выкапываем входные данные
  
  // что-то сумбурно и долго считаем
}

// класс который требуется реализовать
class MyCalc
{
  private:
    void *HandleThread;
    unsigned long IdThread;
    
    int tmp1;
    char tmp2;
    
  public:
    MyCalc(int indata1, char indata2)
    {
      ArgsThread* args=new ArgsThread();
      
      args->tmp1 = &tmp1;  // упаковываем данные
      args->tmp2 = &tmp2;
      args->indata1 = indata1;
      args->indata2 = indata2;
      
      HandleThread = CreateThread(NULL, 0, &CalculationThread, args, 0, &IdThread); // создаем поток
    };
    
    ~MyCalc
    {
      TerminateThread(HandleThreade, NULL);  // завершаем выполнение потока
      CloseHandle(HandleThread);        // закрываем хендл
    };
};


Поток находился вне класса, что, в принципе, удовлетворяло задаче. После раздумий пришла идея реализации потока внутри класса, а точнее, один из методов должен стать функцией, которую выполнял бы поток.
Недолго думая (что очень зря), родился такой код:
class MyCalc
{
  private:
    void *HandleThread;
    unsigned long IdThread;
    
    int tmp1;
    char tmp2;
  protected:
    unsigned long CalculationThread(void *arg)
    {
      // что-то сумбурно и долго считаем
    }
    
  public:
    MyCalc(int indata1, char indata2)
    {
      HandleThread = CreateThread(NULL, 0, &CalculationThread, this, 0, &IdThread); // создаем поток
    };
    
    ~MyCalc
    {
      TerminateThread(HandleThreade, NULL);  // завершаем выполнение потока
      CloseHandle(HandleThread);        // закрываем хендл
    };
};


Естественно, во время компиляции я получил ошибку о том, что передаваемые в функцию CreateThread параметры не корректны, а в частности прототип функции потока не соответствовал запрашиваемому типу. На одном из форумов было найдено решение, суть которого заключалась в том, что метод делаем static. Что тоже в принципе удовлетворяло меня, но как оказалось поток не имел доступа ко внтренним данным класса. И в конечном итоге решено было сделать этот метод полноправным членом класса. После полу часа возни с компилятором родилось такое решение:
// объявляем необходимые типы
typedef unsigned long (__stdcall *ThrdFunc)(void *arg);    // прототип функции потока
typedef unsigned long (__closure *ClassMethod)(void *arg);    // прототип метода класса

// данное объединение позволяет решить несостыковку с типами
typedef union
{
  ThrdFunc Function;
  ClassMethod Method;
}tThrdAddr;

// для гибкости использования храним все в одном месте
typedef struct
{
  void* Handle;        // хэндл потока
  tThrdAddr Addr;        // адрес
  unsigned long Id;      // ID потока
  unsigned long ExitCode;    // код выхода
}tThrd;

class MyCalc
{
  private:
    tThrd MyThread;
    
  protected:
    unsigned long ThrdHandle(void *arg)
    {
      // что-то сумбурно и долго считаем
    };
    
  public:
    MyCalc()
    {
      MyThread.Addr.Method = &ThrdHandle; // тут главная магия
      MyThread.Handle = CreateThread(NULL, 0, MyThread.Addr.Function, this, 0, &MyThread.Id);
      GetExitCodeThread(MyThread.Handle, &MyThread.ExitCode);
    };
    
    ~MyCalc()
    {
      if(MyThread.Handle)
      {
        TerminateThread(MyThread.Handle, MyThread.ExitCode);
        CloseHandle(MyThread.Handle);
      }
    };
};


Таким образом наш поток становится и членом класса одновременно, со всем вытекающими ООП возможностями. Отпадает необходимость в «огороде» из упаковки и распаковки аргументов содержащих данные, поскольку теперь есть доступ ко всем методам и полям класса. Таких методов-потоков можно реализовать бесконечно много, и пользователь класса не сможет получить к ним хоть какой-нибуть доступ (правила private и protected) или случайно вызвать выполнение кода содержащегося в методе-потоке.

P.S. При желании прототип метода может в корне отличатся от того, что объявлен в примере, ведь используется только его адрес, но не стоит забывать и о передаваемых параметрах.

UPD: данный код генерировался и оттачивался в среде C++ Builder, поэтому в прототипе метода присутствует __closure. изменяя прототип вы можете без больших потерь и изменений использовать данный код в других компиляторах.
Теги:
Хабы:
+4
Комментарии37

Публикации

Изменить настройки темы

Истории

Работа

Программист C++
128 вакансий
QT разработчик
7 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн