多线程同步说
先给大家看个恐怖东西.
class TIFF2JPEG {
public:
static void newInstance (tstring & strFilepath, HGLOBAL hGlobal);
static void waitObject ();
protected:
static ULONG_PTR initInstance ();
static DWORD WINAPI ThreadProc (LPVOID lpParameter);
TIFF2JPEG ();
TIFF2JPEG (tstring & str, HGLOBAL hg);
int Translate ();
static ULONG_PTR gdiplusToken;
tstring strFilepath;
HGLOBAL hGlobal;
DWORD dwSize;
};
int ImageInMemory = 0;
deque < TIFF2JPEG * >t2j_list;
CRITICAL_SECTION CriticalSection_t2j_list;
CLSID GifCodec;
bool bThreadDelete = 0;
vector < HANDLE > vThreads;
TIFF2JPEG::TIFF2JPEG ()
{
}
TIFF2JPEG::TIFF2JPEG (tstring & str, HGLOBAL hg):strFilepath (str)
{
hGlobal = hg;
dwSize = GlobalSize (hGlobal);
return;
}
ULONG_PTR TIFF2JPEG::initInstance ()
{
int i;
HANDLE hThread;
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);
GetCodecClsid (L"image/jpeg", &GifCodec);
InitializeCriticalSection (&CriticalSection_t2j_list);
for (i = 0; i < MAX_THREAD; i++) {
hThread = CreateThread
(NULL, 0, TIFF2JPEG::ThreadProc, NULL, 0, NULL);
SetThreadPriority (hThread, THREAD_PRIORITY_IDLE);
vThreads.push_back (hThread);
}
return gdiplusToken;
}
void TIFF2JPEG::newInstance (tstring & strFilepath, HGLOBAL hGlobal)
{
TIFF2JPEG *pt2j = new TIFF2JPEG (strFilepath, hGlobal);
//可以锁定不严格
while (ImageInMemory > MAX_IMAGE)
Sleep (100);
//互斥锁定
EnterCriticalSection (&CriticalSection_t2j_list);
ImageInMemory += pt2j->dwSize;
t2j_list.push_back (pt2j);
LeaveCriticalSection (&CriticalSection_t2j_list);
return;
}
void TIFF2JPEG::waitObject ()
{
bThreadDelete = 1;
WaitForMultipleObjects (vThreads.size (), &(vThreads[0]), TRUE,
INFINITE);
vThreads.clear ();
DeleteCriticalSection (&CriticalSection_t2j_list);
GdiplusShutdown (gdiplusToken);
return;
}
DWORD WINAPI TIFF2JPEG::ThreadProc (LPVOID lpParameter)
{
TIFF2JPEG *pt2j;
Sleep (1000);
while (1) {
//互斥锁定
EnterCriticalSection (&CriticalSection_t2j_list);
if (t2j_list.size () == 0) {//必须严格锁定
LeaveCriticalSection (&CriticalSection_t2j_list);
if (bThreadDelete)
break;
Sleep (100);
continue;
}
pt2j = t2j_list.front ();
t2j_list.pop_front ();
LeaveCriticalSection (&CriticalSection_t2j_list);
printf ("%0.8X:%sn", GetCurrentThreadId (),
pt2j->strFilepath.c_str ());
pt2j->Translate ();
printf ("%0.8X:endn", GetCurrentThreadId ());
EnterCriticalSection (&CriticalSection_t2j_list);
ImageInMemory -= pt2j->dwSize;
delete pt2j;
LeaveCriticalSection (&CriticalSection_t2j_list);
}
return 0;
}
int TIFF2JPEG::Translate ()
{
IStream *pis;
Image *image;
Status stat;
wchar_t wsFilePath[0x400];
if (CreateStreamOnHGlobal (hGlobal, TRUE, &pis) != S_OK) {
ON_ERROR;
return -1;
}
image = Image::FromStream (pis);
stat = image->GetLastStatus ();
if (stat != Ok) {
ON_ERROR;
return -1;
}
MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, strFilepath.c_str (),
-1,
wsFilePath, 0x400);
image->Save (wsFilePath, &GifCodec, NULL);
delete image;
pis->Release ();
return 0;
}
这是我的程序里面写的一个组件,用于后台将TIF转换到jpg。前面还一堆很复杂的程序,以后再说。不过这个小小的程序里面用了蛮多复杂东西的。
首先是纯工厂模式,注意构造函数,我声明为protected。这样其他类就无法构造新对象,整个类就剩下一个静态方法可以构造新对象。
然后是对象静态方法回掉,用一个对象的静态方法作为参数启动线程,然后传入这个类的指针。然后这个线程调用这个指针的某个方法,就相当于多线程启动了这个方法一样。
再下面是生产者消费者模型的一个解法,利用临界区锁定保护一个队列。解法没啥新意,但是要写对还是有难度的。
//互斥锁定
EnterCriticalSection (&CriticalSection_t2j_list);
if (t2j_list.size () == 0) {//必须严格锁定
这里不能在互斥外判断是否有处理对象。因为可能两个线程同时发现有对象,但是进去才发现就一个对象。也不能用测试ImageInMemory的方法来判断,因为有逻辑问题(在单线程里面不会出现的逻辑问题)。单线程中,循环完成后,对象处理好,ImageInMemory也对应变更了。可是多线程的时候,一个线程处理对象的时候,ImageInMemory还是有数值的。所以如果用ImageInMemory来判断是否有处理对象,一样出错。
while (ImageInMemory > MAX_IMAGE)
Sleep (100);
这个则是一个忙等待的技巧,不过这里用问题不大而已。
EnterCriticalSection (&CriticalSection_t2j_list);
ImageInMemory -= pt2j->dwSize;
delete pt2j;
LeaveCriticalSection (&CriticalSection_t2j_list);
这里就非常有意思了,为什么要这么写?ImageInMemory的-=操作是这么执行的。ImageInMemory读入到寄存器里面,执行减法,然后写入内存。问题在于,读入后是否有人也要修改他的值。
bThreadDelete = 1; WaitForMultipleObjects (vThreads.size (), &(vThreads[0]), TRUE, INFINITE);
这里也有一个小技巧,所有线程都会使用互斥区。但是我要关闭互斥区,怎么办呢?简单方法就是关闭所有线程,只是太暴力了。所以用bThreadDelete标识来让线程自主关闭。然后等待线程的关闭。注意这里传入的时候,用了vector来管理所有的线程列表。我前几天才知道vector可以这么用的。
还有一个不算技巧的技巧,在main函数前自动运行一段代码和在main后自动运行一段代码。定义一个包装类,然后写一个构造函数,写一个析构函数。在类的cpp里面定义一个这个对象。构造就会自动在main前执行,析构就在main之后。CWinApp就用了这个手法。只是比java的static用法更加烦琐而已。