Konstantin Bukin CSE791 – Advanced Windows Programming Summer 2001 More on threads Konstantin Bukin CSE791 – Advanced Windows Programming Summer 2001
MFC’s thread implementation MFC Internals by G. Shepherd, S. Wingo Executing a class member in its own thread (C/C++ Users Journal, Vol.17, No. 12, p. 57) Singleton Creation the thread-safe way (C/C++ Users Journal, Vol.17, No. 10, p. 43) Improving performance with thread-private heaps (C/C++ Users Journal, Vol.17, No. 9, p. 50)
Thread’s thread creation CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority, UINT nStackSize, DWORD dwCreateFlags, LPSECURITY_ATTRIBUTES lpSecurityAttrs); CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass,
AfxBeginThread() It’s all about CWinThread and it’s CreateThread() CWinThread* pThread = new CWinThread(pfnThreadProc, pParam); if (!pThread->CreateThread( dwCreateFlags | CREATE_SUSPENDED, nStackSize, lpSecurityAttrs)) { pThread->Delete(); return NULL; } VERIFY(pThread->SetThreadPriority(nPriority)); if (!(dwCreateFlags & CREATE_SUSPENDED)) VERIFY(pThread->ResumeThread() != (DWORD)-1); return pThread; It’s all about CWinThread and it’s CreateThread()
CWinThread class CWinThread : public CCmdTarget { public: CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam); BOOL CreateThread(DWORD dwCreateFlags = 0, UINT nStackSize = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL); virtual BOOL InitInstance(); virtual int Run(); virtual BOOL PreTranslateMessage(MSG* pMsg); virtual BOOL PumpMessage(); // low level message pump virtual BOOL OnIdle(LONG lCount); //return TRUE if more idle processing virtual BOOL IsIdleMessage(MSG* pMsg); //checks for special messages private: LPVOID m_pThreadParams; //generic parameters passed to starting function AFX_THREADPROC m_pfnThreadProc; HANDLE m_hThread; // this thread's HANDLE } Back to AfxBeginThread()
CWinThread::CreateThread() Initialization of _AFX_THREAD_STARTUP startup structure m_hThread = (HANDLE)_beginthreadex(lpSecurityAttrs, nStackSize, &_AfxThreadEntry, &startup, dwCreateFlags| CREATE_SUSPENDED, (UINT*) &m_nThreadID); ResumeThread(), ::ResumeThread(m_hThread) ::WaitForSingleObject(startup.hEvent, INFINITE) _AfxThreadEntry() Cleans up after creating child thread ::SetEvent(startup.hEvent2) _AfxThreadEntry() keeps running Pic
_AfxThreadEntry() Copies and initializes thread’s specific data (a lot of them) Unblocks parent thread ::WaitForSingleObject(startup.hEvent2, INFINITE) Back to CWinThread::CreateThread() if (pThread->m_pfnThreadProc != NULL) nResult=(*pThread->m_pfnThreadProc)(pThread->m_pThreadParams); else if (!pThread->InitInstance()) nResult=pThread->ExitInstance(); else pThread-> Run(); //clean up and shutdown thread AfxEndThread(nResult); Pic
A time line for Mom and Junior threads
CWinThread::Run() int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0;} } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable
CWinThread::OnIdle() BOOL CWinThread::OnIdle(LONG lCount){ if (lCount <= 0) { // send WM_IDLEUPDATECMDUI to the main window // send WM_IDLEUPDATECMDUI to all frame window else if (lCount >= 0) { //Call AfxLockTempMaps/AfxUnlockTempMaps //to free maps, DLLs, etc } return lCount < 0; //nothing more to do if lCount >= 0
Executing a class member in its own thread Motivation class Task {…}; class Processor { public: void SubmitTask(Task t); private: usigned ProcessTask(void *data); std::queue<Task> m_tasks; } Problem WINBASEAPI HANDLE WINAPI CreateThread(LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, WDORD dwCreationFlags, LPDWORD lpThreadID);
Usage Foo f; runInThread(f, &Foo::member, args);
Solution #1 template <class Object> void runInThread(Object& obj, DWORD(WINAPI Objecg::*memberFunc)(void), HANDLE* handle=0, DWORD* id=0) { using std::runtime_error; DWORD (WINAPI *threadFunc)(void*); //pass object “this” pointer via thread void* pointer void *thisPointer=satic_cast<void*>(&obj); //copy class member pointer to global function pointer *reinterpret_cast<long*>(&threadFunc)=*reinterpret_cast<long*>(&memberFunc); DWORD threadId = -1; HANDLE result=CreateThread(0, 0, threadFunc, thisPointer, 0, &threadId); if (result == 0) throw runtime_error(“CreateThread failed”); if (handle) *handle=result; if (id) *id = threadId; }
Problems of solution #1 The solution relies on WINAPI calling convention (__stdcall) which is not always possible. It would not work with const member functions. The solution assumes (incorectly) that non-static member function will always revieve their this pointer as the first argument on the call stack. Using the void* thread argument to pass the object this pointer prevents the member function from taking any parameters. It’s a big limitation.
Solution #2 template <class Object, class MemberFunc, class Param> class ThreadableMember { private: Object& m_instance; MemberFunc m_member; struct ThrdArg { Object *object; MemberFunc memberFunc; Param param; ThrdArg(Object& o, MemberFunc m, const Param& p) : object(o), memberFunc(m), param(p) {} }; static DWORD WINAPI ThreadFunc(LPVOID v) { std::auto_ptr<ThrdArg> arg(reinterpret_cast<ThrdArg*>(v); return ((arg->object)->*(arg->memberFunc))(arg->param); }
Solution #2 (cont.) public: ThreadableMember(Object& instance, MemberFunc member) : m_instance(instance), m_member(member) {} void run(const Param& param, HANDLE* handle=0, DWORD* id=0){ DWORD thrdId=-1; ThrArg* thrdArg = new ThrArg(m_instance, m_member, param); HANDLE result = CreateThread(0, 0, &ThreadFunc, thrdArg,0,&thrdId); if (result != 0){ if (handle) *handle=result; if (id) *id = thrdId; } else { delete thrdArg; throw std::tuntime_error(“CreateThread failed”); } };
Usage Foo f; ThreadableMember<Foo, DWORD(Foo::*)(char*), char*> m(f, &Foo::Bar1); m.run(“alpha”); Helper function: template <class Object, class MemberFunc, class Param> inline void runInThread(Object& instance, MemberFunc member, const Param& param, HANDLE* handle=0, DWORD* id=0){ ThreadableMember<Object,MemberFunc,Param> m(instance, member); m.run(param, handle, id); } runInThread(f, &Foo::Bar2, class Bar2Param);
Issues Take care of you data members Thread security and stack-size can be added as a parameters to Threadable::run() and runInThread() One more parameter (perhaps, with default value) can be added to create thread in a suspended state
Singleton Creation in a Thread-safe Way class Singleton { public: static Singleton& instance(){ if (!m_instance) m_instance = new Singleton; return *m_instance; } protected: Singleton(); private: static Singleton* m_instance; }; Singleton* Singleton::m_instance = 0;
Problem Thread may be preempted by the operating system during if (!m_instance). Then another thread gets control. Since the first thread did not finished creation of the m_instance, the second thread evaluates (!m_instance) as true and creates another singleton.
Solution class Singleton { public: static Singleton& instance(){ m_key.Lock(); if (!m_instance) m_instance = new Singleton; return *m_instance; m_key.Unlock(); } protected: Singleton(); private: static Singleton* m_instance; static CCriticalSection m_key; }; Singleton* Singleton::m_instance = 0; CCriticalSection Singleton::m_key; Different from the code in the article
Heaps Let’s compare these two functions: void SomeFunc(Foo bar){ Obj* pObj=new Obj; pObj->Hadler(bar); delete pObj; } Obj obj; obj.Hadler(bar);
Possible memory leak (can be solved with auto_ptr) Heap is a shared resource, which has to be synchronized The more interdependences between threads the less scalable application
Process heap and Private heap When a new Win32 process starts up, OS creates a default Process heap. GetProcessHeap() returns its handle HANDLE HeapCreate(DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize) creates Private heap LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwButes); BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); BOOL HeapDestroy(HANDLE hHeap); Serialization is turned on by default, but can be turned off by using a flag value of HEAP_NO_SERIALIZE Access to the process heap in an multithreaded application must be serialized.