利用信号量实现简单进程内消息队列
前几天学习了线程和线程的通信方式,线程的通信方式与进程有部分类似,比如自旋锁、信号量等。但进程间还有一个消息队列的通信方式,用于进程内线程的一些轻量级应用场合显得过于大材小用。如果要做到进程内线程之间类似于消息队列的数据传输通信,尽管可以使用条件变量加上全局数据的方式,但这样未免线程间的耦合性太强了。因此打算以信号量为基础在应用层实现一个简单的消息队列机制。
引入信号量的目的是为了令等待(接收)消息的线程在队列中没有消息的情况下陷入阻塞,或者在队列中存在多个消息时,可以多次获取到信号量,这里的信号量是计数型信号量。
有了信号量为基础,就可以得出编程思路:
①消息队列结构:包含一个信号量、一个自旋锁和一个先进先出的队列结构。
②队列结构:一个单向链表,包含一个头节点指针、下一个节点指针、一个数据指针、一个数据长度变量。
③消息队列的初始化:内存空间清0,即将所有指针设为NULL。
④发送消息:申请一个消息节点的内存空间,再向队列里添加该消息节点,然后释放1个信号量。
⑤接受消息:阻塞请求信号量,获取到信号量后,从队列里取出第一个消息节点,拷贝数据后,释放该节点的内存空间。
⑥消息队列的销毁:循环移出队列里剩余的消息并释放内存空间,最后销毁信号量和自旋锁。
下面就开始逐一实现:
·首先是队列结构类型的声明:

·消息队列的初始化

初始化做的事情比较少,只需要进行空指针判断、消息队列结构清0和信号量的初始化。
·向队列添加消息节点

在消息队列结构初始化好后,其中的队列是一个空链表。因此每次在添加节点时都需要判断当前队列是否为空,若为空则新节点插入表头并设置新的表头,若不为空则插入表尾。
这是一个文件内部调用的函数,因此定义成静态static。
·从队列移出一个节点

根据先进先出的原则,从队列中移除一个节点应从链表的第一个节点开始移除,所谓移除节点仅仅是将其从链表中移出,其所占的内存空间还不能释放,需要等该节点的消息数据被读取后才可以释放。因此通过指向指针的指针将该节点返回以便处理。
这是也一个文件内部调用的函数。
·发送消息

发送消息函数有3个形式参数,第一个msg就是指向需要请求消息的消息队列,第二个参数buf是指向发送线程要发送的数据缓冲区,第三个参数size指示数据的大小,单位字节。
发送消息首先要申请一个队列节点占用的内存空间,然后将该节点添加进队列中,对该节点添加消息数据和消息大小。最后再释放1个信号量即可。
·接收(等待)消息

接收消息函数有4个形式参数,第一个msg与发送消息相同,也是指向需要请求消息的消息队列;第二个参数buf是指向接收线程要存放数据的缓冲区;第三个参数buf_size指示接收线程数据缓冲区的大小,防止消息长度超过该数据缓冲区而导致的非法访问;第四个参数msg_size指向保存消息长度的变量,需要由接收线程提供。
发送数据首先要对参数有效性进行判断,之后请求一个信号量,若此时信号量为0,则表示队列中没有消息,此时线程应陷入阻塞。若有一个线程向此消息队列中添加了一个消息,
则接收数据的线程因为请求到了信号量而被唤醒,之后应从队列中移出一个消息节点,并将节点的数据拷贝到接收线程,最后释放该节点的内存空间。
注:在向队列添加和移除节点时,应该有保护机制,这里使用的是自旋锁spinlock,以确保多个线程并发访问队列时不存在数据一致性问题。
·测试:
最后编写一个测试程序测试该消息队列是否可以使用,判断是否可以使用的标准有:
①是否可以在2个线程之间实现1对1单向通信(1收1发)。
②是否可以在多个线程之间实现1对多单向通信(1收多发)。
测试程序:

测试程序中创建了3个入口地址相同的线程,通过传入线程的参数来令他们每隔1秒向主线程发送不同的消息,而主线程一直循环等待消息并打印。执行结果如下:

可以看出这个消息队列在“3个线程发送,1个线程接收”的情况下是没有问题的。可以用作一些简单应用场合。
最后附上源码文件:
链接:https://pan.baidu.com/s/1YDZLqxm3elmG2gf3sNkNqQ
提取码:1234