利用最近一周晚上下班回来的闲散时间继续学习boost线程库的使用。在我的上一篇文章《boost c++ lib on linux(3) - thread库的使用初学》中记录了boost线程库的基本使用方法和编译链接,参杂了一些线程互斥变量,本地存储的类的使用。本文记录了本周我学习线程同步条件变量的使用实例,主要实现了生产者消费者模型的队列使用过程,通过多线程条件变量实现生产者消费者模型队列。
这里的一点体会就是,在实现一个书本上的实例的时候,我们不光是简单实现或者将代码重新敲一遍就是,而应该尽量将书本合上,自己思考要实现一个这样的实例应该怎么开始,这样便于对实例理解更加深刻。在编写本实例的时候,我先从队列的基本数据结构开始,实现了一个循环队列的模板类,然后针对该模板类,利用boost的单元测试库设计了单元测试代码,并运行通过所有的单测检查,保证循环队列模板类的代码正确性。基本的循环队列数据结构实现以后就着手开始编写生产者和消费这对这个队列的操作代码,使用boost对线程库编写多线程代码。先验证在没有条件变脸支持的队列在对线程环境下会是什么结果。最后复用循环队列模板代码,重新设计了支持多线程的条件变量的队列数据结构,并做了boost单元测试,保证基本的功能没问题后,重新编译多线程访问队列的代码,并运行查看结果。
在这过程中,我们及学习了基本的boost thread同步条件变量的使用,同时也学习到了:
1、boost 的单元测试的框架的使用方法以及在编译过程中应该注意的事项;
2、巩固和复习了模板类的编写和使用,代码的复用(这里我没有直接继承非多线程的循环队列的类,偷了懒直接复制代码来改了实现多线程版本的循环队列);
3、学会了在多目录情况下工程代码的管理和Makefile.am的编写和代码编译;
4、体会了一把linux环境下相对完整的软件编写,以及利用单元测试框架保证代码模块质量的过程;
工程的目录结构如图所示:
src目录下面是生产这消费者模型及多线程调用的实现代码,unittest下面是单元测试代码。
configure.in内容如下,用于指定生成Makefile文件等:
# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS]) AC_CONFIG_SRCDIR([unittest/TestQueueTemplate.cpp]) #AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE # Checks for programs. AC_PROG_CXX AC_PROG_CC # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. AC_CHECK_HEADER_STDBOOL # Checks for library functions. #AC_CONFIG_FILES([Makefile # src/Makefile # unittest/Makefile]) AC_OUTPUT([Makefile src/Makefile unittest/Makefile])
SUBDIRS=src unittest
/************************************************************************* > File Name: Queue.h > Author: Liu Xin > Created Time: 2012年11月05日 星期一 21时03分32秒 ************************************************************************/ #ifndef _QUEUE_H_ #define _QUEUE_H_ template <class type> class Queue { public: Queue(){} Queue(int sz); void enqueue(type t); type* dequeue(); type* get_front(); type* get_tail(); bool is_empty(); bool is_full(); int queue_size(); void print_queue(); protected: type* buf; int size; int front; int tail; }; #endif /************************************************************************* > File Name: Queue.cpp > Author: Liu Xin > Created Time: 2012年11月05日 星期一 21时06分01秒 ************************************************************************/ #include "Queue.h" #include <iostream> using namespace std; template <class type> Queue<type>::Queue(int sz){ buf = NULL; buf = new type[sz]; size = sz; front = 0; tail = 0; } template <class type> void Queue<type>::enqueue(type t){ if (!is_full()){ buf[tail]=t; tail = (tail+1) % size; }else std::cout << "Queue is full. " << std::endl; } template <class type> type* Queue<type>::dequeue(){ if (front!=tail) { int idx = front; front = (front+1) % size; return &buf[idx]; }else{ std::cout << "Queue is empty." << std::endl; return NULL; } } template <class type> bool Queue<type>::is_empty(){ if (front==tail) return true; return false; } template <class type> bool Queue<type>::is_full(){ if(front == tail + 1) // 留空一个位置来标记空和满的情况 return true; return false; } template <class type> int Queue<type>::queue_size() { return front-tail; } template <class type> type* Queue<type>::get_front() { if (front!=tail) return &buf[front]; else{ std::cout << "Queue empty. " << std::endl; return NULL; } } template <class type> type* Queue<type>::get_tail() { if (tail==0 && tail!=front) return &buf[size-1]; else if (tail!=front) return &buf[tail-1]; else{ std::cout << "Queue empty. " << std::endl; return NULL; } } template <class type> void Queue<type>::print_queue() { int i = front; std::cout << "QUEUE: "; while (i!=tail) { std::cout << buf[i] << " "; i = (i+1) % size; } std::cout << std::endl; }
/************************************************************************* > File Name: Queue.h > Author: Liu Xin > Created Time: 2012年11月05日 星期一 21时03分32秒 ************************************************************************/ #ifndef _QUEUE_MT_H_ #define _QUEUE_MT_H_ #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> using namespace boost; using namespace std; template <class type> class QueueMT { public: QueueMT(){} QueueMT(int sz); ~QueueMT(); void enqueue(type t); type* dequeue(); type* get_front(); type* get_tail(); bool is_empty(); bool is_full(); int queue_size(); void print_queue(); protected: type* buf; int size; int front; int tail; // Multi-thread variables mutex mu; condition_variable_any cond_enqueue; condition_variable_any cond_dequeue; }; #endif /************************************************************************* > File Name: QueueMT.cpp > Author: Liu Xin > Created Time: 2012年11月05日 星期一 21时06分01秒 ************************************************************************/ #include "QueueMT.h" #include <iostream> #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> using namespace std; using namespace boost; template <class type> QueueMT<type>::QueueMT(int sz){ buf = NULL; buf = new type[sz]; size = sz; front = 0; tail = 0; } template <class type> QueueMT<type>::~QueueMT() { if(buf!=NULL){ delete []buf; } } template <class type> void QueueMT<type>::enqueue(type t){ { mutex::scoped_lock lock(mu); while(is_full()){ cond_enqueue.wait(mu); } buf[tail]=t; tail = (tail+1) % size; } cond_dequeue.notify_one(); } template <class type> type* QueueMT<type>::dequeue(){ int idx=tail; { mutex::scoped_lock lock(mu); while(is_empty()){ cond_dequeue.wait(mu); } idx = front; front = (front+1) % size; } cond_enqueue.notify_one(); return &buf[idx]; } template <class type> bool QueueMT<type>::is_empty(){ if (front==tail) return true; return false; } template <class type> bool QueueMT<type>::is_full(){ if(front == tail + 1) // 留空一个位置来标记空和满的情况 return true; return false; } template <class type> int QueueMT<type>::queue_size() { return front-tail; } template <class type> type* QueueMT<type>::get_front() { if (front!=tail) return &buf[front]; else{ std::cout << "Queue empty. " << std::endl; return NULL; } } template <class type> type* QueueMT<type>::get_tail() { if (tail==0 && tail!=front) return &buf[size-1]; else if (tail!=front) return &buf[tail-1]; else{ std::cout << "Queue empty. " << std::endl; return NULL; } } template <class type> void QueueMT<type>::print_queue() { int i = front; std::cout << "Queue: "; while (i!=tail) { std::cout << buf[i] << " "; i = (i+1) % size; } std::cout << std::endl; }
/************************************************************************* > File Name: condition.cpp > Author: Liu Xin > Created Time: 2012年11月05日 星期一 20时55分58秒 ************************************************************************/ #include<iostream> #include<boost/thread/thread.hpp> #include<boost/bind.hpp> #include<boost/thread/mutex.hpp> #include<boost/thread/condition.hpp> #include<boost/ref.hpp> #include"QueueMT.h" #include"QueueMT.cpp" // 这里是必需,否则链接时Queue的成员函数均提示未定义,模板类比较特殊在这里 using namespace std; using namespace boost; mutex io_mutex; // io mutex void producer(QueueMT<int> &queue, int n, int threadId) { for (int i=0; i<n; i++) { { mutex::scoped_lock lock(io_mutex); std::cout << "Thread " << threadId << " enqueue: " << i << std::endl; } queue.enqueue(i); { mutex::scoped_lock lock(io_mutex); queue.print_queue(); } thread::yield(); } } void consumer(QueueMT<int> &queue, int n, int threadId) { for (int i=0; i<n; i++) { int* front = queue.dequeue(); { mutex::scoped_lock lock(io_mutex); if(front!=NULL){ std::cout << "Thread " << threadId << " dequeue: " << *front << std::endl; queue.print_queue(); } } // thread::yield(); // sleep(1); } } void testThreadSafeQueueTemplate() { // 使用thread_group管理线程 thread_group threads; QueueMT<int> queue(16); threads.create_thread(bind(producer, ref(queue), 15, 1)); threads.create_thread(bind(consumer, ref(queue), 3, 2)); threads.create_thread(bind(consumer, ref(queue), 9, 3)); threads.create_thread(bind(consumer, ref(queue), 3, 4)); threads.join_all(); } int main() { testThreadSafeQueueTemplate(); return 0; }
AUTOMAKE_OPTIONS=foreign bin_PROGRAMS=thread_demo recursive_mutex condition_demo INCLUDES=-I/usr/local/include -I. LDADD=/usr/local/lib/libboost_thread.so /usr/local/lib/libboost_system.so thread_demo_SOURCES=thread_demo.cpp recursive_mutex_SOURCES=recursive_mutex.cpp condition_demo_SOURCES=condition.cpp Queue.cpp
/************************************************************************* > File Name: unittest/main.cpp > Author: Liu Xin > Mail: [email protected] > Created Time: 2012年11月07日 星期三 23时10分57秒 ************************************************************************/ #define BOOST_TEST_MAIN // 这是单元测试的入口,否则链接时找不到main函数入口 #include <boost/test/included/unit_test.hpp> /************************************************************************* > File Name: TestQueueTemplate.cpp > Author: Liu Xin > Mail: [email protected] > Created Time: 2012年11月07日 星期三 21时30分07秒 ************************************************************************/ #define BOOST_AUTO_TEST_MODULE QueueTemplateTest #include<iostream> #include<boost/test/unit_test.hpp> #include <../src/Queue.h> #include <../src/Queue.cpp> #include <../src/QueueMT.h> #include <../src/QueueMT.cpp> using namespace std; BOOST_AUTO_TEST_SUITE(QueueTemplateBasicTest) BOOST_AUTO_TEST_CASE(testQueueTemplate) { std::cout << "==================== testQueueTemplate ==================" << std::endl; Queue<int> q(4); q.enqueue(1); BOOST_CHECK(*q.get_front() == 1); // 1 q.enqueue(2); BOOST_CHECK(*q.get_tail() == 2); // 2 q.print_queue(); BOOST_CHECK(q.is_empty() == false); BOOST_CHECK(q.is_full() == false); BOOST_CHECK(*q.dequeue() == 1); q.print_queue(); q.enqueue(3); BOOST_CHECK(*q.get_tail() == 3); q.print_queue(); q.enqueue(4); BOOST_CHECK(*q.get_tail() == 4); q.print_queue(); q.enqueue(5); BOOST_CHECK(*q.get_tail() == 4); q.print_queue(); BOOST_CHECK(q.is_full() == true); BOOST_CHECK(*q.dequeue() == 2); BOOST_CHECK(*q.dequeue() == 3); BOOST_CHECK(*q.dequeue() == 4); BOOST_CHECK(q.dequeue() == NULL); q.enqueue(8); BOOST_CHECK(*q.get_tail() == 8); BOOST_CHECK(q.is_empty() == false); q.print_queue(); } BOOST_AUTO_TEST_CASE(testQueueMTTemplate) { std::cout << "==================== testQueueMTTemplate ==================" << std::endl; QueueMT<int> q(4); q.enqueue(1); BOOST_CHECK(*q.get_front() == 1); // 1 q.enqueue(2); BOOST_CHECK(*q.get_tail() == 2); // 2 q.print_queue(); BOOST_CHECK(q.is_empty() == false); BOOST_CHECK(q.is_full() == false); BOOST_CHECK(*q.dequeue() == 1); q.print_queue(); q.enqueue(3); BOOST_CHECK(*q.get_tail() == 3); q.print_queue(); q.enqueue(4); BOOST_CHECK(*q.get_tail() == 4); q.print_queue(); BOOST_CHECK(*q.dequeue() == 2); q.enqueue(5); BOOST_CHECK(*q.get_tail() == 5); q.print_queue(); BOOST_CHECK(q.is_full() == true); BOOST_CHECK(*q.dequeue() == 3); BOOST_CHECK(*q.dequeue() == 4); BOOST_CHECK(*q.dequeue() == 5); q.print_queue(); } BOOST_AUTO_TEST_SUITE_END()
bin_PROGRAMS=unittest unittest_SOURCES=main.cpp TestQueueTemplate.cpp INCLUDES=-I. -I/usr/local/include unittest_LDADD=/usr/local/lib/libboost_system.so \ /usr/local/lib/libboost_unit_test_framework.so \ /usr/local/lib/libboost_prg_exec_monitor.so \ /usr/local/lib/libboost_thread.so
liuxin@liuxin-Inspiron-1440:~/workspace/develop/boost/thread/unittest$ ./unittest Running 2 test cases... ==================== testQueueTemplate ================== QUEUE: 1 2 QUEUE: 2 QUEUE: 2 3 QUEUE: 2 3 4 Queue is full. QUEUE: 2 3 4 Queue is empty. QUEUE: 8 ==================== testQueueMTTemplate ================== Queue: 1 2 Queue: 2 Queue: 2 3 Queue: 2 3 4 Queue: 3 4 5 Queue: *** No errors detected
liuxin@liuxin-Inspiron-1440:~/workspace/develop/boost/thread/src$ ./condition_demo Thread 1 enqueue: 0 Queue: 0 Thread 1 enqueue: 1 Queue: 1 Thread 1 enqueue: 2 Thread 3 dequeue: 1 Queue: Queue: Thread 2 dequeue: 0 Queue: Thread 1 enqueue: 3 Queue: 3 Thread 2 dequeue: 3 Queue: Thread 1 enqueue: 4 Queue: 4 Thread 2 dequeue: 4 Queue: Thread 1 enqueue: 5 Queue: 5 Thread 1 enqueue: 6 Queue: 5 6 Thread 1 enqueue: 7 Queue: 5 6 7 Thread 1 enqueue: 8 Queue: 5 6 7 8 Thread 1 enqueue: 9 Queue: 5 6 7 8 9 Thread 1 enqueue: 10 Queue: 5 6 7 8 9 10 Thread 1 enqueue: 11 Queue: 5 6 7 8 9 10 11 Thread 1 enqueue: 12 Queue: 5 6 7 8 9 10 11 12 Thread 1 enqueue: 13 Queue: 5 6 7 8 9 10 11 12 13 Thread 1 enqueue: 14 Queue: 5 6 7 8 9 10 11 12 13 14 Thread 3 dequeue: 5 Queue: 6 7 8 9 10 11 12 13 14 Thread 3 dequeue: 6 Queue: 7 8 9 10 11 12 13 14 Thread 3 dequeue: 7 Queue: 8 9 10 11 12 13 14 Thread 3 dequeue: 8 Queue: 9 10 11 12 13 14 Thread 3 dequeue: 9 Queue: 10 11 12 13 14 Thread 3 dequeue: 10 Queue: 11 12 13 14 Thread 3 dequeue: 11 Queue: 12 13 14 Thread 3 dequeue: 12 Queue: 13 14 Thread 4 dequeue: 2 Queue: 13 14 Thread 4 dequeue: 13 Queue: 14 Thread 4 dequeue: 14 Queue:上面的结果输出展示了一个生产者消费者队列的执行过程。有一个生产者线程向队列添加元素,三个消费者从队列取出元素,每个线程入队和出队的次数由线程函数的参数指定,详见condition.cpp代码。