接前一篇文章:Linux实用功能代码集(3) ------ 线程间消息队列(1)
本文内容参考:
消息队列函数由msgget、msgctl、msgsnd、msgrcv四个函数解析_msgget函数-CSDN博客
特此致谢!
上一回讲解了线程间消息队列机制以及其中会用到的接口函数,包括:



本回给出实际的完整代码并进行讲解。完整代码如下:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#define MAX_MSG_SIZE 1024
#define MAX_MSG_NUM 16
typedef struct {
long type;
char data[MAX_MSG_SIZE];
} msg_t;
typedef struct {
msg_t msgs[MAX_MSG_NUM];
int head;
int tail;
int count;
pthread_mutex_t mutex;
pthread_cond_t cond;
} msg_queue_t;
msg_queue_t g_queue;
//pthread_t g_recv_tid;
void queue_init(void)
{
g_queue.head = g_queue.tail = 0;
g_queue.count = 0;
pthread_mutex_init(&g_queue.mutex, NULL);
pthread_cond_init(&g_queue.cond, NULL);
}
int send_msg(long type, const char *data)
{
if (!data)
return -1;
pthread_mutex_lock(&g_queue.mutex);
if (g_queue.count >= MAX_MSG_NUM)
{
pthread_mutex_unlock(&g_queue.mutex);
return -1;
}
g_queue.msgs[g_queue.tail].type = type;
strcpy(g_queue.msgs[g_queue.tail].data, data);
g_queue.tail = (g_queue.tail + 1) % MAX_MSG_NUM;
g_queue.count++;
pthread_cond_signal(&g_queue.cond); //wake up recv thread
pthread_mutex_unlock(&g_queue.mutex);
return 0;
}
int recv_msg(long *type, char *data)
{
if (!type || !data)
return -1;
pthread_mutex_lock(&g_queue.mutex);
while (g_queue.count == 0) //receive (blocked mode)
pthread_cond_wait(&g_queue.cond, &g_queue.mutex);
*type = g_queue.msgs[g_queue.head].type;
strcpy(data, g_queue.msgs[g_queue.head].data);
g_queue.head = (g_queue.head + 1) % MAX_MSG_NUM;
g_queue.count--;
pthread_mutex_unlock(&g_queue.mutex);
return 0;
}
void *recv_thread(void *arg)
{
long type;
char data[MAX_MSG_SIZE] = {0};
while (1)
{
recv_msg(&type, data);
printf("received message: type=%ld, data=%s\n", type, data);
}
pthread_exit(NULL);
}
int detach_thread_create(pthread_t *thread_handle, void *(*fn)(void *), void *arg)
{
pthread_attr_t tattr;
int ret = -1;
if (!thread_handle || !fn)
{
printf("parameter can't be null\n");
return -1;
}
ret = pthread_attr_init(&tattr);
if (ret)
{
printf("pthread_attr_init error: %s\n", strerror(errno));
//log_e("pthread_attr_init Error:%s", strerror(err));
return -1;
}
ret = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
if (ret)
{
printf("pthread_attr_setdetachstate error: %s\n", strerror(errno));
pthread_attr_destroy(&tattr);
return -1;
}
ret = pthread_create(thread_handle, &tattr, fn, arg);
if (ret)
{
printf("pthread_create error: %s\n", strerror(errno));
pthread_attr_destroy(&tattr);
return -1;
}
pthread_attr_destroy(&tattr);
return 0;
}
int main(int argc, char *argv[])
{
int ret;
pthread_t recv_tid;
msg_t s_msg;
queue_init();
ret = detach_thread_create(&recv_tid, recv_thread, NULL);
if (ret)
{
printf("detach_thread_create failed\n");
return -1;
}
while (1)
{
int c = getchar();
if (c == 'a' || c == 'A')
{
s_msg.type = 0x01111111;
memcpy(s_msg.data, "abcdefgh", strlen("abcdefgh") + 1);
send_msg(s_msg.type, s_msg.data);
}
else if(c == 'e' || c == 'E')
{
printf("%s exit\n", argv[0]);
return -1;
}
}
return 0;
}
代码中首先定义了两个数据类型:msg_t和msg_queue_t。
cpp
typedef struct {
long type;
char data[MAX_MSG_SIZE];
} msg_t;
cpp
typedef struct {
msg_t msgs[MAX_MSG_NUM];
int head;
int tail;
int count;
pthread_mutex_t mutex;
pthread_cond_t cond;
} msg_queue_t;
msg_t实际上是仿照进程间通信的消息队列中的经常会定义并用到的msgbuf_t类型:
cpp
typedef struct msgbuf
{
long mtype;
char mtext[TEXT_SIZE];
} msgbuf_t;
其中存的是消息类型和数据(消息内容)。
而真正的核心结构是msg_queue_t。其中分为了3部分:
- 消息类型和数据
cpp
msg_t msgs[];
- 消息缓冲区相关
cpp
msg_t msgs[MAX_MSG_NUM];
int head;
int tail;
int count;
- 互斥锁和条件变量
cpp
pthread_mutex_t mutex;
pthread_cond_t cond;
有了以上数据结构,地基就算打好了。
这个消息队列的特点:
✅ 线程安全(加了锁)
✅ 阻塞等待消息(不占CPU)
✅ 支持多线程发送、多线程接收
✅ 轻量、极快
✅ 不用系统调用,纯用户态实现
接下来讲解具体函数实现代码:
(1)初始化(全局)消息队列
主(main)函数中一上来先调用queue_ini(),初始化(全局)消息队列。相关代码如下:
cpp
int main(int argc, char *argv[])
{
int ret;
pthread_t recv_tid;
msg_t s_msg;
queue_init();
......
return 0;
}
cpp
void queue_init(void)
{
g_queue.head = g_queue.tail = 0;
g_queue.count = 0;
pthread_mutex_init(&g_queue.mutex, NULL);
pthread_cond_init(&g_queue.cond, NULL);
}
queue函数就是初始化全局消息队列。将头指针、尾指针置为0,也就代表读、写指针都在最开始的位置;将计数也置为0,表示没有消息。然后就是初始化线程锁和条件变量,两者须配合使用。
(2)创建消息队列接收线程(分离式)
接下来,创建消息队列接收线程。注意,这里是创建的是分离式线程(创建一般线程也可以,并并无实质影响)。相关代码如下:
cpp
ret = detach_thread_create(&recv_tid, recv_thread, NULL);
if (ret)
{
printf("detach_thread_create failed\n");
return -1;
}
cpp
int detach_thread_create(pthread_t *thread_handle, void *(*fn)(void *), void *arg)
{
pthread_attr_t tattr;
int ret = -1;
if (!thread_handle || !fn)
{
printf("parameter can't be null\n");
return -1;
}
ret = pthread_attr_init(&tattr);
if (ret)
{
printf("pthread_attr_init error: %s\n", strerror(errno));
//log_e("pthread_attr_init Error:%s", strerror(err));
return -1;
}
ret = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
if (ret)
{
printf("pthread_attr_setdetachstate error: %s\n", strerror(errno));
pthread_attr_destroy(&tattr);
return -1;
}
ret = pthread_create(thread_handle, &tattr, fn, arg);
if (ret)
{
printf("pthread_create error: %s\n", strerror(errno));
pthread_attr_destroy(&tattr);
return -1;
}
pthread_attr_destroy(&tattr);
return 0;
}
(3)接收线程中以阻塞方式接收消息
在接收线程中,调用recv_msg函数以阻塞方式(通过pthread_cond_wait函数实现)接收消息。相关代码如下:
cpp
int recv_msg(long *type, char *data)
{
if (!type || !data)
return -1;
pthread_mutex_lock(&g_queue.mutex);
while (g_queue.count == 0) //receive (blocked mode)
pthread_cond_wait(&g_queue.cond, &g_queue.mutex);
*type = g_queue.msgs[g_queue.head].type;
strcpy(data, g_queue.msgs[g_queue.head].data);
g_queue.head = (g_queue.head + 1) % MAX_MSG_NUM;
g_queue.count--;
pthread_mutex_unlock(&g_queue.mutex);
return 0;
}
void *recv_thread(void *arg)
{
long type;
char data[MAX_MSG_SIZE] = {0};
while (1)
{
recv_msg(&type, data);
printf("received message: type=%ld, data=%s\n", type, data);
}
pthread_exit(NULL);
}
如果缓冲区中有消息,则拷贝给出参,并移动头指针;否则阻塞等待。
(4)等待输入,并发送消息
在main函数的主循环中,等待键盘输入。如果输入'A'或'a',则调用send_msg函数发送消息;如果接收到输入'E'或'e',则退出程序。相关代码如下:
cpp
while (1)
{
int c = getchar();
if (c == 'a' || c == 'A')
{
s_msg.type = 0x01111111;
memcpy(s_msg.data, "abcdefgh", strlen("abcdefgh") + 1);
send_msg(s_msg.type, s_msg.data);
}
else if(c == 'e' || c == 'E')
{
printf("%s exit\n", argv[0]);
return -1;
}
}
send_msg函数中,如果消息队列缓冲区未满,则每调用一次,就向消息队列中写入依一次数据,并移动尾指针,同时将计数加1。之后通过pthread_cond_signal函数唤醒阻塞等待中的接收线程。
cpp
int send_msg(long type, const char *data)
{
if (!data)
return -1;
pthread_mutex_lock(&g_queue.mutex);
if (g_queue.count >= MAX_MSG_NUM)
{
pthread_mutex_unlock(&g_queue.mutex);
return -1;
}
g_queue.msgs[g_queue.tail].type = type;
strcpy(g_queue.msgs[g_queue.tail].data, data);
g_queue.tail = (g_queue.tail + 1) % MAX_MSG_NUM;
g_queue.count++;
pthread_cond_signal(&g_queue.cond); //wake up recv thread
pthread_mutex_unlock(&g_queue.mutex);
return 0;
}
程序编译及运行结果如下:
bash
$ make clean
rm -rf *.o thread_msgqueue
$
$ make
gcc -o thread_msgqueue.o -c thread_msgqueue.c
gcc -o thread_msgqueue thread_msgqueue.o -L. -lpthread
chmod 755 thread_msgqueue
rm -rf *.o
thread_msgqueue.c
bash
$ ./thread_msgqueue
a
received message: type=17895697, data=abcdefgh
a
received message: type=17895697, data=abcdefgh
e
./thread_msgqueue exit
完整工程代码已上传至:实现线程间消息队列的示例工程代码资源-CSDN下载。