Створення класів із потоками, C

Зустрічаючи у форумах питання про те, як створювати класи із потоками, я вирішив написати цю статтю.

Звичайно, можна використовувати клас MFC, але не всі програми використовують цю бібліотеку.

Вся ідея полягає в тому, що ми все-таки опишемо глобальну функцію, яку і будемо передавати як параметр функції CreateThread, а в свою чергу ця функція запустить виконання функції потоку у Вашому класі.

Отже, створимо базовий клас:

class CMyThread friend DWORD WINAPI ThreadProc(LPVOID lpParameter); // Пояснення див. нижче HANDLE m_hThread;

public: DWORD Terminate(BOOL bCritical=FALSE); BOOL Execute(); CMyThread(); virtual

DWORD m_dwID; virtual DWORD ThreadFunc()=NULL; // Ця функція буде перевизначатися в наступних класах BOOL m_bTerminated; // цей прапор буде вказувати потоку, що час закруглюватися і завершувати свою роботу коректно. >;

Ми описали клас з усіма важливими функціями: запуску потоку, зупинки.

Тепер напишемо глобальну потокову функцію:

extern DWORD WINAPI ThreadProc(LPVOID lpParameter) CMyThread *pMyThread=(CMyThread *)lpParameter; return pMyThread-ThreadFunc(); >

Ключове слово extern запобігає використанню цієї функції поза цим файлом програми. Як параметр отримаємо вказівник на об'єкт, в якому і буде поміщений код необхідний для виконання в окремому потоці. Хоча ми й перетворимо покажчик до покажчика на базовий клас, але буде викликана функція спадкового класу.

CMyThread::CMyThread() m_bTerminated=FALSE; m_hThread=NULL; >

CMyThread() Terminate(FALSE); // при знищенні об'єкта, переконаємось, що потік завершений або завершимо його >

Тепер перейдемо до найголовнішого – запуску нашого потоку:

BOOL CMyThread::Execute() if (m_hThread) return FALSE; > m_hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,this,0,&m_dwID);

Ця функція завершує роботу потоку. Спочатку вона попереджає потік про завершення, але якщо він сам не завершується, "вб'ємо" його.

DWORD CMyThread::Terminate(BOOL bCritical) if (m_hThread==NULL) return (DWORD)-1;

m_bTerminated=TRUE; DWORD dwExitCode;

if (!bCritical) for(int i=10;i>0;i--) GetExitCodeThread(m_hThread,&dwExitCode); if (dwExitCode==STILL_ACTIVE) Sleep(25); else break; > >

GetExitCodeThread(m_hThread,&dwExitCode); if (dwExitCode==STILL_ACTIVE) TerminateThread(m_hThread,-1); GetExitCodeThread(m_hThread,&dwExitCode); CloseHandle(m_hThread); m_hThread=NULL; m_bTerminated=FALSE; return dwExitCode; >

Ось і все, базовий клас готовий. Тепер створюємо наш клас, з необхідними характеристиками, функцією потоку та успадковуємо базовий клас:

class CMathThread : public CMyThread public: double m_dValue; CMathThread(); virtual

protected: DWORD ThreadFunc(); // обов'язково визначаємо цю функцію, і поміщаємо до неї свій код >;

Ось ця функція і запуститься на виконання після виклику CMathThread::Execute() :

m_dValue+=.00001; // Власне врахування > return 0; >

Створюємо будь-які класи, перевизначаємо віртуальну функцію ThreadFunc() та створюємо хоч сотню об'єктів такого типу, передаючи їм аргументи через члени класу.

Зазначимо, створити базовий клас не можна, т.к. він містить суто віртуальну функцію, яку необхідно визначати ву наступних класах.

Досвідчені програмісти одразу виявлять кілька помилок, яких ніяк не можна допускати під час роботи з потоками. Наприклад, неправильно використовувати змінну для попередження потоку завершення (m_bTerminate). Друге, необхідно дописати механізми спілкування з об'єктом із тимчасовою зупинкою потоку. Але метою статті було пояснення створення подібних класів, тому що синхронізація потоків надто велика тема для опису цієї статті.

У разі я допустив використання змінної попередження потоку, т.к. вона має на увазі тільки 2 стани 0 і не 0. Але зчитування змінної m_dValue при працюючому потоці загрожує. Необхідно використовувати Event, Mutex, Semaphore чи інші механізми синхронізації з потоком для зчитування загальних даних. А це, як я вже сказав, зовсім інша історія.