本文对于内核中加入了System V 进程通信机制的系统来说是多余的,因为System V 进程通信机制中已经实现了消息队列。对于那些未将System V 进程通信选入内核编译的系统(本文中将要描述的就是这类情况,下面所提到的都是这种Linux系统,不再特别说明),要如何实现类似Windows的线程消息队列操作(PostThreadMessage / GetMessage)呢?
众所周知,Windows的线程消息队列是很方便的一种机制,用它来做线程间的通信、同步不失为一种很不错的途径(本人就很喜欢用这种方法)。那么在Linux系统中又将如何来实现类似的线程通信机制呢?本人的做法是使用UNIX域协议(UNIX Domain)的Socket来模拟,虽然功能达不到Windows的线程消息队列那样强大,但对于一般的线程通信操作已经够用了。
为什么要用UNIX Domain Socket 而不是TCP/UDP,是因为前者对于位于同一主机的通信效率是最高的(它没有协议栈的处理,速度大概是后者的两倍)。本文不介绍UNIX Domain Socket 以及Linux线程的使用,下面介绍上述模拟过程的实现:
首先,每个线程都要建立一个消息队列,在Windows中这个工作是系统自动完成的,我们现在需要模拟,那么就要维护一张整个进程中所有线程的消息队列的全局表,可以用链表来实现之:
线程消息队列类型:
typedef struct msg_queue
{
pthread_t tid;
int sock_fd;
char quename[32];
struct msg_queue *next_que;
} msg_queue_t, *pmsg_queue_t;
线程消息结构:
typedef struct msg_head
{
ushort msg;
ushort len;
uchar msg_buf[256];
}msg_head_t, *pmsg_head_t;
全局链表:
static pmsg_queue_t lpmsg_queue = NULL;
全局锁,用来临界全局线程消息队列表的操作,防止冲突:
static pthread_mutex_t msg_que_mutex = PTHREAD_MUTEX_INITIALIZER;
从队列中接收消息的操作是阻塞的,windows中让线程结束消息循环就是发送WM_QUIT消息到目标线程,因此我们也模拟一个这样的消息:
#define QUIT_MESSAGE 0
若要向指定线程发送消息,首先需要寻找到它的消息队列:
static pmsg_queue_t find_msg_queue(pthread_t tid)
{
pthread_mutex_lock(&msg_que_mutex);
pmsg_queue_t ptemp = lpmsg_queue;
while (ptemp)
{
if (ptemp->tid == tid)
break;
ptemp = ptemp->next_que;
}
pthread_mutex_unlock(&msg_que_mutex);
return ptemp;
}
往全局线程消息队列表中加入新的线程消息队列:
static pmsg_queue_t add_new_queue(pthread_t tid)
{
pmsg_queue_t ptemp = NULL, ptail = NULL;
struct sockaddr_un addr;
pthread_mutex_lock(&msg_que_mutex);
if (NULL == lpmsg_queue)
{
lpmsg_queue = (pmsg_queue_t)calloc(1, sizeof(msg_queue_t));
ptemp = lpmsg_queue;
}
else
{
ptemp = lpmsg_queue;
while (ptemp->next_que)
{
ptemp = ptemp->next_que;
}
ptail = ptemp;
ptemp = (pmsg_queue_t)calloc(1, sizeof(msg_queue_t));
ptail->next_que = ptemp;
}
if (ptemp)
{
ptemp->tid = tid;
if ((ptemp->sock_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
{
free(ptemp);
ptemp = NULL;
}
else
{
sprintf(ptemp->quename, "/tmp/que_%d", (int)tid); //用线程ID来做名称,确保唯一性
unlink(ptemp->quename); //防止重名的发生,先做删除操作
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = PF_UNIX;
strcpy(addr.sun_path, ptemp->quename);
if (bind(ptemp->sock_fd, (struct sockaddr *)&addr, sizeof(addr)))
{
close(ptemp->sock_fd);
free(ptemp);
ptemp = NULL;
}
}
}
pthread_mutex_unlock(&msg_que_mutex);
return ptemp;
}
若线程结束,则要从全局线程消息队列表中删除其线程消息队列:
void delete_msg_que()
{
pmsg_queue_t ptemp = NULL, ptail = NULL;
pmsg_queue_t premove = NULL;
premove = find_msg_queue(pthread_self());
if (NULL == premove) return;
pthread_mutex_lock(&msg_que_mutex);
ptemp = lpmsg_queue;
while (ptemp)
{
ptail = ptemp;
if (ptemp == premove)
break;
ptemp = ptemp->next_que;
}
if (ptemp)
{
if (ptemp == lpmsg_queue)
lpmsg_queue = lpmsg_queue->next_que;
else ptail->next_que = ptemp->next_que;
close(ptemp->sock_fd);
unlink(ptemp->quename);
free(ptemp);
}
pthread_mutex_unlock(&msg_que_mutex);
}
为当前线程创建线程消息队列:
pthread_t create_msg_que()
{
pthread_t tid = pthread_self();
if (NULL == find_msg_queue(tid))
if (NULL == add_new_queue(tid))
return 0;
return tid;
}
至此,所有的准备工作都已经就绪,可以实现最有意义的功能了,那就是接收、发送线程消息。
从消息队列中接收线程消息:
uchar get_message(pmsg_head_t msg_hd)
{
int n = 0, addlen = 0;
pmsg_queue_t ptemp = NULL;
struct sockaddr_un addr;
if (NULL != (ptemp = find_msg_queue(pthread_self())))
{
memset(&addr, 0, sizeof(struct sockaddr_un));
addlen = sizeof(addr);
n = recvfrom(ptemp->sock_fd, msg_hd, sizeof(msg_head_t), 0, (struct sockaddr *)&addr, &addlen);
if (n > 0)
return (QUIT_MESSAGE != msg_hd->msg);
}
return 0;
}
发送线程消息到指定的线程消息队列:
int post_message(const pthread_t thread_id,
const ushort msg,
const ushort msg_len,
const uchar *msg_data)
{
struct sockaddr_un addr;
msg_head_t msg_hd;
pmsg_queue_t pthis = NULL, ptemp = find_msg_queue(thread_id);
if (NULL == ptemp) return 0;
pthread_t tid = pthread_self();
if (NULL == (pthis = find_msg_queue(tid)))
pthis = add_new_queue(tid);
if (pthis)
{
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = PF_UNIX;
strcpy(addr.sun_path, ptemp->quename);
msg_hd.msg = msg;
if (msg_len > 0 && NULL != msg_data)
{
msg_hd.len = msg_len;
memcpy(msg_hd.msg_buf, msg_data, msg_len);
}
return sendto(pthis->sock_fd, &msg_hd, 4 + msg_len, 0, (struct sockaddr *)&addr, sizeof(addr));
}
return 0;
}
以上就是本人模拟实现Linux环境中的线程消息队列机制的全过程,请大家多多批评指正,要拍砖的话还望手下留情!