使用线程间通信之条件变量

Posted mfmdaoyou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用线程间通信之条件变量相关的知识,希望对你有一定的参考价值。

近期用C++写安卓下的一个通讯程序。作为jni库给java调用,採用多线程轮询遇到一个问题描写叙述例如以下:
A线程收到数据,放入队列,是生产者。
B、C、D若干个线轮询训消息队列,假设队列有数据就取出进行处理,没数据就Sleep(T)歇息,问题是这个T值取多大合适?取大了消息处理不及时。取小了手机cpu上升电池非常快耗光。



这个问题最佳解法是採用条件变量,能够比較完美解决这个问题。下面代码使用C++封装,用win32 SDK的条件变量举例,Linux下有全然等价的概念:

// 线程消息通知
class ThreadMsgNotify
{
   // 条件变量和临界变量
   CONDITION_VARIABLE cv_;
   CRITICAL_SECTION cs_;
public:
   ThreadMsgNotify();
   ~ThreadMsgNotify();

   int Wait(DWORD ms);  // 消费者调用此函数,阻塞等待毫秒数
   void Notify();  //  生产者调用此函数: 发出通知
};

各成员方法实现例如以下:

// ---------------------------------------
ThreadMsgNotify::ThreadMsgNotify()
{
   InitializeConditionVariable(&cv_);
   InitializeCriticalSection(&cs_);
}

ThreadMsgNotify::~ThreadMsgNotify()
{
   WakeAllConditionVariable(&cv_);  // 唤醒所有线程
   Sleep(50);

   DeleteCriticalSection(&cs_);
   DeleteConditionVariable(&cv_);
}

int ThreadMsgNotify::Wait(DWORD ms)  // 消费者,阻塞等待毫秒数
{
   EnterCriticalSection(&cs_);
   int ret = SleepConditionVariableCS(&cv_, &cs_, ms);  // 等待
   LeaveCriticalSection(&cs_);
   return(ret);
}

void ThreadMsgNotify::Notify()  // 生产者: 发出通知
{
   EnterCriticalSection(&cs_);
   WakeConditionVariable(&cv_);  // 唤醒一个等待线程(假设有的话)
   LeaveCriticalSection(&cs_);
}
// --------------

上面的代码非常easy。差点儿不用解释。

以下再给出測试代码:

class TagThreadNotifyTest
{
  private:
     list<string> msgList;
     bool isEnd = false;
     CRITICAL_SECTION cs_;
  public:
     int no;
     ThreadMsgNotify threadNotify;  // 线程通知,接收队列收到消息时通过该对象唤醒处理线程

     TagThreadNotifyTest();
     ~TagThreadNotifyTest();

     void New(const char* info);  // 生产者添加一条消息
     int Recv(string& msg);  // 消费者读取一条消息,返回读取前队列中的消息数

     // 消费者线程函数
     static LRESULT WINAPI ProcThread(void* lParam);
};

// 辅助函数。获取当前时间戳
void CurTime(char* timeStr)
{
   SYSTEMTIME st;
   GetLocalTime(&st);
   sprintf(timeStr, "%02d:%02d:%02d.%03d", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
}

TagThreadNotifyTest::TagThreadNotifyTest()
{
  InitializeCriticalSection(&cs_);

  // 创建3个消费者线程
  for(int i=0; i<3; i++){
     no = i;
     DWORD dwThreadid;
     HANDLE thd = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ProcThread,
                (void*)this, 0, &dwThreadid);
     CloseHandle(thd);
     Sleep(30);
  }
}

TagThreadNotifyTest::~TagThreadNotifyTest()
{
  isEnd = true;
  Sleep(100);
  DeleteCriticalSection(&cs_);
}

// 生产者将新消息放入队列
void TagThreadNotifyTest::New(const char* info)
{
   // 加锁后插入队列
   EnterCriticalSection(&cs_);
      msgList.push_back(info);
   LeaveCriticalSection(&cs_);

   threadNotify.Notify();  // 通知其它线程,去处理数据

   printf("notify...\n");
}

// 消费者读取消息。假设没有将返回0
int TagThreadNotifyTest::Recv(string& msg)
{
   EnterCriticalSection(&cs_);
      int n = msgList.size();
      if( n>0 ){
          msg = msgList.front();
          msgList.pop_front();
      }
   LeaveCriticalSection(&cs_);
   return(n);
}

// 消费者线程
LRESULT WINAPI TagThreadNotifyTest::ProcThread(void* lParam)
{
  TagThreadNotifyTest * test = (TagThreadNotifyTest *)lParam;
  int no = test->no;
  printf("Thread start,  no=%d...\n", no);
  char timeStr[80];
  while( !test->isEnd ){
     string msg;
     int ret = test->Recv(msg);  // 读取一条消息
     CurTime(timeStr);
     if( ret ){  // 假设有就打印出来
        printf(" [%d %s]Recv: %s\n", no, timeStr, msg.c_str());
        Sleep(1000);  // 延时1秒模拟处理较慢的情况
        continue;
     }
     else{  // 没有收到
        printf(" [%d %s]...\n", no, timeStr);
     }
 
     // 歇息15秒,假设有通知则会随时结束歇息
     test->threadNotify.Wait(15000);
  }

  printf("Thread End : no=%d.\n", no);
  return(1);
}

int main()
{
   // 控制台測试程序

   // new一个測试对象,此对象会创建3个消费者线程
   TagThreadNotifyTest* test = new TagThreadNotifyTest();

   // 作为生产者线程。就是接收你的按键,回车后产生一条消息
   while(true){
       char s[500];
       memset(s, 0, 500);
       gets(s);
       if( strcmp(s, "exit")==0 ){
           break;
       }
       if( s[0]=='\0' )
           continue;

       // 提交消息
       test->New(s);
   }

   delete test;
   return(0);
}

在windows和Linux下(替换成相应函数)均測试通过。

输入一个回车后,消费者线程将马上取到消息并打印出来。假设没有消息。则消费者线程等待15秒。CPU非常轻松。


以上是关于使用线程间通信之条件变量的主要内容,如果未能解决你的问题,请参考以下文章

转:Java并发编程之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

Go中多协程协作之sync.Cond

嵌入式开发基础之线程间通信

Java并发编程之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

python基础(21)-线程通信

多线程:Signal vs BusyWait(Polling),线程间条件变量问题