Linux实用功能代码集(4) —— 线程间消息队列(2)

接前一篇文章: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下载

相关推荐
Name_NaN_None4 小时前
Linux 使用 Remmina 连接 Windows 远程桌面 ——「小白教程」
linux·网络·电脑·远程工作
shepherd1114 小时前
别再无脑 cat 了!后端排查 GB 级生产日志的实战命令
linux·后端
剪刀石头布Cheers4 小时前
Ubuntu安装向日葵远程黑屏
linux·运维·ubuntu
Vect__4 小时前
基于CSAPP深刻理解编译链接过程
linux·c++
123过去4 小时前
reaver使用教程
linux·网络·测试工具·智能路由器
blog.pytool.com4 小时前
Ubuntu 22.04 解决 Qt 报错:Unknown module (s) in QT: qml quick
linux·qt·ubuntu
MarkHD4 小时前
RPA工程化实践:三种核心设计模式让复杂流程优雅可控
linux·设计模式·rpa
就是个名称5 小时前
echart绘制天顶图
linux·前端·javascript
x-cmd5 小时前
[260326] x-cmd v0.8.10:跨 Shell 统一配置命令短名;自动装好依赖运行 WhisperLiveKit 实时语音转写
linux·人工智能·ai·whisper·shortcut·x-cmd