Shell's Home

Jan 19, 2007 - 2 minute read - Comments

多线程同步说

先给大家看个恐怖东西.

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用法更加烦琐而已。

Tags: c program

全局初始化 TTS杂论

comments powered by Disqus