WIndows编程之线程池的使用

Posted $逝水无痕$

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WIndows编程之线程池的使用相关的知识,希望对你有一定的参考价值。

不得不说,做C++服务器程序开发,要是不理解线程池,不懂线程池,做C++服务器端的程序就没有任何意义。特别就是上次我因为理解错了线程池而做错了一件事,而被指导人批了一顿,至今记忆犹新,所以趁着周末学了下线程池的使用,小有成绩。

 

先看一种比较简单的线程池的实现。

 1 #include <windows.h>
 2 #include <string>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 #define BEGINTHREAD(Fun,Param)  CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Fun,Param,0,NULL);
 8 
 9 DWORD WINAPI ThreadProc(LPVOID lpParameter);
10 
11 int main()
12 {
13 
14     string words[10] = { "I am a gay.....", "I am a gay, too", "Let us do something exceting", "ah, Do you want to do it in the forest or in the bed?", "neither, I want to do it on the street. haha",
15                         "I heard you like music.", "Yeah, Just like the light music.", "For example...", "bosty music and so on", "ah, you mean that is the light music...." };
16 
17     for (int i = 0; i < 10; i++)
18     {
19         //下面的代码演示了,使用CreateThread和QueueUserWorkItem,实际效果
20         //是一样的,当然线程不多的情况下如此,如果线程很多时一定要使用QueueUserWorkItem
21         QueueUserWorkItem(ThreadProc, (PVOID)&words, WT_EXECUTELONGFUNCTION);
22 
23         //显示使用CreateThread来创建多个线程的效果
24         //BEGINTHREAD(ThreadProc, (LPVOID)&words[i]);
25     }
26 
27     while (true);
28 
29     return 0;
30 }
31 
32 //该函数可以由CreateThread的线程启动,也可以使用QueueUserWorkItem线程池中的线程启动
33 DWORD WINAPI ThreadProc(LPVOID lpParameter) {
34     while (true)
35     {
36         std::string word = *(std::string*)lpParameter;
37         std::cout << "线程[ID:" << GetCurrentThreadId() << "]说:" << word << std::endl;
38         Sleep(5 * 1000);
39     }
40     return 0;
41 }

这里我们没有使用到任何线程池的信息,就可以直接使用线程池了,因为我们使用的方法里头默认给我们创建了一个线程池,但是在实际服务器的开发中,我们必须设置线程池的大小,必须自定义线程池的属性,就必须设置线程池的信息,这时就需要我们手动调用来创建线程池。

 

运行截图:

从这张图片里面也可以看出线程池里面执行的情况与我们自定义多线程还是有很大区别的,多线程里面的输出语句是不会被打断的,但是再看看我们的程序,连输出语句也被打断了。(具体多线程里面的输出语句为什么没有被打断,目前我也不清楚,按理是应该被打断的)

 

下面介绍我在企业遇到的一种写法(简版)

#include <iostream>
#include <Windows.h>

using namespace std;

int nFinish = 0;

VOID NTAPI WorkThread(PTP_CALLBACK_INSTANCE Instance, PVOID param)
{
    //目前获取值有点问题,不写出来了
    cout << "The ThreadId is : " << GetCurrentThreadId() << endl;
    delete param;
    nFinish++;
}

void main()
{
    int i = 0;
//    while (i < 10)
//    {
//这里的参数的含义 第一个是线程的回调函数,第二个是你传给回调函数的参数,第三个是回调环境
if (TrySubmitThreadpoolCallback(WorkThread, (PVOID)string("You are a Gay。。。。").c_str(), NULL)) { cout << "没错,你就是基佬" << endl; } i++; // } // while (nFinish < 10) // { // Sleep(1000); // } cout << "哈哈,线程执行完了。。" << endl; }

运行截图:(注意执行顺序,哪怕这个过程是你在线程里面开的,结果也可能是这样的)

这里使用到了一个函数:TrySubmitThreadpoolCallback,这个函数的作用就是向线程池提交任务,由于这里采用了默认的线程池,只有一个线程,所以只能执行一个。 

 

因为直接提交线程池有可能导致失败而导致线程任务不会执行(TrySubmitThreadpoolCallback,你看到try就应该明白这是一种不可靠的方法),所以很多人建议这么做,新建一个任务项,我们将任务项提交到线程池。

#include <iostream>
#include <Windows.h>

using namespace std;

VOID CALLBACK WorkThread1(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
{
    for (int i = 0; i<5; i++)
    {
        cout << "this is a WorkCallback fun" << endl;
        Sleep(1000);
    }
}

int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
    PTP_WORK pw = CreateThreadpoolWork(WorkThread1, NULL, NULL);
    SubmitThreadpoolWork(pw);
    //给线程足够多的时间启动
    Sleep(2000);
    //TRUE,试图取消提交的工作项。如果工作项已启动,则等待;
    //FALSE,当前线程挂起,直到工作项完成
    WaitForThreadpoolWorkCallbacks(pw, TRUE);
    CloseThreadpoolWork(pw);

    return 0;
}

这里的任务就都会被提交到线程池里面去。。。

 

运行截图:

 

内核对象被触发的时候,进行调用回调函数

#include <iostream>
#include <Windows.h>

using namespace std;

VOID CALLBACK WorkThread3(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult)
{
    for (int i = 0; i < 10; i++)
    {
        cout << "" << GetCurrentThreadId() << "】this is a WaitCallback fun" << endl;
        Sleep(1000);
    }
}

int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
    PTP_WAIT pw = CreateThreadpoolWait(WorkThread3, NULL, NULL);
    HANDLE h = CreateEvent(NULL, TRUE, FALSE, NULL);
    //关联内核对象
    SetThreadpoolWait(pw, h, NULL);
    //0表示不等待,NULL表示无限等待,负值表示相对时间,正值表示绝对时间
    Sleep(1000);
    //触发内核对象
    SetEvent(h);
    Sleep(1000);
    WaitForThreadpoolWaitCallbacks(pw, TRUE);
    CloseThreadpoolWait(pw);
    CloseHandle(h);

    return 0;
}

运行截图:

 

在最后记录一下我在企业里面遇到的情况,这里是简版,有很多的安全问题,不想管了,累死了,忙活了两天才还原出来。我的周末啊,我的王者啊。。。。

//这是.h文件的内容
#pragma once
#include <iostream>
#include <Windows.h>

using namespace std;

#define PARAMDATASIZE   250

typedef struct CALLBACKDATA
{
    HANDLE hEvent;
    int NO;

    CALLBACKDATA()
    {
        hEvent = NULL;
        NO = -1;
    }
}*PCALLBACKDATA;

class CThreadPool
{
private:
    PTP_POOL             m_pThreadPool;
    PTP_CLEANUP_GROUP    m_pCleanupGroup;
    TP_CALLBACK_ENVIRON  m_CallbackEnvironment;

    HANDLE               *m_hThreadEvent;
    unsigned int          m_nThreadPoolSize;

public:
    bool InitPool();
    static void NTAPI CallBackFunc(PTP_CALLBACK_INSTANCE Instance, PVOID pContext);
    void start();
};
//这是.cpp文件的内容
#include "head.h"

bool CThreadPool::InitPool()
{
//创建一个线程池
    m_pThreadPool = CreateThreadpool(NULL);
//线程池的规格
    m_nThreadPoolSize = 10;
    if (m_pThreadPool == NULL)
    {
        cout << "CreateThreadpool failed." << endl;
        return false;
    }
//最大允许的线程数
    SetThreadpoolThreadMaximum(m_pThreadPool, m_nThreadPoolSize);
//最小允许的线程数
    if (!SetThreadpoolThreadMinimum(m_pThreadPool, 1))
    {
        cout << "SetThreadpoolThreadMinimum failed." << endl;
        return false;
    }
//这里设置线程的清理
    m_pCleanupGroup = CreateThreadpoolCleanupGroup();
    if (m_pCleanupGroup == NULL)
    {
        cout << "CreateThreadpoolCleanupGroup Failed." << endl;
        return false;
    }
//初始化线程池的回调环境
    InitializeThreadpoolEnvironment(&m_CallbackEnvironment);
    SetThreadpoolCallbackPool(&m_CallbackEnvironment, m_pThreadPool);
//设置线程的清理环境
    SetThreadpoolCallbackCleanupGroup(&m_CallbackEnvironment, m_pCleanupGroup, NULL);
//创建线程的事件,让线程池里的线程随时工作
    m_hThreadEvent = new HANDLE[m_nThreadPoolSize];
    for (int i = 0; i < m_nThreadPoolSize; ++i)
    {
        m_hThreadEvent[i] = CreateEvent(NULL, TRUE, TRUE, NULL);
    }
}

//回调函数,仅输出一句话
void NTAPI CThreadPool::CallBackFunc(PTP_CALLBACK_INSTANCE Instance, PVOID pContext)
{
    CThreadPool* pThreadPool = new CThreadPool();

    PCALLBACKDATA pData = (PCALLBACKDATA)pContext;
    if (NULL == pData)
    {
        cout << "pData is NULL." << endl;
        delete pData;
        pData = NULL;
        pContext = NULL;;
        return;
    }

    if (NULL == pThreadPool)
    {
        cout << "pThreadPool is NULL." << endl;
        delete pData;
        pData = NULL;
        pContext = NULL;;
        return;
    }

    cout << "" << GetCurrentThreadId() << "" << "the param is : " << pData->NO << endl;

    SetEventWhenCallbackReturns(Instance, pData->hEvent);
    //枷锁,我不想写了,错就错吧

    delete pData;
    pData = NULL;
    pContext = NULL;
}

//安排线程进行执行
void CThreadPool::start()
{
    CThreadPool* pThreadPool = new CThreadPool();

    //初始化10个线程
    for (int j = 0; j < m_nThreadPoolSize; ++j)
    {
        SetEvent(m_hThreadEvent[j]);
    }

//有100个任务等待执行
    int i = 0;
    while (i < 100)
    {
        DWORD dwRet = WaitForMultipleObjects(m_nThreadPoolSize, m_hThreadEvent, FALSE, INFINITE);
        switch (dwRet)
        {
        case WAIT_TIMEOUT:    break;
        case WAIT_FAILED:  return;
//如果线程申请成功
        default:
        {
            int nIndex = dwRet - WAIT_OBJECT_0;
//让被申请的线程不在发送信号(发送信号的含义就是这个线程处于空闲)
            ResetEvent(m_hThreadEvent[nIndex]);
//自定义结构体,目的是将参数传递给回调函数
            PCALLBACKDATA pData1 = new (std::nothrow)CALLBACKDATA;
            if (NULL != pData1)
            {
                ++i;
                pData1->hEvent = m_hThreadEvent[nIndex];
                pData1->NO = i;
//提交任务到线程池里面    TrySubmitThreadpoolCallback((PTP_SIMPLE_CALLBACK)CallBackFunc, pData1, &m_CallbackEnvironment);
            }
//继续检测同一时期还在发送信号的线程
            while (++nIndex < m_nThreadPoolSize)
            {
                dwRet = WaitForMultipleObjects(m_nThreadPoolSize - nIndex, &m_hThreadEvent[nIndex], FALSE, 0);
                switch (dwRet)
                {
                case WAIT_TIMEOUT:    break;
                case WAIT_FAILED:
                {
                    cout << "WAIT_FAILED" << endl;
                    return;
                }
                default:
                {
                    nIndex = nIndex + dwRet - WAIT_OBJECT_0;
                    ResetEvent(m_hThreadEvent[nIndex]);
                    if (i < 100)
                    {
                        PCALLBACKDATA pData2 = new (std::nothrow)CALLBACKDATA;
                        if (NULL != pData2)
                        {
                            ++i;
                            pData2->hEvent = m_hThreadEvent[nIndex];
                            pData2->NO = i;
                            TrySubmitThreadpoolCallback((PTP_SIMPLE_CALLBACK)CallBackFunc, pData2, &m_CallbackEnvironment);
                        }
                    }
                    
                }
                break;
                }
            }
         }
        break;
        }
    }
}

void main()
{
    CThreadPool* pool = new CThreadPool();
    pool->InitPool();
    pool->start();

    while (true);
}

执行截图:

 

因为TrySubmitThreadpoolCallback可能失败,大家可以尝试使用提交任务项的方法进行执行这个程序,结果应该差不多。

最后的无线循环是我自己加的,实际过程需要检测线程执行完的条数,然后判断是否继续等待或者返回函数。

 

累死了,还有3个小时,赶紧去王者劝退几个小学生。。

这次就到这里了,,第一次到博客园写博客,除了编辑器用的不习惯,没有广告是我最喜欢的。祝博客园越走越远。。

以上是关于WIndows编程之线程池的使用的主要内容,如果未能解决你的问题,请参考以下文章

java之并发编程线程池的学习

java 核心编程——线程之线程池(ExecutorService)

.NET编程之线程池内幕

并发编程之:深入解析线程池

十:并发编程之Executor线程池原理与源码解读

多线程编程—线程池的实现