实验目的:
练习使用mutex 来保护需要互斥访问对象,同时练习在linux下面使用autotools 来创建Makefile.am 文件编写。
练习将STL 中的 queue 与 linux 中的加锁解锁操作相互结合使用。
为后续的学习网络编程实现多个客户端同时向服务器端发送请求消息做铺垫,本实验中实现的互斥访问队列,
可以用作服务器端为了接受来自多个客户端的请求而开辟的缓冲区,即缓冲消息相应队列。
实验环境:
Ubuntu 操作系统,g++ ,gcc 编译器, autotools 系列工具
实验代码:
1. mutexQueue.hpp
由于互斥访问的队列通常作为类中的成员变量,所以将它的实现方法直接写到 hpp 文件中。
这段代码的主要思想是将STL 中的队列 queue 封装到类mutexQueue 中,并且在类中为了实现对队列中元素的互斥访问,
增加了POSIX 中常用到的互斥量来进行加锁。即每个想要访问队列中元素(访问= 读取 ; 修改 )的线程,
都首先需要申请_mutex, _mutex 是POSIX标准中定义的互斥量,原型是pthread_mutex_t .
只有当当前的队列不被任何线程访问的时候, _mutex 才处于空闲可被申请的状态。当线程成功申请到互斥量_mutex 之后,
即可通过该互斥量对队列进行加锁,即调用POSIX中所定义的, pthread_mutex_lock (pthread_mutex_t *)来对互斥资源
(也称作是临界资源)进行加锁,在加锁操作之后,_mutex 即无法被其他线程申请,
在加锁期间申请占用_mutex 的线程均会被至于阻塞状态,直至当前占用该队列的线程释放锁,_mutex 被释放,
此时之前由于申请占用_mutex 的线程才会被从阻塞状态唤醒,并对该_mutex 进行重新申请,若申请成功,
则通过_mutex 进行加锁,加锁之后独自占用互斥资源,申请失败,继续阻塞。
在这里使用template 的方式来定义类mutexQueue 这样使用者就可以像似使用普通的 STL queue 一样向其中传入定义
的复杂类型一样使用 mutexQueue 所定义的对象了。
#include
#include
template
class mutexQueue
{
private:
std::queue _queue ;
pthread_mutex_t _mutex ;
public :
mutexQueue()
{
_mutex = PTHREAD_MUTEX_INITIALIZER ;
}
unsigned int size()
{
unsigned int s ;
pthread_mutex_lock (&_mutex) ;
s =(unsigned int ) _queue.size() ;
pthread_mutex_unlock (&_mutex) ;
return s ;
}
void push (Data const &data )
{
pthread_mutex_lock (&_mutex) ;
_queue.push(data) ;
pthread_mutex_unlock (&_mutex) ;
}
bool empty () const
{
bool res ;
pthread_mutex_lock (&_mutex ) ;
if ( _queue.size() == 0 )
res = true ;
else
res = false ;
pthread_mutex_unlock (&_mutex) ;
return res ;
}
bool top ( Data &value )
{
/**
here you should add the pthread_mutex_lock too!
because during current thread visiting period
you can not make sure other one or more thread do something(pop , push)
the change the size of the queue 2014/11/11 by Armin
*/
if ( _queue.size() == 0 )
{
return false ;
}
else
{
pthread_mutex_lock (&_mutex) ;
value = _queue.front() ;
pthread_mutex_unlock(&_mutex) ;
return true ;
}
}
void pop ()
{
if (_queue.size() != 0)
{
pthread_mutex_lock (&_mutex) ;
_queue.pop() ;
pthread_mutex_unlock (&_mutex) ;
}
}
} ;
在这段代码中所演示的就是,如何在程序中刚刚定义好的 mutexQueue 这个对象,不过,在本程序中并没有创建多线程,
所以互斥访问的效果根本看不出来。
这段代码,在第一次发布博客的时候错误的 copy 了//mutexQueue.hpp 的源代码,在这里更正一下!
//by Armin1027 2014/11/11
#include
#include
#include "mutexQueue.hpp"
using namespace std ;
int main ( int argc , char * args [] )
{
string line ;
#include
#include
#include "mutexQueue.hpp"
using namespace std ;
int main ( int argc , char * args [] )
{
string line ;
mutexQueue q ;
// cout<<"get size of q "<< q.size() <>line ;
q.push(line) ;
cout<<"input string 2 for queue inserting "<>line ;
q.push(line) ;
// q.push("the end") ;
cout<<"now length of the queue is "<
编译脚本
g++ test.c test.h -o test.exe
是我们在 linux/unix 下面 编写 cpp 程序的时候最为常用的编译命令,但是如果今后写的程序从单个的文件,
到一些列的文件,并且等待编译的文件分布在当前目录下面的各个文件夹中,这种命令写起来就很费劲了。
makefile 是linux 下面 用来组织(编译,连接,最终生成可执行文件 )c++项目的一种最为常用的一种编译脚本,
但是其中的语法规范很复杂,变动性很多,由于每个人都有着各自不同的书写 makefile 的风格,所以makefile
的编写规范还是阅读性是 linux 初学者很难在短时间内理解,掌握,熟练应用的 。
GNU的牛人们为了弥补这种不足,开发出来一套规范的编译工具,这一系列工具统称是 autotools ,关于它的 相关说明
或是原理或者是安装,可以自己去查相关资料,我就不加介绍了。
在这里我要介绍的是,如何根据本实验中的两个文件 mutexQueue.hpp Main.cpp
来编写自己的Makefile文件
1.首先,将目录切换到你所编写的两个文件的所在目录下面:
cd thread_mutex/
ls => Main.cpp , mutexQueue.hpp
2. 输入 autoscan 命令,得到的结果如下所示
new : autoconf.log , configure.scan
old : Main.cpp mutexQueue.hpp
3.将生成的 configure.scan 重新命名为 configure.in
mv configure.scan configure.in
4. 根据自己编写的项目中原文件的结构来修改configure.in 中的内容
修改前:
修改后:
5.修改之后如下图所示,退出之后,分别按顺序输入命令
aclocal
autoconf
autoheader
6.编写Makefile.am 文件,这个文件中定义了将要生成的makefile 中的规范
在这里需要说明一点的就是,如果在不使用 autotools 的情况下,使用命令在随后需要添加一条 -lpthread
来表明需要使用到库 pthread 中所定义的相关方法或是变量,相应的这一点在Makefile.am 中也是需要有所体现的,
所以就有了mQueue_LDADD=-lpthread 这个对应的是 pthread 库中的相关调用,是作为静态库被引入的,
而后面的那个 _LDFLAGS = .. 说明的是 pthread 是作为动态库被引入的。
静态库和动态库就是,静态库是与程序进行绑定好的,比较占内存容量,不过由于是事前绑定好
(”事前“,在这里我还不太确定这个“事” 是编译还是链接阶段); 而动态链接,就是只有在程序中需要使用的时候,
才将其与你所写的程序进行链接,速度较慢,但是内存资源消耗很少。
bin_PROGRAMS= 名字是将要生成的可执行文件的名字。
7. 输入命令automake ,会对Makefile.am 进行解析生成 Makefile.in
这个地方不得不说的是, automake 命令后面的 --add-missing 选项是必须要加上的(至少在本试验中是这样的,
并且,这个 automake --add-missing 命令至少需要运行 2 次以上,知道不抛出异常信息为止,
不过请注意,这里我说的异常信息,是由于项目中缺少相关的文件,而这些文件通过 --add-missing 参数随着运行
次数不断的增多而添加到当前的项目中的,当然了,如果你写的程序本身有问题或者是编译文件中存在问题的话,
那带 --add-missing 参数运行即便都得不到你想要的结果的)
automake 之后生成Makefile.in
输入命令 ./configure , 如果正确执行的话会生成 Makefile , 这个就是我们编译文件生成的 Makefile了
8.如果前面的步骤执行正确的话,生成Makefile, 这时只需要输入 make
即可让程序自动编译,生成最终在Makefile.am 中定义的bin_PROGRAMS = x
以 x 为名称的可执行程序的
9.在当前的目录中输入 ./x 即可以使得程序得以运行 (绿色的 mQueue 即为所需要生成的可执行文件 )
10, ./mQueue 运行该可执行文件
如果不使用autotools 进行编译的话,直接使用命令行来编译这两文件也不难:
g++ Main.cpp mutexQueue.hpp -lpthread -o Main :这样既可 。
因为我们在mutexQueue.hpp 中使用到的mutex 是 pthread.h 库中的 pthread_mutex_t
所以在程序编译之后,后期运行之前进行链接的时候是需要使用 pthread 库中所定义的相关方法的,
所以需要指定一下 -lpthread (-l :link 在连接阶段需要使用pthread.h 库中的相关函数定义与实现)
不足及改进
1. 没有创建多个线程来同时访问,要在后续的代码中,定义函数指针来通过 POSIX 中的 pthread_t ,pthread_create(...)
等相关方法在Main 中启动多个线程来实现互斥访问互斥队列。
2. 没有实现进程间通信,如果在程序中使用condition 相关线程通信的POSIX 相关的函数的话,
可以试一试模拟生产者消费者之间的通信问题从一对一到一对多,从而可以衍生出服务器端与客户端进行通信的效果。
3. 在熟练掌握pthread_create(...) 和函数指针的的基础上可以结合前一个实验来实现同时创建两个线程,
一个是 Server 另一个是 client
使用相同的函数签名 ,通过一个函数指针指向两种调用。
这些想法可以在这几天试着实验一下。