當在類中需要創建線程時,總是因為線程函數需要定義成靜態成員函數,但是又需要訪問非靜態數據成員這種需求,來做若干重復性的繁瑣工作。比如我以前就經常定義一個靜態成員函數,然後定一個結構體,結構體形式如下所示,將類指針傳入到線程函數中以方便訪問費非態成員變量。
struct THREAD_PARAMER { CTestClass* pThis; PVOID pContext; }
其實這裡不算解決問題吧,應該是用一些其他的方式來減少這種重復性工作。
根據線程函數的要求,除了可以弄成靜態成員函數外,其實也可以是全局函數。所以其實不定義靜態成員函數也可以在類中創建線程,那重點就是如果把類對象指針、具體執行的函數、需要傳遞的上下文參數這三個類內部的信息傳遞到全局的線程函數中呢?
我想到的方法仍然脫離不了封裝,因為實際的線程函數只接受一個參數,如果要傳遞三個過去,必然需要封裝出一個新的類型來進行傳遞。
所以這裡要在全局線程中去間接調用類中的成員函數,達到讓這個成員函數偽裝成線程函數的目的,首先要做兩點:
1、封裝API函數CreateThread,直接傳遞類對象指針、成員函數、需要的函數進去就能創建線程,並執行到成員函數中去。
2、對於不同的類,方法要一致,這裡就考慮使用模板參數來代替類類型。
有必要在這裡先聲明一下,下面的內容都是我自己根據當時知識程度一步一步深入的過程,所以如果要找最好的解決方案,可以直接看最後的版本,或者直接去我的git上遷移代碼(肯定是我目前最新的)。
有了上面的總結,經過實驗寫出了如下代碼來簡化在類中創建線程,首先上測試代碼,這部分代碼後面不再改變。
#include <iostream> #include "ThreadInClass.h" class CTest { public: void SayHelloInThread(char* nscName) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::ThreadWork, nscName); } DWORD ThreadWork(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "子線程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << "hello, " << (char*)p << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); std::cout << "主線程ID: " << ldwThreadID << std::endl; CloseHandle(lhMainThread); } CTest loTest; char* lscName = "colin"; loTest.SayHelloInThread(lscName); system("pause"); return; }
下面是封裝的類模板,注意我這裡簡化了StartThreadInClass函數沒有列出CreateThread的可用參數,如果需要的話在這裡加上即可。
#include <iostream>
#include<functional>
using std::function;
#include<Windows.h>
namespace ThreadInClass{
// 參數容器模板類,用於存放要調用的類對象、函數、及其參數。(返回值不用存放,因為返回值要作為線程結束狀態,所以必須為DWORD)
template<typename tClassName>
class CRealThreadParamer
{
private:
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;
RealExcuteFun mfExcuteFun;
tClassName* mpoInstance;
PVOID mpoContext;
public:
CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext){
mpoInstance= npThis;
mfExcuteFun= nfWorkFun;
mpoContext= npContext;
}
DWORD Run()
{
return mfExcuteFun(mpoInstance, mpoContext);
}
};
// 線程創建執行類,用於提供創建線程和執行線程的接口封裝
template<typename tClassName>
class CThreadActuator
{
public:
typedef CRealThreadParamer<tClassName> CThreadParamer;
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;
static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext)
{
CThreadParamer* lpoParamer = new CThreadParamer(npThis, nfWorkFun, npContext);
return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr);
}
static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if(nullptr == npParam)
return 0;
else
{
CThreadParamer* lfThreadParamer = (CThreadParamer*)npParam;
DWORD ldwRet= lfThreadParamer->Run();
delete lfThreadParamer;
lfThreadParamer= NULL;
return ldwRet;
}
}
};
}
我這裡用到了std::funciton,而這裡的用法有點類似於使用typddef的方式去聲明一種函數類型。
執行結果如下:
template<typename tClassName> class CRealThreadParamer { private: typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun; NewRealExcuteFun mfExcuteFun; public: CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext) { mfExcuteFun = std::tr1::bind(nfWorkFun, npThis, npContext); } DWORD Run() { return mfExcuteFun(); } };
再細細看了下現在的CRealThreadParamer,構造函數裡直接把所有的參數綁定到了實際執行的函數上,所以內內部只需要保存一個std::function類型了。
等等,既然只有一個std::function類型了,那我之前增加這個類來保存三個類中的參數還有什麼意義,直接傳遞這麼一個類型不就行了嗎?
也就是說,應該是可以在StartThreadInClass的實現中就把所有參數綁定成一個函數調用,然後保存到std::function傳遞給線程函數,線程函數再執行這個函數就行了。
根據上述思路,進一步優化後,代碼簡化了很多很多了,如下:
#include <iostream> #include <functional> using std::function; #include <Windows.h> namespace ThreadInClass{ // 線程創建執行類,用於提供創建線程和執行線程的接口封裝 template<typename tClassName> class CThreadActuator { public: typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun; static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext) { NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(std::tr1::bind(nfWorkFun, npThis, npContext)); return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); } static DWORD WINAPI ThreadDispatch(PVOID npParam) { if(nullptr == npParam) return 0; else { NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam; DWORD ldwRet = (*lfThreadParamer)(); delete lfThreadParamer; lfThreadParamer = NULL; return ldwRet; } } }; }
到了第三版,我再沒有想到還可以簡化的方式了,不過到是發現了,如果在使用的時候,我需要傳入的上下文內容比較多,還是需要自己構造一個結構體來存放上下文信息。因為類中用來做線程函數(間接的)的形式是固定為DWORD(PVOID)類型的。
那麼有沒有一種方式可以讓這個函數可以有任意多個不同類型的參數呢?其實是有的,那就是使用C++11的類可變參模板。
在更改代碼之前,先測試一下直接使用tuple類型作為上下文參數傳遞,因為它可以存放很多不同類型的數據到 一個變量中,從某種程度上也是可以滿足多個上下文參數的。
測試代碼如下:
#include <iostream> #include "ThreadInClass.h" #include <tuple> using std::tr1::tuple; class CTest { public: void SayHelloInThread(char* nscName) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoSayHelloInThread, nscName); } DWORD DoSayHelloInThread(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoSayHelloInThread線程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << "hello, " << (char*)p << std::endl; return 0; } void PrintSumInThread(tuple<int, int>& roAddTupleInfo) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoPrintSumInThread, &roAddTupleInfo); } DWORD DoPrintSumInThread(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoPrintSumInThread線程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::tr1::tuple<int, int>* lpoT1 = (std::tr1::tuple<int, int>*)p; int i = std::tr1::get<0>(*lpoT1); int j = std::tr1::get<1>((*lpoT1)); std::cout << i << " + " << j << " = " << i + j << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); std::cout << "主線程ID: " << ldwThreadID << std::endl; CloseHandle(lhMainThread); } CTest loTest; char* lscName = "colin"; loTest.SayHelloInThread(lscName); tuple<int, int> t1(1, 2); loTest.PrintSumInThread(t1); system("pause"); return; }運行結果:
#include <iostream> #include <functional> using std::tr1::function; using std::tr1::bind; #include <Windows.h> namespace ThreadInClass{ template<typename tClassName, typename... ArgsType> // 變參模板 class CThreadActuator { public: typedef function<DWORD()> NewRealExcuteFun; /// 使用變參模板 static HANDLE StartThreadInClass(tClassName* npThis, function<DWORD(tClassName*, ArgsType...)> nfWorkFun, ArgsType... npArgs) { NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(bind(nfWorkFun, npThis, npArgs...)); return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); } // 真正的線程函數,間接調用類成員函數 static DWORD WINAPI ThreadDispatch(PVOID npParam) { if (nullptr == npParam) return 0; else { NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam; DWORD ldwRet = (*lfThreadParamer)(); delete lfThreadParamer; lfThreadParamer = NULL; return ldwRet; } } }; }
附上測試代碼:
#include <iostream> using std::cout; using std::endl; #include "ThreadInClass.h" #include <tuple> using std::tr1::tuple; using std::tr1::get; class CTest { public: void SayHelloInThread(char* nscName) { ThreadInClass::CThreadActuator<CTest, char* >::StartThreadInClass(this, bind(&CTest::DoSayHelloInThread, std::tr1::placeholders::_1, std::tr1::placeholders::_2), nscName); } DWORD DoSayHelloInThread(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); cout << "DoSayHelloInThread線程ID: " << ldwThreadID << endl; CloseHandle(lhThread); } cout << "hello, " << (char*)p << endl; return 0; }void PrintSumInThread(tuple<int, int>& roAddTupleInfo) { ThreadInClass::CThreadActuator<CTest, tuple<int, int>& >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread, std::tr1::placeholders::_1, std::tr1::placeholders::_2), roAddTupleInfo); } DWORD DoPrintSumInThread(tuple<int, int>& roAddTupleInfo) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); cout << "DoPrintSumInThread線程ID: " << ldwThreadID << endl; CloseHandle(lhThread); } int i = get<0>(roAddTupleInfo); int j = get<1>(roAddTupleInfo); cout << i << " + " << j << " = " << i + j << endl; return 0; } void PrintSumInThread2(int &i, int &j) { ThreadInClass::CThreadActuator<CTest, int, int >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread2, std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), i, j); } DWORD DoPrintSumInThread2(int i, int j) { HANDLE lhThread = GetCurrentThread(); if (NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoPrintSumInThread2線程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << i << " + " << j << " = " << i + j << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); cout << "主線程ID: " << ldwThreadID << endl; CloseHandle(lhMainThread); } CTest loTest; char* lscName = "colin"; loTest.SayHelloInThread(lscName); tuple<int, int> t1(1, 2); loTest.PrintSumInThread(t1); int i = 4; int j = 5; loTest.PrintSumInThread2(i, j); system("pause"); return; }執行結果如下:
CTest::DoPrintSumInThread2, std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), 這麼傳遞的。其中參數占位符根據成員函數實際的參數個數來定。而參數在這裡創建線程的時候也是固定了的,既然如此,我干嘛還用占位符呢?直接傳遞bind(&CTest::DoPrintSumInThread2, this, i, j)不就行了嗎?
經測試上述方案是可行的,仔細想了下,在CreateThread的時候我們需要傳遞一個參數給線程函數,這個參數類型我們定義成了function類型,而上面這種方式其實也是function啊,並且對於任何已經知道要傳遞的參數值的成員函數,都可以通過bind,返回function< DWORD() >類型。這就意味著在線程函數裡,我根本不需要知道其他信息,只需要執行這個function代表的函數就可以了啊。
茅塞頓開,有了如下代碼:
#include <iostream> #include <functional> using std::tr1::function; using std::tr1::bind; #include <Windows.h> namespace ThreadInClass{ class CThreadActuator { public: typedef function<DWORD()> NewRealExcuteFun; // 使用變參模板 static HANDLE StartThreadInClass(function<DWORD()> nfWorkFun) { NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(nfWorkFun); return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); } // 真正的線程函數,間接調用類成員函數 static DWORD WINAPI ThreadDispatch(PVOID npParam) { if (nullptr == npParam) return 0; else { NewRealExcuteFun* lpfWorkFun = (NewRealExcuteFun*)npParam; DWORD ldwRet = (*lpfWorkFun)(); delete lpfWorkFun; lpfWorkFun = NULL; return ldwRet; } } }; }#include <iostream> using std::cout; using std::endl; #include "ThreadInClass.h" class CTest { public: void PrintSumInThread(int &i, int &j) { ThreadInClass::CThreadActuator::StartThreadInClass(bind(&CTest::DoPrintSumInThread, this, i, j)); } DWORD DoPrintSumInThread(int i, int j) { HANDLE lhThread = GetCurrentThread(); if (NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoPrintSumInThread2線程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << i << " + " << j << " = " << i + j << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); cout << "主線程ID: " << ldwThreadID << endl; CloseHandle(lhMainThread); } CTest loTest; int i = 4; int j = 5; loTest.PrintSumInThread(i, j); system("pause"); return; }
呀,不僅去掉了模板,代碼也簡潔了好多倍。
是的,沒錯還有第6版本。那就是使用c++11的std::thread,使用方式就不多說了,我也是看的別人的介紹。跟我前面介紹的方式差不多,不過額外增加了很多功能就是了。不過也不是說我前面寫的都沒用,很大程度上thread內部用的方式其實就是差不多的。
至此再也沒有新的版本了- -。。
最後附上代碼鏈接:https://github.com/monotone/ThreadInClass.git