zoukankan      html  css  js  c++  java
  • 多线程笔记2

    发信站: 饮水思源 (2004年06月11日06:54:38 星期五)

    多线程

    闭门造车,大家指正

    1.建立多线程

    1.1 C runtime library 与 多线程 

    C runtime library 诞生在上世纪70年代。那会多任务还是个新奇的东西,就是压根没想
    到为以后的多线程考虑罗。
    没有支持多线程当然叫单线程版罗。

    支持多线程是个很泛的概念,支持多线程需要要干些什么事呢?

    C runtime library 里有些全局变量,静态变量。race condition,对的,会有同步问题
    ,但绝对不止是同步这么简单,
    仔细推敲一下,这些变量就应该每个线程各持一份然后老死不相往来么。

    有哪些变量呢?比方说errno...所以你不能在线程中用fopen/fclose fwrite/fread new/
    malloc free/delete..

    但你把这些都换成CreateFile/ReadFile/WriteFile HeapAlloc/HeapFree

    这些个WIN API函数可是可以的,没什么道理好讲,是操作系统的函数。

    所以你用C runtime library的单线版就得,

    所以说C runtime library单线程版并非就不能用到多线程上。

    不过这堆api里也没有一个可替代Stream I/O的,自己写一个?(笑)。

    臭规矩是多!有没有别的办法?我把那些个该死的全局变量,静态变量给每个线程留个副
    本不就行拉。

    实际上ms就是把这些个C runtime library的全局变量,静态变量放到一个叫_tiddata的结
    构里边,当然也不光是这些变量

    在线程建立的时候每个线程分到一个实例(CreateThread可干不了这事,后面会说到)


    好现在每个线程都有这么一个结构了。

    但跑fopen/flose等敏感函数要跟那些全局变量,静态变量打交道时,

    C runtime library怎么知道你每个线程的那个放全局变量,静态变量的结构实例在什么地
    方,

    还有编译器怎么知道要连接多线程版还是单线程版

    看看编译是的命令行可以看出单线程版是/ML 多线程版是/MT 或是/MD

    (用vc的朋友可在setting->c/c++->code generation->use run-time libary中切换)

    用个#defined #else #endif区别是单线程版还是多线程版,是单线程版把那个进程共享
    的全局变量,静态变量

    声明出来么,就像: extern int errno;

    多线程版的话就用个函数返回个指向本线程(线程内共享)errno的指针出来:

    extern int* __cdcel _errno(void);
    #define errno (*__errno())

    这就是多线程版的C runtime library。

    最后一个问题,每个线程发配一个包含结构的内存块,要用的时候随便从内存块中的变量
    set/get。怎么个实现的拉...


    TLS(线程本地存储器)就是用来实现这么个东东的,

    每个线程都有那么一份TLS(这个提法不好),有1000个槽(win2000),每个槽四个字节,
    正好放个指针呃

    刚刚说到每个线程不是有个数据结构的实例(_tiddata)么,它的指针就放在TLS中,

    不过这些槽可不是随便能放的,得向进程申请,调用 DWORD TlsAlloc(),返回的那个DWOR
    D就是可放的槽号拉,

    用BOOL TlsSetVale(DWORD,LPVOID) / LPVOID TlsGetValue(DWORD) get/set指针。


    加载dll时,如果dll有全局变量,静态变量,

    你的dll不大可能只有一个线程加载吧,那些个全局变量,静态变量可是每个线程要一份的


    每个线程需要一个指向这块内存区的指针,他们占用的槽号是一样的.


    让进程调用DWORD TlsAlloc()/TlsFree() 来分配,管理这些槽。(在dll attach/detach
    的时候)



    每个线程可以用多个槽,不过占用的槽越少越好,毕竟整个进程就能分配出来1000个,nt
    下只有64个。very的宝贵啊

    好在这些个问题ms都用封装的api搞得服服帖帖,是不是很faint,好像全是废话

    1.2 开线程了

    开线程可用CreateThread/_beginthread/_beginthreadex/AfxBeginThread

    /*CreateThread*/
    HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
    SIZE_T dwStackSize, // initial stack size
    LPTHREAD_START_ROUTINE lpStartAddress, // thread function
    LPVOID lpParameter, // thread argument
    DWORD dwCreationFlags, // creation option
    LPDWORD lpThreadId // thread identifier
    );
    无视C runtime library的存在,就是没有这么个包含全局变量,静态变量的结构实例分配
    出来。

    当然你可用压根不用C runtime library中那些“敏感的”函数,或自己处理TLS。呵呵


    /*_beginthread && _beginthreadex*/
    unsigned long _beginthread( void( __cdecl *start_address )( void * ), unsigned
    stack_size, void *arglist )

    unsigned long _beginthreadex( void *security, unsigned stack_size,
    unsigned ( __stdcall *start_address )( void * ), void *arglist,
    unsigned initflag, unsigned *thrdaddr );

    有关心C runtime library,其实它也是调用的CreateThread,

    辅助api函数threadstartex(LPVOID)的指针作为lpStartAddress送到CreateThread,
    start_address,arglist也放到_tiddata里作为lpParameter送到CreateThread

    threadstartex(LPVOID)作为thread function跑起来

    看到没有,这个函数的LPVOID吃进分配出来的包含arglist和start_address的_tiddata,
    _tiddata的指针放进TLS中,

    把start_address作为一个普通函数调用(这个提法不好),arglist作为参数.

    总算把C runtime library搞定拉。

    但是_beginthread还是不能用,它有个race condition的隐患,

    个人揣度ms是想封装CloseHanle(不要windows.h了?方便移植?),反正这个函数公认的蠢
    拉,

    _beginthreadex没有这个问题,不过自己CloseHanle。能在启动时停下来检查handle,再接
    着跑。

    1.2 关线程了

    既然CreateThread/_beginthread不在考虑之列了,配套的ExitThread/_endthread,也用
    不着
    _endthreadex与ExitThread/_endthread一样是自杀式函数,线程直接返回,刚返回时_ti
    ddata没放掉。

    跟CreateThread/_beginthread/_beginthreadex的过程相反

    _endthreadex与_endthread都是释放掉_tiddata的内存再调用ExitThread

    说到这插一句,你要是在主线程调用ExitThread,其他线程的资源是放不出来的.可能操作
    系统会有办法,不过还是不用的好

    比方你一个tcp server crash掉的时候,本地端口会进入TIME_WAIT状态,如果server程序重
    开时不设定端口SO_REUSEADDR属性

    就开不动服务器了.

    还有种情况,从message窗口看明明主线程退出了,某个线程要是在WaitForSingleObject()
    是退不出来得.

    _endthreadex还是用得很少的,除非程序立马会crash。

    我们经常需要(绝大多数情况)在另一个线程放个信号出来告诉计算机,你该关某某线程


    1.有很多人喜欢用变量,而且不怎么喜欢加volatile,呵呵
    而且这个东西有很多弊端,但也有用得着的地方。但前提是不能把这东西做成轮询,

    该阻塞的地方就得阻塞住(这个提法不好)。
    什么地方用后面会提到。
    2.setevent+WaitFor....
    注意这里的event可要个会手动重置的,要不消息可能丢失

    //module.h
    #ifndef _MODULE_
    #define _MODULE_
    #define RET_ABNORMAL 1L
    #define RET_SUCCESS 0L
    #define ERR_CREATE_EXITEVENT -1
    #define ERR_CREATE_THREAD -2
    class PlayerModule
    {
    public:
    PlayerModule();
    virtual ~PlayerModule();
    //开启线程,注意返回值
    int StartThread();
    //关闭线程
    void StopThread();
    //测试是不是有线程attach
    bool IsStart() {return NULL == hModuleThread;}
    protected:
    // 主线程函数
    virtual void Run();
    HANDLE hExitEvent; //用来给退出信号的句柄
    private:
    //注意这里的static UINT WINAPI
    static UINT WINAPI ThreadProc(LPVOID pParam);
    HANDLE hModuleThread;//线程句柄
    } ;
    #endif
    //module.cpp
    #include "module.h"

    Module::Module()
    {
    hExitEvent = NULL;
    hModuleThread = NULL;
    }
    PlayerModule::~PlayerModule()
    {
    StopThread();//清理
    }
    int Module::StartThread()
    {
    if(NULL == ( hExitEvent = CreateEvent(NULL,TRUE,FALSE,NULL))) //手动复位
    {
    return ERR_CREATE_EXITEVENT;
    }
    HANDLE hThread;
    UINT uiThreadId = 0;

    hThread = (HANDLE) _beginthreadex(NULL, // attrib
    0, // stack
    ThreadProc, // thread proc
    this, // thread proc param
    CREATE_SUSPENDED, // creation mode挂起
    &uiThreadId); // thread ID记录下来可做调试依据
    // verify the thread handle
    if ( NULL != hThread)
    {
    //continue thread
    ResumeThread( hThread );
    hModuleThread = hThread;
    return RET_SUCCESS;
    }
    else
    {
    CloseHandle(hExitEvent);
    hExitEvent = NULL;
    return ERR_CREATE_THREAD;
    }
    }

    UINT Module::ThreadProc(LPVOID pParam)
    {
    Module* pThis = reinterpret_cast<Module*>( pParam );
    _ASSERTE( pThis != NULL );
    pThis->Run();//真正的主线程函数
    return RET_SUCCESS;
    }
    void Module::StopThread()
    {
    if(hModuleThread)
    {
    SetEvent(hExitEvent);//放信号
    if (WaitForSingleObject(hModuleThread, 5000L) == WAIT_TIMEOUT)//等线程退出

    TerminateThread(hModuleThread, RET_ABNORMAL);
    CloseHandle(hModuleThread);
    CloseHandle(hExitEvent);
    hExitEvent = NULL;
    hModuleThread = NULL;
    }
    }
    UINT Module::Run()
    {
    return 0;
    }
    看到了吧,就三板斧
    SetEvent(hExitEvent);//放信号
    if (WaitForSingleObject(hModuleThread, 5000L) == WAIT_TIMEOUT)//等线程退出

    TerminateThread(hModuleThread, RET_ABNORMAL);//实在不行,恼羞成怒硬退
    这样肯定是安全的,如果线程返回RET_ABNORMAL,那肯定是你的run中的循环
    没能退出来,

    这里的run在超类中重写的。

    如果你的run函数本来就是要轮循的,用全局变量也可,不过这么写也不错:

    while(true)
    {
    .......//do something
    if(WaitForSingleObject(hExitEvent,0) != WAIT_TIMEOUT)
    break;
    }

    如果有阻塞用轮循代码效率可低多了

    比方producer/comsumer模式下,comsumer这头

    UINT DeriveFromModule::Run()
    {
    HANDLE handls[2];
    handls[1] = hSem; //这个次序还是蛮重要的
    handls[0] = hExitEvent;
    while (true)
    {

    DWORD ret;
    ret = WaitForMultipleObjects(2,handls,FALSE,INFINITE);
    if (ret == WAIT_OBJECT_0) {
    break;
    }
    ....//use Semaphore do something
    }
    return 0;
    }

    但如果阻塞的是一个io,比方是个sock。情况又不那么一样拉
    实际的办法把io关了,句柄置位,线程判断句柄后退出。
    不重用Module,写个IOModule,框架一样的只是没有hExitEvent。
    #define INVALID_HANDLE_VALUE NULL

    bool RTPSocketComm::IsOpen() const
    {
    return ( INVALID_HANDLE_VALUE != hComm );
    }

    void IOModule::StopComm()
    {
    // Close Socket
    if (IsOpen())
    {
    //调用sock关闭
    shutdown(sock, SD_BOTH);
    closesocket( sock );
    hComm = INVALID_HANDLE_VALUE;
    Sleep(50);//50ms
    }

    // Kill Thread
    if (NULL != hIOModuleThread)
    {
    if (WaitForSingleObject(hIOModuleThread, 5000L) == WAIT_TIMEOUT)//等5s够长了

    TerminateThread(hIOModuleThread, 1L);
    CloseHandle(hIOModuleThread);
    hIOModuleThread = NULL;
    }
    }
    UINT IOModule::Run()
    {
    DWORD dwBytes = 0L;
    ....
    while( IsOpen() )
    {
    // 采用阻塞式socket,等待事件通知
    dwBytes = recvfrom(....);
    or dwBytes = recv(....);
    or select(...)+recvfrom(..)|recv(..)

    if (dwBytes > 0)
    {
    ...//process buffer
    }
    else{// 错误
    break;
    }

    }
    return 0;
    }

    在线程外边执行StopComm()关线程。

    当然也可重用Module改写run,下面select停靠了三个sock,
    对于使用中的sock一般不会在select停1s以上,所以也没什么大开销,回头把sock再关了
    就行了

    UINT DeriveFromModule::Run()
    {
    fd_set fdset;
    struct timeval tv;

    UINT ASock;//接收端口1-3
    UINT BSock;
    UINT CSock;

    GetASocket(&ASock);
    GetBSocket(&BSock);
    GetCSocket(&CSock);


    while (true)
    {
    tv.tv_sec = 1;//1s
    tv.tv_usec = 0;
    FD_ZERO(&fdset);
    FD_SET(ASock,&fdset);
    FD_SET(BSock,&fdset);
    FD_SET(CSock,&fdset);
    int ret = select(FD_SETSIZE,&fdset,NULL,NULL,&tv);
    if ( ret > 0) {
    if(FD_ISSET(ASock,&fdset))
    {
    ...
    }
    else if(FD_ISSET(BSock,&fdset))
    {
    ...
    }
    else if(FD_ISSET(CSock,&fdset))
    {

    }
    if(WaitForSingleObject(hExitEvent,0) != WAIT_TIMEOUT)
    break;
    Sleep(0);
    }
    return 0;
    }
    select是种比较土的同步io办法拉,不过overlapped io,i/o completion ports又比较多
    东西了,而且跟这篇帖子也不怎么靠边

    ms恐吓我说用mfc来开发程序时得用AfxBeginThread,不好用_beginthreadex,偶知道mfc
    也用Tls的,有AFX_MODULE_PROCESS_STATE,
    _AFX_THREAD_STATE ,AFX_MODULE_PROCESS_STATE三个状态要线程局部化。

    不过偶在工作线程里不用mfc,这些线程不加载mfc的dll,要不是图mfc做界面快....

    所以偶一直非常固执的用_beginthreadex+winsock api,倒也没出过什么事。到现在为止
    偶觉得用_beginthreadex也没什么大问题。

    AfxBeginThread的用法类似。

    1.3 媒体定时器

    比windows api提供的定时器强大多了.定义查看:<mmsystem.h> winmm.lib

    实际上每个定时器都是一个后台线程,可用来做采样和视频.做过的人都知道

    下面是个包装类,非常之简单,resolution是要求的精度,internalTimerProc是个回调.
    //mmTimers.h
    #ifndef ___multimedia_timers___
    #define ___multimedia_timers___
    #include <mmsystem.h>
    class CMMTimers
    {
    public:
    CMMTimers(UINT resolution);
    virtual ~CMMTimers();

    UINT getTimerRes() { return timerRes; };

    bool startTimer(UINT period,bool oneShot);//是周期的,还是只激发一次
    bool stopTimer();

    virtual void timerProc() {};//写处理函数

    protected:
    UINT timerRes;
    UINT timerId;
    };
    #endif


    //mmTimers.cpp
    #include "StdAfx.h"
    #include "mmTimers.h"


    CMMTimers::CMMTimers(UINT resolution) : timerRes(0), timerId(0)
    {
    TIMECAPS tc;

    if (TIMERR_NOERROR == timeGetDevCaps(&tc,sizeof(TIMECAPS)))
    {
    timerRes = min(max(tc.wPeriodMin,resolution),tc.wPeriodMax);
    timeBeginPeriod(timerRes);
    }
    }


    CMMTimers::~CMMTimers()
    {
    stopTimer();
    if (0 != timerRes)
    {
    timeEndPeriod(timerRes);
    timerRes = 0;
    }
    }


    extern "C"
    void
    CALLBACK
    internalTimerProc(UINT id,UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)
    {
    CMMTimers * timer = (CMMTimers *)dwUser;

    timer->timerProc();
    }


    bool CMMTimers::startTimer(UINT period,bool oneShot)
    {
    bool res = false;
    MMRESULT result;

    result = timeSetEvent(period,timerRes,internalTimerProc,(DWORD)this,oneShot ?
    TIME_ONESHOT : TIME_PERIODIC);
    if (NULL != result)
    {
    timerId = (UINT)result;
    res = true;
    }

    return res;
    }


    bool CMMTimers::stopTimer()
    {
    MMRESULT result;

    result = timeKillEvent(timerId);
    if (TIMERR_NOERROR == result)
    timerId = 0;

    return TIMERR_NOERROR == result;
    }

    2 互斥

    2.1 互斥变量

    CRITICAL_SECTION && Mutex
    没什么好说的就是不跨进程的时候用CRITICAL_SECTION,CRITICAL_SECTION的速度比Mute
    x快太多了
    比方说锁个链表:
    MYList::MYList()
    {
    ...
    InitializeCriticalSection(&criticalsection);
    }

    MYList::~MYList()
    {

    DeleteCriticalSection(&criticalsection);
    }

    inline void MYList::LockList()
    {
    EnterCriticalSection(&criticalsection);
    }
    inline void MYList::UnlockList()
    {
    LeaveCriticalSection(&criticalsection);
    }

    int MYList::Add(ListMember *mem)
    {
    EnterCriticalSection(&criticalsection);
    ....//调链表指针
    LeaveCriticalSection(&criticalsection);
    ......
    }
    ListMember* MYList::Extract()
    {
    EnterCriticalSection(&criticalsection);
    ....//调链表指针
    LeaveCriticalSection(&criticalsection);
    .....
    }

    semaphore用来实现producer/consumer模型

    比方说上面的链表就可用作一个管道,同步在内部都做好了,比方链表每add一个元素

    用ReleaseSemaphore(hSem,1,NULL)发一个通知。consumer这边WaitForSingleObject()返
    回,

    并用Extract()接收一个

    3. 同步

    WaitForSingleObject/WaitForMultipleObjects前边都有。

  • 相关阅读:
    关于ORA-04091异常的出现原因,以及解决方案
    <Interview problem>二进制加法
    Hadoop日志文件分析系统
    Android studio安装
    weex环境配置
    weex构建项目
    参数保存
    下拉搜索的小白demo
    angularJs中$http获取后台数据实例(搜集到的)
    前后台对接angularjs(搜集的)
  • 原文地址:https://www.cnblogs.com/kex1n/p/2286554.html
Copyright © 2011-2022 走看看