代码阅读 1

本文详细解析了一个程序如何通过主进程与多个子进程(worker进程)进行通信的机制,包括初始化配置、守护进程设置、信号处理及父子进程间的通信套接口(管道)建立等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:main函数的解析

1:初始化:配置文件模块初始化和守护程序(CStartMotor类)的初始化

    dts_uint32 uiRet = CStartMotor::GetStartMotor().Init( argc,argv );
    if ( uiRet == -1 )
    {
        return -1;
    }

    g_iCUPNum = sysconf( _SC_NPROCESSORS_ONLN );

    //如果是daemon模式,本进程变为守护进程
    uiRet = CStartMotor::GetStartMotor().DeamonStart();
    if ( uiRet == -1 )
    {
        return -1;
    }

    uiRet = CStartMotor::GetStartMotor().WatchDog();
    if ( uiRet == -1 )
    {
        return -1;
    }

    //设置捕获、屏蔽和不屏蔽的信号
    uiRet = CStartMotor::GetStartMotor().SetSigaction();
    if ( uiRet == -1 )
    {
        return -1;
    }

    //屏蔽所有关心的信号,让内核暂时保留信号,不要发给程序,因为这时候程序不系统被打断。
    uiRet = CStartMotor::GetStartMotor().Block_Specified_Signals();
    if ( uiRet == -1 )
    {
        return -1;
    }


    CConfigFileIni stConfigFileIni;

    //读取配置文件
    if ( os_read_file( &stConfigFileIni ) == -1 )
    {
        return -1;
    }

    //初始化程序基础
    if ( os_init_cycle( &stConfigFileIni ) == -1 )
    {
        return -1;
    }

2:
父子进程的通信套接口(管道)初始化

    //创建进程间通信的互通套接口通道 zhb
    if ( CStartMotor::GetStartMotor().SetSigPipe() == -1 )
    {
        GAL_ERROR << "set sig pipe fail";
        GAL_ERROR_PRINT << "set sig pipe fail";

        return -1;
    }
    epoll_event event;
    event.data.fd = CStartMotor::GetStartMotor().m_iSigFd[ 0 ];//父进程的接收套接口;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl( iEpollfd, EPOLL_CTL_ADD, event.data.fd, &event );

    GAL_INFO_PRINT << "m_iSigFd[ 0 ]==" << CStartMotor::GetStartMotor().m_iSigFd[ 0 ] << "; m_iSigFd[1]==" << CStartMotor::GetStartMotor().m_iSigFd[ 1 ];

3: main函数的主体循环
3.1: fork出了所有的worker子进程,并初始化。

        //这是个重要的函数,fork出了所有的工作进程(子进程); zhb
        if ( g_Monitor::Instance().Init( &stConfigFileIni, iEpollfd ) == -1 )
        {
            break;
        }

3.2: 处理来自子进程消息和外部信号的主体循环

        for ( ;; )
        {
            //通过epoll来接收子进程发送来的消息。zhb
            iEvCnt = epoll_wait( iEpollfd, events, 2048, iWaitTimeMs );
            //CDTS_Auxiliary::DTS_unblockterm_sig();

            if ( iEvCnt < 0 )
            {
                if ( CDTS_Auxiliary::GetSockErr() != EINTR )
                {
                    GAL_ERROR_PRINT << "sig epoll_wait error : %m";
                    usleep( 1000 );
                }

                GAL_DEBUG_PRINT << "sig epoll_wait trigger";

                continue;
            }

            if ( iEvCnt == 0 )
            {
                continue;
            }

            //fprintf( stderr,"monitor epoll_wait %d\n",iEvCnt );

            for ( int i = 0; i < iEvCnt; ++i )
            {
                iLiveFd = events[ i ].data.fd;
                if ( iLiveFd == CStartMotor::GetStartMotor().m_iSigFd[ 0 ] )
                {
                    if ( events[ i ].events & EPOLLIN )
                    {
                        iRet = read( CStartMotor::GetStartMotor().m_iSigFd[ 0 ],iSignals,sizeof(iSignals) );
                        if ( iRet <= 0 )
                        {
                            continue;
                        }
                        else
                        {
                            char* action;
                            GAL_DEBUG_PRINT << "parent sig fd read size = " << iRet/sizeof(int);
                            GAL_DEBUG << "parent sig fd read size = " << iRet/sizeof(int);
                            for ( int i = 0 ; i < iRet/sizeof(int); ++i )
                            {
                                action = NULL;
                                GAL_DEBUG_PRINT << "Signals [ " << i << " ] = " << iSignals[ i ];
                                GAL_DEBUG << "Signals [ " << i << " ] = " << iSignals[ i ];
                                switch ( iSignals[ i ] )
                                {
                                case SIGQUIT:
                                    CStartMotor::GetStartMotor().m_iRun = 0;
                                    action = "shutting down";
                                    break;
                                case SIGTERM:
                                case SIGINT:
                                    CStartMotor::GetStartMotor().m_iTerminate = 1;
                                    action = "exiting";
                                    break;
                                case SIGHUP:
                                    CStartMotor::GetStartMotor().m_iReconfigure = 1;
                                    action = "reconfiguring";
                                    break;
                                case SIGUSR1:
                                    CStartMotor::GetStartMotor().m_iReopen = 1;
                                    action = "reopening";
                                    break;
                                case SIGALRM:
                                    CStartMotor::GetStartMotor().m_iSigalrm = 1;
                                    break;
                                case SIGIO:
                                    CStartMotor::GetStartMotor().m_iSigio = 1;
                                    break;
                                case SIGCHLD:
                                    CStartMotor::GetStartMotor().m_iReap = 1;
                                    if ( g_Monitor::Instance().m_iWorkProcessSum > 0 )
                                    {
                                        action = "chile process close";
                                        g_Monitor::Instance().GetWorkStatus();
                                    }
                                    break;
                                default:
                                    break;
                                }

                                GAL_INFO << "parent signal " << iSignals[ i ] << " received " << action;
                                GAL_INFO_PRINT << "parent signal " << iSignals[ i ] << " received " << action;
                            }
                        }
                    }
                }
                else
                {
                    //接收子进程传过来的信令
                    g_Monitor::Instance().Notify( iLiveFd );
                }
            }

}

二: 子进程(worker)进程解析

1: 子进程的创建和初始化
CMonitor 类是子进程(worker)进程的的处理类

        //子进程的创建 zhb
        for ( int i = 0; i < m_iWorkProcessSum; ++i )
        {
            Spawn_Process( i,pstConfigFileIni );
        }

2:工作进程的解析

2.1: Spawn_Process函数,fork子进程(worker进程前的初始化)前的初始化。

    pid_t tPid = fork();

    switch ( tPid )
    {
    case -1:
        {
            GAL_ERROR_PRINT << "fork() failed";
            GAL_ERROR << "fork() failed";

            CDTS_Auxiliary::DTS_socket_close( m_stProcess[ iSpawn ].iChannelFd[ 0 ] );
            m_stProcess[ iSpawn ].iChannelFd[ 0 ] = -1;
            CDTS_Auxiliary::DTS_socket_close( m_stProcess[ iSpawn ].iChannelFd[ 1 ] );
            m_stProcess[ iSpawn ].iChannelFd[ 1 ] = -1;
        }
        return -1;
    case 0:
        {
            //子进程
            m_iPid = getpid();

            os_worker_process_cycle( iSpawn,pstConfigFileIni );
            //worker_process_cycle( m_stProcess[ iSpawn ].iChannelFd[ 1 ] );
        }
        break;
    default:
        break;
    }

2.2: worker进程的初始化函数

    //worker进程的初始化,也是很关键的哦;zhb
    if ( g_Worker::Instance().Init( iSpawn, g_Monitor::Instance().m_stProcess[ iSpawn ].iChannelFd[ 1 ],pstConfigFileIni,iEpollfd ) == -1 )
    {   
        GAL_ERROR_PRINT << "work process " << iSpawn << " init error";
        GAL_ERROR << "work process " << iSpawn << " init error";

        exit( 2 );
    }

这是个关键函数,调用了CDockMgr类中的初始化函数, CDockMgr类统一处理各种类型(epr,ts,rtp,tsOverHttp)的码流创建和删除。

    if ( g_DockMgr::Instance().Init( pstConfigFileIni ) == -1 )
    {
        GAL_ERROR << "work process " << m_iSlot << " dockmgr init fail";
        GAL_ERROR_PRINT << "work process " << m_iSlot << " dockmgr init fail";

        return -1;
    }

重要的函数,对各种码流的接收机制进行初始化。libev实例化(也就创建了处理线程,libev初始化,设置好了回调函数)。 下面以epr为例子,看看回调函数怎么设置的。

dts_uint32 CDockMgr::Init( CConfigFileIni* pstConfigFileIni )
{
    GAL_DEBUG_PRINT << " CDockMgr::Init...000!";

    char szEprConfig[ 1024 ] = { 0 };
    if ( pstConfigFileIni->GetParaValue( g_szEPRSeq, g_szEPRConfigPara, szEprConfig," " ) == -1 )
    {   
        GAL_ERROR << "read [ " << g_szEPRSeq << " ] " << g_szEPRConfigPara << " fail";
        GAL_ERROR_PRINT << "read [ " << g_szEPRSeq << " ] " << g_szEPRConfigPara << " fail";
        return -1;
    }

    if ( g_EPRMgr::Instance().Init( szEprConfig ) == -1 )
    {
        GAL_ERROR << "create EPR fail";
        GAL_ERROR_PRINT << "create EPR fail";
        return -1;
    }

    char szRtprConfig[ 1024 ] = { 0 };
    if ( pstConfigFileIni->GetParaValue( g_szRTPRSeq,g_szRTPRConfigPara,szRtprConfig," " ) == -1 )
    {
        GAL_ERROR << "read [ " << g_szRTPRSeq << " ] " << g_szRTPRConfigPara << " fail";
        GAL_ERROR_PRINT << "read [ " << g_szRTPRSeq << " ] " << g_szRTPRConfigPara << " fail";
        return -1;
    }

    GAL_DEBUG_PRINT << " CDockMgr::Init...111!szRtprConfig==" << szRtprConfig;

    if ( g_RtprMgr::Instance().Init( szRtprConfig ) == -1 )
    {
        GAL_ERROR << "create RTPR fail";
        GAL_ERROR_PRINT << "create RTPR fail";
        return -1;
    }

    char szTSRConfig[ 1024 ] = { 0 };
    if ( pstConfigFileIni->GetParaValue( g_szTSRSeq,g_szTSRConfigPara,szTSRConfig," " ) == -1 )
    {
        GAL_ERROR << "read [ " << g_szTSRSeq << " ] " << g_szTSRConfigPara << " fail";
        GAL_ERROR_PRINT << "read [ " << g_szTSRSeq << " ] " << g_szTSRConfigPara << " fail";
        return -1;
    }

    GAL_DEBUG_PRINT << " CDockMgr::Init...222!szTSRConfig==" << szTSRConfig;

    if ( g_TSRMgr::Instance().Init( szTSRConfig ) == -1 )
    {
        GAL_ERROR << "create TSR fail";
        GAL_ERROR_PRINT << "create TSR fail";
        return -1;
    }

    char szTOHRConfig[ 1024 ] = { 0 };
    if ( pstConfigFileIni->GetParaValue( g_szTSOVERHTTPRSeq,g_szTSOVERHTTPRConfigPara,szTOHRConfig," " ) == -1 )
    {
        GAL_ERROR << "read [ " << g_szTSOVERHTTPRSeq << " ] " << g_szTSOVERHTTPRConfigPara << " fail";
        GAL_ERROR_PRINT << "read [ " << g_szTSOVERHTTPRSeq << " ] " << g_szTSOVERHTTPRConfigPara << " fail";
        return -1;
    }

    GAL_DEBUG_PRINT << " CDockMgr::Init...333!szTOHRConfig==" << szTOHRConfig;

    if ( g_TOHRMgr::Instance().Init( szTOHRConfig ) == -1 )
    {
        GAL_ERROR << "create TOHR fail";
        GAL_ERROR_PRINT << "create TOHR fail";
        return -1;
    }

    char szTPushConfig[ 1024 ] = { 0 };
    if ( pstConfigFileIni->GetParaValue( g_szTPushSeq,g_szTPushConfigPara,szTPushConfig," " ) == -1 )
    {
        GAL_ERROR << "read [ " << g_szTPushSeq << " ] " << g_szTPushConfigPara << " fail";
        GAL_ERROR_PRINT << "read [ " << g_szTPushSeq << " ] " << g_szTPushConfigPara << " fail";
        return -1;
    }

    GAL_DEBUG_PRINT << " CDockMgr::Init...444!szTPushConfig==" << szTPushConfig;

    if ( g_InternalTransferMgr::Instance().Init( szTPushConfig ) == -1 )
    {
        GAL_ERROR << "create TPush fail";
        GAL_ERROR_PRINT << "create TPush fail";
        return -1;
    }

    return 0;
}

libev初始化了,设置好回调函数,处理线程池也在这里创建好了。下面是libev的处理过程。

    dts_uint32 CEPR_ReceiveMgr::Init( const char* pszConfig, const EPR_INFO_CALLBACK& f )
    {
        GAL_DEBUG_PRINT << "EPR Receive init";
        GAL_DEBUG << "EPR Receive init";

        m_strConfig = pszConfig;

        m_EPR_INFO_CALLBACK = f;//设置好了 函数指针m_EPR_INFO_CALLBACK 的具体地址(回调函数)

        CConfigFileIni stConfigFileIni;
        if ( stConfigFileIni.OpenFile( pszConfig ) == -1 )
        {
            GAL_ERROR << "RTPR read config file fail " << pszConfig;
            GAL_ERROR_PRINT << "RTPR read config file fail " << pszConfig;
            return -1;
        }

        int iIdleTime = 0;
        stConfigFileIni.GetParaValue( g_ListenerSeq,g_IdleTimePara,iIdleTime,30 );
        iIdleTime *= 1000;

        int iThread = 0;
        if ( stConfigFileIni.GetParaValue( g_IOThreadSeq,g_ThreadNumPara,iThread,0 ) == -1 )
        {
            CDTS_Auxiliary::GetCPUNum(iThread);
            if ( iThread == 0 )
            {
                iThread = 32;
            }
        }

        GAL_DEBUG_PRINT << "EPR create incoming " << iThread << " thread , IdleTime = " << iIdleTime;

        //创建了libev的处理线程,也设置好了回调函数 CEPR_ReceiveMgr::ClearReceiver,这个处理函数是空函数。zhb
        if ( m_stIncomingThread.Init( iThread, GFD::bind( &CEPR_ReceiveMgr::ClearReceiver, this ),iIdleTime ) == -1 )
        {
            GAL_ERROR << "EPR create incoming " << iThread << " thread error";
            GAL_ERROR_PRINT << "EPR create incoming " << iThread << " thread error";

            return -1;
        }

        GAL_DEBUG_PRINT << "EPR Receive init success";
        GAL_DEBUG << "EPR Receive init success";

        return 0;
    }

libev创建好的线程,进入处理线程循环。

void* PollThreadEntry( void* pThread )
{
    CBasicThread* pBasicThread = static_cast< CBasicThread* >( pThread );
    if ( pBasicThread == NULL )
    {
        return (void*)-1;
    }
    return (void*)( pBasicThread->ThreadLoop() );
}
dts_uint32 CPollThread::ThreadLoop()
{
    GAL_DEBUG_PRINT << "Notify...777 ! ";

    int iCnt = m_stEventCnt.GetAndAdd( 1 );
    if ( iCnt >= CBasicThread::GetThreadNum() )
    {
        GAL_ERROR_PRINT << "poll thread create event loop";
        //GAL_ERROR << "poll thread create event loop";
        return -1;
    }

    //GAL_DEBUG_PRINT << "poll thread pid[ " << getpid() << " ] tid = " << (int)(gettid()) << "  running ...";

    while ( m_iState == DTS_THREAD_STATE_RUN )
    {
        //主要处理是在EpollWait函数。zhb
        if ( m_vecEventLoop[ iCnt ]->EpollWait( m_iEpollWaitTimeMs ) == 0 )
        {
            if ( m_iState != DTS_THREAD_STATE_RUN )
            {
                break;
            }
            //这个回调函数设置为了空函数。zhb
            m_fThreadLoop_Callback();
        }
    }

    return 0;
}
dts_uint32 CEventLoop::EpollWait( int iWaitTimeMs )
{
    GAL_DEBUG_PRINT << "Notify...888 ! ";

    int iEvCnt = epoll_wait( m_iEpollFd, &(*m_stEvents.begin()), static_cast< int >( m_stEvents.size() ), iWaitTimeMs );
    if ( iEvCnt < 0 )
    {
        if ( CDTS_Auxiliary::GetSockErr() != EINTR )
        {
            GAL_ERROR_PRINT << "epoll_wait error : %m";
            usleep( 1000 );
        }

        return -1;
    }

    if ( iEvCnt == 0 )
    {
        GAL_DEBUG_PRINT << "epoll_wait timeput";
        GAL_DEBUG_PRINT << "Notify...888...111 ! ";

    }

    CBasicSocket* pstSocket = NULL;
    dts_uint32 uiRet;
    for ( int i = 0; i < iEvCnt; ++i )
    {
        pstSocket = static_cast< CBasicSocket* >( m_stEvents[ i ].data.ptr );
        if ( m_stEvents[ i ].events & ( EPOLLHUP | EPOLLERR ) )
        {
            GAL_ERROR_PRINT << "epoll_wait events[ " << m_stEvents[ i ].events << " ] EPOLLHUP | EPOLLERR fd = " << pstSocket->GetFd() << " error = " << errno;
            if ( pstSocket->Release() == 0 )
            {
                delete pstSocket;
            }
            continue;
        }
        else
        {
            GAL_DEBUG_PRINT << "Notify...999! ";

            //主要在这里处理呢 zhb
            uiRet = pstSocket->Notify( &m_stEvents[ i ] );
            if ( uiRet == -1 )
            {
                GAL_ERROR_PRINT << "epoll_wait events Notify -1";
                if ( pstSocket->Release() == 0 )
                {
                    delete pstSocket;
                }
                continue;
            }
        }
    }

    if ( iWaitTimeMs != -1 )
    {
        time_t tTimeNow = time( NULL );
        //GAL_DEBUG_PRINT << "time check " << tTimeNow << " - " << m_tLastCheckIdle << " = "<< tTimeNow - m_tLastCheckIdle << " ? " << (iWaitTimeMs >> 1);
        if ( ( tTimeNow - m_tLastCheckIdle ) > ( iWaitTimeMs / 2000 ) )
        {
            //GAL_DEBUG_PRINT << "time check " << tTimeNow << " - " << m_tLastCheckIdle << " = "<< tTimeNow - m_tLastCheckIdle << " > " << (iWaitTimeMs / 2000 );

            m_tLastCheckIdle = tTimeNow;

            COSQueueNode< CBasicSocket* >* pstCur;
            COSQueueNode< CBasicSocket* >* pstNext;
            OSQUEUE_FOR_EACH_SAFE( pstCur,pstNext,&m_stBusyPollerHead )
            {
                //GAL_DEBUG_PRINT << tTimeNow << " - " << pstCur->m_stContent->GetDealTime() << " = " << tTimeNow - pstCur->m_stContent->GetDealTime();

                if ( pstCur->m_stContent != NULL &&
                    (tTimeNow - pstCur->m_stContent->GetDealTime()) > (iWaitTimeMs / 1000) )
                {
                    GAL_ERROR << "client timeout " << tTimeNow << " - " << pstCur->m_stContent->GetDealTime() << " = " << tTimeNow - pstCur->m_stContent->GetDealTime() << " > " << (iWaitTimeMs/1000);
                    GAL_ERROR_PRINT << "client timeout " << tTimeNow << " - " << pstCur->m_stContent->GetDealTime() << " = " << tTimeNow - pstCur->m_stContent->GetDealTime() << " > " << (iWaitTimeMs/1000);

                    if ( pstCur->m_stContent->Release() == 0 )
                    {
                        delete pstCur->m_stContent;
                    }
                }
            }
        }
    }

    return 0;
}
//EPR的就到这里来处理了。 zhb
dts_uint32 CEPR_Receive::Notify( epoll_event* pstEv )
{
    if ( pstEv->events & ( EPOLLERR | EPOLLHUP ) )
    {
        GAL_ERROR << "EPR receiver epoll error events = " << pstEv->events;
        GAL_ERROR_PRINT << "ts receiver epoll error events = " << pstEv->events;
        return -1;
    }

    if ( !(pstEv->events & EPOLLIN) )
    {
        GAL_ERROR << "EPR receiver epoll unexpect events = " << pstEv->events;
        GAL_ERROR_PRINT << "ts receiver epoll unexpect events = " << pstEv->events;
        return -2;
    }

    if ( GetEprClientNum() >= m_iMaxSession )
    {

        GAL_INFO << "EPR accept client >= " << m_iMaxSession;
        GAL_INFO_PRINT << "EPR accept client >= " << m_iMaxSession;

        return 0;
    }

    //GAL_DEBUG_PRINT << "epoll events = EPOLLIN";

    struct sockaddr_in stPeerAddr;
    socklen_t tPeerSize = sizeof( stPeerAddr );
    DTSSocket iNewSocket = DTS_SOCKET_NULL;
    CEPR_Client* pstEprClient;
    DTSSockInfo stSockInfo;

    stSockInfo.iType = GetSocketInfo()->iType;
    stSockInfo.stLocal = GetSocketInfo()->stLocal;

    for ( int i = 0; ( m_stDTSListenAttr.iAcceptNum == -1 || i < m_stDTSListenAttr.iAcceptNum ); ++i )
    {
        switch ( m_stDTSListenAttr.iType )
        {
        case DTS_SOCKET_TYPE_STREAM:
            {
                tPeerSize = sizeof( stPeerAddr );
                iNewSocket = accept( GetFd(),(struct sockaddr*)&stPeerAddr,&tPeerSize );
                if ( iNewSocket < 0 )
                {
                    if ( CDTS_Auxiliary::GetSockErr() != EAGAIN && CDTS_Auxiliary::GetSockErr() != EINTR )
                    {
                    }
                    return 0;
                }

                //这是处理一个客户端; zhb
                pstEprClient = new CEPR_Client( m_pCode );
                if ( pstEprClient == NULL )
                {
                    return 0;
                }

                stSockInfo.iSocket = iNewSocket;
                stSockInfo.stRemote.uiHost = stPeerAddr.sin_addr.s_addr;
                stSockInfo.stRemote.usPort = stPeerAddr.sin_port;

                GAL_DEBUG_PRINT << "EPR accept client PeerAddress = " << inet_ntoa( stPeerAddr.sin_addr ) << " : " << ntohs( stPeerAddr.sin_port );
                GAL_DEBUG << "EPR accept client PeerAddress = " << inet_ntoa( stPeerAddr.sin_addr ) << " : " << ntohs( stPeerAddr.sin_port );

                pstEprClient->SetSocketInfo( &stSockInfo );

                pstEprClient->SetCallbackFunc();
                pstEprClient->SetTriggerCallback( GFD::bind( &CEPR_Receive::Trigger,this,1,2 ),&m_EPR_INFO_CALLBACK );

                pstEprClient->SetRole( ROLE_CLIENT );

                if ( m_stDTSListenAttr.uiRecvBufSize > 0 )
                {
                    CDTS_Auxiliary::DTS_socket_set_option( iNewSocket,DTS_SOCKOPT_RCVBUF,m_stDTSListenAttr.uiRecvBufSize );
                }
                if ( m_stDTSListenAttr.uiSendBufSize > 0 )
                {
                    CDTS_Auxiliary::DTS_socket_set_option( iNewSocket,DTS_SOCKOPT_SNDBUF,m_stDTSListenAttr.uiSendBufSize );
                }

                if ( pstEprClient->SetMaxRecvLen( m_stDTSListenAttr.uiRecvBufSize ) == -1 )
                {
                    pstEprClient->Release();
                    delete pstEprClient;
                    return 0;
                }

                //通过Attach函数,把回调evloop注册到了CEPR_Client类中去接收码流了。 zhb
                if ( pstEprClient->Attach( m_pstPollThread->GetNextLoop(),EPOLLIN | EPOLLET ) == -1 )
                {
                    pstEprClient->Release();
                    delete pstEprClient;
                    return 0;
                }
            }
            break;
        case DTS_SOCKET_TYPE_DATAGRAM:
            break;
        default:
            break;
        }
    }

    return 0;
}

上面这些代码,基本上梳理了libev的处理过程(以epr的码流接收为例子)。
主要要做的工作其实就是设置好回到函数。
这里重新定义了Notify函数。
dts_uint32 CEPR_Receive::Notify( epoll_event* pstEv )
来实现libev基本的处理函数。

这里其实就用到来一个CPollThread m_stPollThread;
这是一个线程池(个数对应CPU的个数)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值