Witam
Napisałem ostatnio taką testową aplikację wielowątkową (dokładnie 2-wątkową :)) i z racji iż zagadnienia związane z pisaniem aplikacji wielowątkowych należą do tych trudniejszych, tak samo "układanie" aplikacji wielowątkowych też nie jest takie "hop-siup" pomyślałem że zasięgnę opinii osób które mogłyby znaleźć jakieś błędy logiczne i ocenią poniższy przykład.
Krótki opis programu. Klasa CSerial jest niejako "opakowaniem" klasy która otwiera port COM, zamyka, wysyła coś na niego itd.
Klasa ta ma za zadanie operacje IO wrzucić do nowego wątka i sama nie powodować zwiechy samej siebie. w main'ie tworzymy obiekt tej klasy CSerial i z dostępnych funkcji mamy np Switch(state). W funkcji tej jest tworzony nowy wątek (jeśli jeszcze nie istnieje), i przekazujemy do niego wskaźnik na siebie (this). Proces wątka (WorkerThread) z kolei przekazany parametr rzutuje nie ma klasę CSerial, tylko na interfejs który ta klasa implementuje (Iswitch). Interfejs ten udostępnia nam tylko i wyłącznie jedną funkcję obiektu CSerial, która bezpośrednio już operuje na porcie szeregowym. Rzutowanie na interfejs a nie na kompletny obiekt CSerial zabezpiecza nas przed wywołaniem w wątku niedozwolonych funkcji. Może mutex w samym WorkerThread jest na wyrost, bo można założyć że nowy wątek można tworzyć tylko wewnątrz klasy CSerial (a nie jak zakomentowano w main'ie przy naciśnięciu 'm'). Tak samo można założyc, że tylko główny wątek może wywoływać funkcję CSerial::Switch aby między ustawianiem w tej funkcji zmiennej m_operation a faktyczną operacją na porcie w wątku nic nie zmieniło tej zmiennej (dlatego też zastosowałem interfejs).
Dla zasymulowania bardzo długich czasów operacji IO dałem opóźnienia w dwóch miejscach.
#include <windows.h>
#include <conio.h>
#include "Comm.h"
DWORD WINAPI WorkerThread( LPVOID lpParam );
HANDLE ghMutex;
const BYTE STATE_ON = 1;
const BYTE STATE_OFF = 0;
typedef enum PENDING_OP
{
PO_INIT = 0,
PO_TURNON,
PO_TURNOFF,
PO_TEST,
PO_NUM //number of pending operations
}PENDING_OP;
//=============================================================================
// Interface
//=============================================================================
class Iswitch
{
public:
virtual void _switch_thread( void ) = 0;
};
//=============================================================================
// CSerial class
//=============================================================================
class CSerial : private Comm, public Iswitch
{
public:
CSerial()
{
hWriteThread = NULL;
hLocalMutex = NULL;
m_State = STATE_ON;
}
~CSerial();
int Switch( BYTE State );
BYTE GetState( void ) { return m_State; }
private:
void _switch_thread();
HANDLE hWriteThread;
HANDLE hLocalMutex;
DWORD iThreadId;
PENDING_OP m_operation;
BYTE m_State;
};
//=============================================================================
// _switch_thread
//=============================================================================
void CSerial::_switch_thread()
{
/*
//initialize COM port
if( !SetupString( "COM1: Baud=9600 data=8 parity=N stop=1" ) )
{
printf("Setup port error\n");
return;
}
//Open COM port
if( !open() )
{
printf("Opening port error\n");
return;
}
*/
Sleep(2000);
switch( m_operation )
{
case PO_TURNON:
m_State = STATE_ON;
printf("TURN ON\n");
// write( "1", 1 ); //BASE
break;
case PO_TURNOFF:
m_State = STATE_OFF;
printf("TURN OFF\n");
// write( "0", 1 ); //BASE
break;
default:
break;
}
//BASE, close COM port
// close();
}
//=============================================================================
// Write
//=============================================================================
int CSerial::Switch( BYTE State )
{
if( WaitForSingleObject( hWriteThread, 0 ) == WAIT_TIMEOUT )
{
printf("Thread running\n");
return -1;
}
//create local (CSerial class member) mutex to secure m_operation variable ??
//hLocalMutex = CreateMutex( NULL, TRUE, NULL );
if( State == STATE_ON )
m_operation = PO_TURNON;
else
m_operation = PO_TURNOFF;
iThreadId = 1;
printf("Starting new Thread...\n");
hWriteThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)WorkerThread, this, 0, &iThreadId );
//release local (CSerial class member) mutex
//ReleaseMutex( hLocalMutex );
return m_State;
}
//=============================================================================
// WorkerThread
//=============================================================================
DWORD WINAPI WorkerThread( LPVOID lpParam )
{
DWORD dwResult = WaitForSingleObject( ghMutex, 0 );
if( dwResult == WAIT_TIMEOUT )
{
printf("MUTEX LOCKED\n");
return -1;
}
ghMutex = CreateMutex( NULL, TRUE, NULL );
printf( "\nBegin thread...\n" );
Iswitch *ptr = (Iswitch*)lpParam;
ptr->_switch_thread();
Sleep( 3000 );
printf( "\n\nExiting thread...\n" );
ReleaseMutex( ghMutex );
return 0;
}
int main( int argc, char *argv[] )
{
int key;
CSerial *port = new CSerial();
while(1)
{
key = _getch();
if(key=='a')
printf( "\nres0 = %d\t", port->Switch( STATE_ON ) );
if(key=='s')
printf( "\nres0 = %d\t", port->Switch( STATE_OFF ) );
if(key=='x')
break;
if(key=='.')
printf(".%d.", port->GetState() );
//if(key=='m')
// CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)WriteThread, ¶ms, 0, &i );
}
return 0;
}