EA&UML日拱一卒-多任务编程超入门-(11)学以致用

这段时间的文章,算是对线程间数据交换,同步相关的内容进行了简单的说明。但都还停留在知识点的层面,不系统。接下来的通过一个非常接近实战的例子来说明各种知识的综合运用。


本例中用到的想法,代码,稍加修改,应该可以运用到实际的项目中。


需求定义


系 统包含主线程和另外两个线程。两个线程的职责分别是生成数据和接受用户键盘输入并发送给主线程;主线程除了负责协调线程的动作以外,还接受和处理两个线程 发来的事件。数据事件的内容直接表示在屏幕上;键盘输入事件的内容也表示在屏幕上,但是输入ESC键的时候,程序退出。


类图


EA&UML日拱一卒-多任务编程超入门-(11)学以致用

示例代码


Event

事件类,包含两种情况,按键事件KEY_EVENT和数据事件DATA_EVENT。

enum EventId
{
   
KEY_EVENT,
   
DATA_EVENT,
};
struct
Event
{
   
EventId id;
   
int     value;
};


EventList


事件的容器。事件的生产者是CreateDataTask和GetKeyTask。事件的消费者是主线程。这个类除了提供线程安全的数据访问手段以外,还提供消费者线程的阻塞和唤醒功能。

class EventList
{
   
QMutex m_mutex;
   
QSemaphore m_semaphore;
public
:
   
EventList();
   
void AppendEvent(Event event);
   
Event RemoveEvent();
private
:
   
QList<Event> m_eventList;
};


EventList::EventList()
{
}
void
EventList::AppendEvent(Event event)
{
   
m_mutex.lock();
   
m_eventList.push_back(event);
   
m_mutex.unlock();
   
m_semaphore.release();
}
Event EventList::RemoveEvent()\
{
   
Event event;
   
m_semaphore.acquire();
   
m_mutex.lock();
   
event = m_eventList.takeFirst();
   
m_mutex.unlock();
   
return event;
}

m_mutex负责数据的多线程安全,m_semaphore负责待处理待处理事件不存在时阻塞消费者线程和事件存在时唤醒消费者线程。


SampleTask


CreateDataTask和GetKeyTask的共同基类,这个类继承自QThread,主要功能是提供线程停止机制和停止以后的通知。

class SampleTask : public QThread
{
public
:
   
SampleTask(EventList& list,  
              QSemaphore& end);
protected
:
   
EventList& m_eventList;
   
QSemaphore& m_endSemaphore;
   
virtual void run();
   
virtual void doWork() = 0;
};


SampleTask::SampleTask(EventList& list,   
                     QSemaphore& end)
  
:m_eventList(list)
  ,m_endSemaphore(end)
{
}
void SampleTask::run()
{
   
while(!QThread::currentThread()->
               isInterruptionRequested())
   
{
       
doWork();
   
}
   
m_endSemaphore.release();
}


通过调用isInterrptionRequested方法来检查是否存在线程终止请求。在线程退出时,调用m_endSemaphore的release方法实现线程终止的同步。

另外run方法中直接调用纯虚的doWork方法,doWork的具体动作由派生类实现。


CreateDataTask


生成数据事件并发送给EventList.

void CreateDataTask::doWork()
{
   
Event e;
   
e.id = DATA_EVENT;
   
e.value = m_currentValue++;
   
m_eventList.AppendEvent(e);
   
msleep(500); //wait 500ms
}


GetKeyTask


取得用户键盘输入并发送给EventList.

void GetKeyTask::doWork()
{
   
if(kbhit())
   
{
       
int key = getch();
       
Event e;
       
e.id = KEY_EVENT;
       
e.value = key;
       
m_eventList.AppendEvent(e);
   
}
   
else
   
{
       
msleep(50);
   
}
}


主函数


int main(int argc, char *argv[])
{
   
EventList eventList;
   
QSemaphore endSemaphore;
   
GetKeyTask gt(eventList, endSemaphore);
   
gt.start();
   
CreateDataTask ct(eventList, endSemaphore);
   
ct.start();

   
bool done = false;
   
while(!done)
   
{
       
Event event = eventList.RemoveEvent();
       
switch(event.id)
       
{
       
case KEY_EVENT:
           
if(event.value == 0x1b) //ESC
           
{
               
gt.requestInterruption();
               
ct.requestInterruption();
               
done = true;
           
}
           
else
           
{
               
putch(event.value);
           
}
           
break;
       
case DATA_EVENT:
           
printf("\nThe new value:%d\n",
                  event.value);
           
break;
       
}
   
}

   
for(int i = 0; i < 2; i++)
   
{
       
endSemaphore.acquire();
   
}
   
puts("\nProgram has finisned!\n");
    return 0;
}


内容如下:

  1. 首先准备EventList和endSemaphore,并且分别启动CreateDataTask和GetKeyTask。

  2. 接下来是事件处理流程,接受并处理来自各个线程的事件。

  3. 最后是线程停止的同步处理。


示例代码、工程


示例代码,工程可以从以下链接下载。

http://download.****.net/detail/craftsman1970/9910670


写在文章的最后


既然已经读到这里了,拜托大家再用一分钟时间,将文章转发到各位的朋友圈,微信群中。


本公共号的成长需要您的支持!
阅读更多更新文章,请扫描下面二维码,关注微信公众号【面向对象思考】
EA&UML日拱一卒-多任务编程超入门-(11)学以致用