Task详解
来源:互联网 发布:电力猫网络连不上网 编辑:程序博客网 时间:2024/06/13 08:57
Task类中GetEvents函数返回当前除了kAlive外的所有事件标识,同时清除fEvents中除kAlive之外的所有其他标识。
class A{private:public:A(){fEvents = 0;}typedef unsigned int EventFlags;EventFlags fEvents;enum{kAlive = 0x80000000, //EventFlags, againkAliveOff = 0x7fffffff};//EVENTS//here are all the events that can be sent to a taskenum{kKillEvent = 0x1 << 0x0, //these are all of type "EventFlags"kIdleEvent = 0x1 << 0x1,kStartEvent = 0x1 << 0x2,kTimeoutEvent = 0x1 << 0x3,//socket eventskReadEvent = 0x1 << 0x4, //All of type "EventFlags"kWriteEvent = 0x1 << 0x5,//update eventkUpdateEvent = 0x1 << 0x6}; EventFlags GetEvents() { //Mask off every event currently in the mask except for the alive bit, of course, //which should remain unaffected and unreported by this call. EventFlags events = fEvents & kAliveOff; (void)atomic_sub(&fEvents, events); return events; }};int main(int argc, char * argv[]) {A a;a.fEvents = A::kKillEvent | A::kWriteEvent | A::kAlive;cout << setbase(16) << "fEvents:" << a.fEvents << endl;int flag = a.GetEvents();cout << setbase(16) << "GetEvents返回值:" << flag << endl;cout << setbase(16) << "fEvents:" << a.fEvents << endl;return 0;}
Task::Signal将task任务添加到线程的队列中。如果该task指定了某个线程,则添加到对应的线程的队列中。如果没有指定,则添加到线程池的某个线程队列中。而线程池的每个线程都在不停的调用void TaskThread::Entry()中的 theTask = this->WaitForTask()获取线程任务队列中的任务。
在调用void Task::Signal(EventFlags events)时 ,将fEvents设置成包含events和kAlive的事件。 events |= kAlive;
EventFlags oldEvents = atomic_or(&fEvents, events);
在Run中,所有的Task派生类都会在Run中调用GetEvents获取当前时间,同时将当前事件清空。
//监测Task中Run()函数的返回值,共有三种情况
23 //1、返回负数,表明任务已经完全结束
24 if (theTimeout < 0)
25 {
26 delete theTask; //删除Task对象
27 theTask = NULL;
28 doneProcessingEvent = true;
19 }
30 //2、返回0,表明任务希望在下次传信时被再次立即执行
31 else if (theTimeout=0)
32 {
//如果theTask->fEvents的值==Task::kAlive,则将theTask->fEvents设置为0,函数返回1,否则返回0
33 doneProcessingEvent = compare_and_store(Task::kAlive, 0, &theTask->fEvents);
34 if (doneProcessingEvent)
35 theTask = NULL;
36 }
//3、返回正数,表明任务希望在等待theTimeout时间后再次执行
37 else
38 {
/*将该任务加入到Heap中,并且纪录它希望等待的时间。Entry()函数将通过waitfortask()函数进行检测,如果等待的时间到了,就再次运行该任务*/
39 theTask->fTimerHeapElem.SetValue(OS::Milliseconds() + theTimeout);
40 fHeap.Insert(&theTask->fTimerHeapElem);
41 (void)atomic_or(&theTask->fEvents, Task::kIdleEvent);//设置Idle事件
42 doneProcessingEvent = true;
43 }
//此处略…
}
注意,如果Task的Run()函数返回值TimeOut为正数,意味着该任务是一个周期性的工作,例如发送数据的视频泵(pump),需要每隔一定时间 就发出一定量的视频数据,直至整个节目结束。为此,在第38~43行,将该任务加入到堆fHeap中去,并且标记该任务下次运行的时间为TimeOut毫 秒之后
void Task::Signal(EventFlags events){ if (!this->Valid()) return; //Fancy no mutex implementation. We atomically mask the new events into //the event mask. Because atomic_or returns the old state of the mask, //we only schedule this task once. events |= kAlive; EventFlags oldEvents = atomic_or(&fEvents, events); if ((!(oldEvents & kAlive)) && (TaskThreadPool::sNumTaskThreads > 0)) {
//指定了某个线程 if (fUseThisThread != NULL) // Task needs to be placed on a particular thread. { if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s fUseThisThread=%lu q elem=%lu enclosing=%lu\n", fTaskName, (UInt32) fUseThisThread, (UInt32) &fTaskQueueElem, (UInt32) this); fUseThisThread->fTaskQueue.EnQueue(&fTaskQueueElem); }
//添加到线程池的某个线程队列中 else { //find a thread to put this task on unsigned int theThread = atomic_add(&sThreadPicker, 1); theThread %= TaskThreadPool::sNumTaskThreads; if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s thread=%lu q elem=%lu enclosing=%lu\n", fTaskName, (UInt32)TaskThreadPool::sTaskThreadArray[theThread],(UInt32) &fTaskQueueElem,(UInt32) this); TaskThreadPool::sTaskThreadArray[theThread]->fTaskQueue.EnQueue(&fTaskQueueElem); } } else if (TASK_DEBUG) qtss_printf("Task::Signal sent to dead TaskName=%s q elem=%lu enclosing=%lu\n", fTaskName, (UInt32) &fTaskQueueElem, (UInt32) this); }
Task* TaskThread::WaitForTask(){ while (true) { SInt64 theCurrentTime = OS::Milliseconds(); if ((fHeap.PeekMin() != NULL) && (fHeap.PeekMin()->GetValue() <= theCurrentTime)) { if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found timer-task=%s thread %lu fHeap.CurrentHeapSize(%lu) taskElem = %lu enclose=%lu\n",((Task*)fHeap.PeekMin()->GetEnclosingObject())->fTaskName, (UInt32) this, fHeap.CurrentHeapSize(), (UInt32) fHeap.PeekMin(), (UInt32) fHeap.PeekMin()->GetEnclosingObject()); return (Task*)fHeap.ExtractMin()->GetEnclosingObject(); } //if there is an element waiting for a timeout, figure out how long we should wait. SInt64 theTimeout = 0; if (fHeap.PeekMin() != NULL) theTimeout = fHeap.PeekMin()->GetValue() - theCurrentTime; Assert(theTimeout >= 0); // // Make sure we can't go to sleep for some ridiculously short // period of time // Do not allow a timeout below 10 ms without first verifying reliable udp 1-2mbit live streams. // Test with streamingserver.xml pref reliablUDP printfs enabled and look for packet loss and check client for buffer ahead recovery.if (theTimeout < 10) theTimeout = 10; //wait... OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, (SInt32) theTimeout); if (theElem != NULL) { if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found signal-task=%s thread %lu fTaskQueue.GetLength(%lu) taskElem = %lu enclose=%lu\n", ((Task*)theElem->GetEnclosingObject())->fTaskName, (UInt32) this, fTaskQueue.GetQueue()->GetLength(), (UInt32) theElem, (UInt32)theElem->GetEnclosingObject() ); return (Task*)theElem->GetEnclosingObject(); } // // If we are supposed to stop, return NULL, which signals the caller to stop if (OSThread::GetCurrent()->IsStopRequested()) return NULL; } }
/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * *//* File: Task.h Contains: Tasks are objects that can be scheduled. To schedule a task, you call its signal method, and pass in an event (events are bits and all events are defined below). Once Signal() is called, the task object will be scheduled. When it runs, its Run() function will get called. In order to clear the event, the derived task object must call GetEvents() (which returns the events that were sent). Calling GetEvents() implicitly "clears" the events returned. All events must be cleared before the Run() function returns, or Run() will be invoked again immediately. */#ifndef __TASK_H__#define __TASK_H__#include "OSQueue.h"#include "OSHeap.h"#include "OSThread.h"#include "OSMutexRW.h"#define TASK_DEBUG 0class TaskThread;class Task{ public: typedef unsigned int EventFlags; //EVENTS //here are all the events that can be sent to a task enum { kKillEvent = 0x1 << 0x0, //these are all of type "EventFlags" kIdleEvent = 0x1 << 0x1, kStartEvent = 0x1 << 0x2, kTimeoutEvent = 0x1 << 0x3, //socket events kReadEvent = 0x1 << 0x4, //All of type "EventFlags" kWriteEvent = 0x1 << 0x5, //update event kUpdateEvent = 0x1 << 0x6 }; //CONSTRUCTOR / DESTRUCTOR //You must assign priority at create time. Task(); virtual ~Task() {} //return: // >0-> invoke me after this number of MilSecs with a kIdleEvent // 0 don't reinvoke me at all. //-1 delete me //Suggested practice is that any task should be deleted by returning true from the //Run function. That way, we know that the Task is not running at the time it is //deleted. This object provides no protection against calling a method, such as Signal, //at the same time the object is being deleted (because it can't really), so watch //those dangling references! virtual SInt64 Run() = 0; //Send an event to this task. void Signal(EventFlags eventFlags); void GlobalUnlock(); //判断fTaskName是否命名正确,如果fTaskName为空或者不是以“live_”开头,则无效 Bool16 Valid(); // for debugging//任务名称char fTaskName[48];//设置fTaskName,fTaskName命令方式:live_namevoid SetTaskName(char* name); protected: //Only the tasks themselves may find out what events they have received EventFlags GetEvents(); // ForceSameThread // // A task, inside its run function, may want to ensure that the same task thread // is used for subsequent calls to Run(). This may be the case if the task is holding // a mutex between calls to run. By calling this function, the task ensures that the // same task thread will be used for the next call to Run(). It only applies to the // next call to run. void ForceSameThread() { fUseThisThread = (TaskThread*)OSThread::GetCurrent(); Assert(fUseThisThread != NULL); if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); if (TASK_DEBUG) qtss_printf("Task::ForceSameThread fUseThisThread %lu task %s enque elem=%lu enclosing %lu\n", (UInt32)fUseThisThread, fTaskName,(UInt32) &fTaskQueueElem,(UInt32) this); } SInt64 CallLocked() { ForceSameThread(); fWriteLock = true; return (SInt64) 10; // minimum of 10 milliseconds between locks } private: enum { kAlive = 0x80000000, //EventFlags, again kAliveOff = 0x7fffffff }; void SetTaskThread(TaskThread *thread); EventFlags fEvents; TaskThread* fUseThisThread; Bool16 fWriteLock;#if DEBUG //The whole premise of a task is that the Run function cannot be re-entered. //This debugging variable ensures that that is always the case volatile UInt32 fInRunCount;#endif //This could later be optimized by using a timing wheel instead of a heap, //and that way we wouldn't need both a heap elem and a queue elem here (just queue elem) OSHeapElem fTimerHeapElem; OSQueueElem fTaskQueueElem; //Variable used for assigning tasks to threads in a round-robin fashion static unsigned int sThreadPicker; friend class TaskThread; };class TaskThread : public OSThread{ public: //Implementation detail: all tasks get run on TaskThreads. TaskThread() : OSThread(), fTaskThreadPoolElem() {fTaskThreadPoolElem.SetEnclosingObject(this);}virtual ~TaskThread() { this->StopAndWaitForThread(); } private: enum { kMinWaitTimeInMilSecs = 10 //UInt32 }; virtual void Entry(); Task* WaitForTask(); OSQueueElem fTaskThreadPoolElem; OSHeap fHeap; OSQueue_Blocking fTaskQueue; friend class Task; friend class TaskThreadPool;};//Because task threads share a global queue of tasks to execute,//there can only be one pool of task threads. That is why this object//is static.class TaskThreadPool {public: //Adds some threads to the pool static Bool16 AddThreads(UInt32 numToAdd); static void SwitchPersonality( char *user = NULL, char *group = NULL); static void RemoveThreads(); private: static TaskThread** sTaskThreadArray; static UInt32 sNumTaskThreads; static OSMutexRW sMutexRW; friend class Task; friend class TaskThread;};#endif
/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * *//* File: Task.cpp Contains: implements Task class */#include "Task.h"#include "OS.h"#include "OSMemory.h"#include "atomic.h"#include "OSMutexRW.h"unsigned int Task::sThreadPicker = 0;OSMutexRW TaskThreadPool::sMutexRW;static char* sTaskStateStr="live_"; //AliveTask::Task(): fEvents(0), fUseThisThread(NULL), fWriteLock(false), fTimerHeapElem(), fTaskQueueElem(){#if DEBUG fInRunCount = 0;#endif this->SetTaskName("unknown");fTaskQueueElem.SetEnclosingObject(this);fTimerHeapElem.SetEnclosingObject(this);}void Task::SetTaskName(char* name) { if (name == NULL) return; ::strncpy(fTaskName,sTaskStateStr,sizeof(fTaskName)); ::strncat(fTaskName,name,sizeof(fTaskName)); fTaskName[sizeof(fTaskName) -1] = 0; //terminate in case it is longer than ftaskname. }Bool16 Task::Valid(){ if ( (this->fTaskName == NULL) || (0 != ::strncmp(sTaskStateStr,this->fTaskName, 5)) ) { if (TASK_DEBUG) qtss_printf(" Task::Valid Found invalid task = %ld\n", this); return false; } return true;}Task::EventFlags Task::GetEvents(){ //Mask off every event currently in the mask except for the alive bit, of course, //which should remain unaffected and unreported by this call. EventFlags events = fEvents & kAliveOff; (void)atomic_sub(&fEvents, events); return events;}void Task::Signal(EventFlags events){ if (!this->Valid()) return; //Fancy no mutex implementation. We atomically mask the new events into //the event mask. Because atomic_or returns the old state of the mask, //we only schedule this task once. events |= kAlive; EventFlags oldEvents = atomic_or(&fEvents, events); if ((!(oldEvents & kAlive)) && (TaskThreadPool::sNumTaskThreads > 0)) { if (fUseThisThread != NULL) // Task needs to be placed on a particular thread. { if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s fUseThisThread=%lu q elem=%lu enclosing=%lu\n", fTaskName, (UInt32) fUseThisThread, (UInt32) &fTaskQueueElem, (UInt32) this); fUseThisThread->fTaskQueue.EnQueue(&fTaskQueueElem); } else { //find a thread to put this task on unsigned int theThread = atomic_add(&sThreadPicker, 1); theThread %= TaskThreadPool::sNumTaskThreads; if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s thread=%lu q elem=%lu enclosing=%lu\n", fTaskName, (UInt32)TaskThreadPool::sTaskThreadArray[theThread],(UInt32) &fTaskQueueElem,(UInt32) this); TaskThreadPool::sTaskThreadArray[theThread]->fTaskQueue.EnQueue(&fTaskQueueElem); } } else if (TASK_DEBUG) qtss_printf("Task::Signal sent to dead TaskName=%s q elem=%lu enclosing=%lu\n", fTaskName, (UInt32) &fTaskQueueElem, (UInt32) this); }void Task::GlobalUnlock() { if (this->fWriteLock) { this->fWriteLock = false; TaskThreadPool::sMutexRW.Unlock(); } }void TaskThread::Entry(){ Task* theTask = NULL; while (true) { theTask = this->WaitForTask(); // // WaitForTask returns NULL when it is time to quit if (theTask == NULL || false == theTask->Valid() ) return; Bool16 doneProcessingEvent = false; while (!doneProcessingEvent) { //If a task holds locks when it returns from its Run function, //that would be catastrophic and certainly lead to a deadlock#if DEBUG Assert(this->GetNumLocksHeld() == 0); Assert(theTask->fInRunCount == 0); theTask->fInRunCount++;#endif theTask->fUseThisThread = NULL; // Each invocation of Run must independently // request a specific thread. SInt64 theTimeout = 0; if (theTask->fWriteLock) { OSMutexWriteLocker mutexLocker(&TaskThreadPool::sMutexRW); if (TASK_DEBUG) qtss_printf("TaskThread::Entry run global locked TaskName=%s CurMSec=%.3f thread=%ld task=%ld\n", theTask->fTaskName, OS::StartTimeMilli_Float() ,(SInt32) this,(SInt32) theTask); theTimeout = theTask->Run(); theTask->fWriteLock = false; } else { OSMutexReadLocker mutexLocker(&TaskThreadPool::sMutexRW); if (TASK_DEBUG) qtss_printf("TaskThread::Entry run TaskName=%s CurMSec=%.3f thread=%ld task=%ld\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (SInt32) this,(SInt32) theTask); theTimeout = theTask->Run(); }#if DEBUG Assert(this->GetNumLocksHeld() == 0); theTask->fInRunCount--; Assert(theTask->fInRunCount == 0);#endif if (theTimeout < 0) { if (TASK_DEBUG) { qtss_printf("TaskThread::Entry delete TaskName=%s CurMSec=%.3f thread=%ld task=%ld\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (SInt32) this, (SInt32) theTask); theTask->fUseThisThread = NULL; if (NULL != fHeap.Remove(&theTask->fTimerHeapElem)) qtss_printf("TaskThread::Entry task still in heap before delete\n"); if (NULL != theTask->fTaskQueueElem.InQueue()) qtss_printf("TaskThread::Entry task still in queue before delete\n"); theTask->fTaskQueueElem.Remove(); if (theTask->fEvents &~ Task::kAlive) qtss_printf ("TaskThread::Entry flags still set before delete\n"); (void)atomic_sub(&theTask->fEvents, 0); ::strncat (theTask->fTaskName, " deleted", sizeof(theTask->fTaskName) -1); } theTask->fTaskName[0] = 'D'; //mark as dead delete theTask; theTask = NULL; doneProcessingEvent = true; } else if (theTimeout == 0) { //We want to make sure that 100% definitely the task's Run function WILL //be invoked when another thread calls Signal. We also want to make sure //that if an event sneaks in right as the task is returning from Run() //(via Signal) that the Run function will be invoked again. doneProcessingEvent = compare_and_store(Task::kAlive, 0, &theTask->fEvents); if (doneProcessingEvent) theTask = NULL; } else { //note that if we get here, we don't reset theTask, so it will get passed into //WaitForTask if (TASK_DEBUG) qtss_printf("TaskThread::Entry insert TaskName=%s in timer heap thread=%lu elem=%lu task=%ld timeout=%.2f\n", theTask->fTaskName, (UInt32) this, (UInt32) &theTask->fTimerHeapElem,(SInt32) theTask, (float)theTimeout / (float) 1000); theTask->fTimerHeapElem.SetValue(OS::Milliseconds() + theTimeout); fHeap.Insert(&theTask->fTimerHeapElem); (void)atomic_or(&theTask->fEvents, Task::kIdleEvent); doneProcessingEvent = true; } #if TASK_DEBUG SInt64 yieldStart = OS::Milliseconds(); #endif this->ThreadYield(); #if TASK_DEBUG SInt64 yieldDur = OS::Milliseconds() - yieldStart; static SInt64 numZeroYields; if ( yieldDur > 1 ) { if (TASK_DEBUG) qtss_printf( "TaskThread::Entry time in Yield %i, numZeroYields %i\n", (long)yieldDur, (long)numZeroYields ); numZeroYields = 0; } else numZeroYields++; #endif } }}Task* TaskThread::WaitForTask(){ while (true) { SInt64 theCurrentTime = OS::Milliseconds(); if ((fHeap.PeekMin() != NULL) && (fHeap.PeekMin()->GetValue() <= theCurrentTime)) { if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found timer-task=%s thread %lu fHeap.CurrentHeapSize(%lu) taskElem = %lu enclose=%lu\n",((Task*)fHeap.PeekMin()->GetEnclosingObject())->fTaskName, (UInt32) this, fHeap.CurrentHeapSize(), (UInt32) fHeap.PeekMin(), (UInt32) fHeap.PeekMin()->GetEnclosingObject()); return (Task*)fHeap.ExtractMin()->GetEnclosingObject(); } //if there is an element waiting for a timeout, figure out how long we should wait. SInt64 theTimeout = 0; if (fHeap.PeekMin() != NULL) theTimeout = fHeap.PeekMin()->GetValue() - theCurrentTime; Assert(theTimeout >= 0); // // Make sure we can't go to sleep for some ridiculously short // period of time // Do not allow a timeout below 10 ms without first verifying reliable udp 1-2mbit live streams. // Test with streamingserver.xml pref reliablUDP printfs enabled and look for packet loss and check client for buffer ahead recovery.if (theTimeout < 10) theTimeout = 10; //wait... OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, (SInt32) theTimeout); if (theElem != NULL) { if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found signal-task=%s thread %lu fTaskQueue.GetLength(%lu) taskElem = %lu enclose=%lu\n", ((Task*)theElem->GetEnclosingObject())->fTaskName, (UInt32) this, fTaskQueue.GetQueue()->GetLength(), (UInt32) theElem, (UInt32)theElem->GetEnclosingObject() ); return (Task*)theElem->GetEnclosingObject(); } // // If we are supposed to stop, return NULL, which signals the caller to stop if (OSThread::GetCurrent()->IsStopRequested()) return NULL; } }TaskThread** TaskThreadPool::sTaskThreadArray = NULL;UInt32 TaskThreadPool::sNumTaskThreads = 0;Bool16 TaskThreadPool::AddThreads(UInt32 numToAdd){ Assert(sTaskThreadArray == NULL); sTaskThreadArray = new TaskThread*[numToAdd]; for (UInt32 x = 0; x < numToAdd; x++) { sTaskThreadArray[x] = NEW TaskThread(); sTaskThreadArray[x]->Start(); } sNumTaskThreads = numToAdd; return true;}void TaskThreadPool::RemoveThreads(){ //Tell all the threads to stop for (UInt32 x = 0; x < sNumTaskThreads; x++) sTaskThreadArray[x]->SendStopRequest(); //Because any (or all) threads may be blocked on the queue, cycle through //all the threads, signalling each one for (UInt32 y = 0; y < sNumTaskThreads; y++) sTaskThreadArray[y]->fTaskQueue.GetCond()->Signal(); //Ok, now wait for the selected threads to terminate, deleting them and removing //them from the queue. for (UInt32 z = 0; z < sNumTaskThreads; z++) delete sTaskThreadArray[z]; sNumTaskThreads = 0;}
- Task详解
- Task与Activity详解
- Task 与 Activity 详解
- Task 与 Activity 详解
- Task(Activity栈) 详解
- Task与Activity详解
- Task与Activity详解
- Android Task详解
- Activity Task 详解
- Task(Activity栈) 详解
- [NIO]dawn之Task详解
- TimerTask、Quartz、Spring-Task 详解
- Async Task的使用详解
- Activity的task相关 详解
- Task和Activity回退栈详解
- 8.3Task全生命周期详解
- Android Gradle 自定义Task 详解
- Android task和back stack详解
- 银行卡四元素校验API 验证姓名手机号码身份证号码银行卡号是否一致
- cubemx_usart源码分析
- 理解Node.js事件驱动编程
- mybatis快速使用
- 酒干倘卖无
- Task详解
- 应用netcat实现端口转发
- toolbar3
- python 5-1 如何读写文本文件str1.encode('utf8')/decode('utf8')/open("text2.txt","wt",encoding="utf8")
- 【剑指offer】面试题38-数字在排序数组中出现的次数
- MySQL绿色版的安装(mysql-5.6.22-win32.zip)
- js实现图片预加载
- Spark读码笔记之核心源码编译
- jquery之封装插件