So, the subject of today's entry is multithreading. Multithreading is no longer the wave of the future, it's here and it's not going away. As such, I figured I should learn a thing or two about it. This is something I've wanted to do for a while, but have been terribly lazy about. And, you know, it's very relevant to work, so that's a plus.
That all said, I spent an hour tonight hammering out a very basic introduction for myself into the world of threading. Nothing terribly special, it's just one thread writing to a queue while another reads from it. I'll let the code speak for itself, critique it as you will.
Mutex.h
#ifndef MUTEX_H#define MUTEX_H#include #define LOCK(hLock, timeout) { thread::Mutex _lock(hLock, timeout);#define SAFE_LOCK(hLock, timeout) try { thread::Mutex _lock(hLock, timeout);#define UNLOCK }typedef void* HANDLE;namespace thread{ class WaitAbandonedException : public std::exception { public: WaitAbandonedException() throw() {} WaitAbandonedException(const WaitAbandonedException& rhs) throw() {} WaitAbandonedException& operator=(const WaitAbandonedException& rhs) throw() {} virtual ~WaitAbandonedException() throw() {} virtual const char *what() const throw() {return "Wait Abandoned";} }; class WaitTimeoutException : public std::exception { public: WaitTimeoutException() throw() {} WaitTimeoutException(const WaitTimeoutException& rhs) throw() {} WaitTimeoutException& operator=(const WaitTimeoutException& rhs) throw() {} virtual ~WaitTimeoutException() throw() {} virtual const char *what() const throw() {return "Wait Timeout";} }; class Mutex { public: Mutex( HANDLE hLock, DWORD timeout); Mutex( const Mutex& copy ); Mutex &operator=(const Mutex &rhs); ~Mutex(); bool Locked() {return m_IsLocked;} private: HANDLE m_Lock; //mutable locked state because it will be changed on copy and assignment when ownership is passed mutable bool m_IsLocked; };}#endif
Mutex.cpp
#include #include "Mutex.h"thread::Mutex::Mutex( HANDLE hLock, DWORD timeout ): m_Lock(hLock),m_IsLocked(false){ //aquire lock on construction DWORD Ret = WaitForSingleObject(m_Lock, timeout); switch (Ret) { case WAIT_OBJECT_0: m_IsLocked = true; break; case WAIT_ABANDONED: throw *(new thread::WaitAbandonedException); break; case WAIT_TIMEOUT: throw *(new thread::WaitTimeoutException); break; default: //unkown error, just bail throw; break; }}thread::Mutex::Mutex(const thread::Mutex& copy){ *this = copy;}thread::Mutex& thread::Mutex::operator=(const thread::Mutex &rhs){ //release any existing lock if (m_IsLocked) { ReleaseMutex(m_Lock); } m_Lock = rhs.m_Lock; m_IsLocked = rhs.m_IsLocked; //take ownership of copied mutex rhs.m_IsLocked = false; return *this;}thread::Mutex::~Mutex(){ if (m_IsLocked) { ReleaseMutex(m_Lock); }}
thread.h
#ifndef THREAD_H#define THREAD_H#include extern HANDLE g_Mutex;namespace thread{ DWORD WINAPI Func(LPVOID lParam);}#endif
thread.cpp
#include "thread.h"#include "Mutex.h"#include #include #include extern std::queue<int> g_Queue;DWORD WINAPI thread::Func(LPVOID /*lParam*/){ while(TRUE) { SAFE_LOCK(g_Mutex, INFINITE) while (!g_Queue.empty()) { if (g_Queue.front() == -1) { return TRUE; } std::cout << "Thread two reading " << g_Queue.front() << " from queue.\n"; g_Queue.pop(); } UNLOCK catch (std::exception &e) { std::cout << "Caught exception in thread 2: " << e.what() << "\n"; delete &e } } return TRUE;}
main.cpp
#include #include #include #include "thread.h"#include "Mutex.h"HANDLE g_Mutex;std::queue<int> g_Queue;void FillQueue();int main(){ g_Mutex = CreateMutex( NULL, FALSE, NULL ); DWORD threadID; HANDLE hThread = CreateThread( NULL, 0, thread::Func, NULL, 0, &threadID ); FillQueue(); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(g_Mutex); return 0;}void FillQueue(){ for (int i = 0; i < 100; ++i) { SAFE_LOCK(g_Mutex, INFINITE) std::cout << "Thread one adding " << i << " to the queue.\n"; g_Queue.push(i); UNLOCK catch (std::exception &e) { std::cout << "Caught exception in thread 1: " << e.what() << "\n"; delete &e } } g_Queue.push(-1); return;}
'Til next time!
In fact, I don't really believe that you can have sane copy semantics on a Mutex. I would probably just make them non-copyable.