多线程笔记2
2009-05-01 20:22
113 查看
发信站: 饮水思源 (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前边都有。
相关文章推荐
- (8)Java笔记8之多线程
- 【Python笔记】Python多线程进程如何正确响应Ctrl-C以实现优雅退出
- IOS7笔记-10、多线程、滚动视图
- JAVA多线程学习笔记—1
- Java多线程基础知识总结笔记
- IOS学习笔记 多线程基本理论基础(1)
- Thrift学习笔记(3)--Thrift 多线程阻塞式IO服务模型
- java学习笔记--多线程
- 一、java多线程编程核心技术之(笔记)——多线程的实现
- 学习笔记——多线程
- java-多线程学习笔记
- 《软件调试的艺术》笔记--调试多线程程序
- Java学习笔记(3)----网络套接字服务器多线程版本
- Java学习笔记之——多线程(一)
- java多线程学习笔记
- 多线程笔记1
- java多线程(java学习笔记之----多线程)
- Java多线程学习笔记——从Java JVM对多线程数据同步的一些理解
- 《多线程》笔记 (修正版)
- 黑马程序员 笔记(十一)——面向对象(多线程)