目录
前言
熟悉多线程编程的同学都知道,当我们为了避免资源竞争时,需要加锁和解锁,而频繁的加锁和解锁必定会造成资源的损耗和开销,所以线程邮箱应运而生。
一、线程邮箱是什么?
简单来说,线程邮箱就是模拟收发邮件的形式,使线程任务不在主动争抢共享资源,只是检查自己的“邮箱”看是否有别的任务对自己有资源的传递。
二、线程邮箱的组成及构造
构造:
线程邮箱的基本组成是以链表的形式将每个线程任务串联起来,而每个节点中不仅要有自己线程所负责的任务还有自己的“邮箱”(以队列的形式),所以每个邮箱中待处理的任务节点需要包含发送者的信息以及接收者的信息还有所处理的数据。
看起来复杂是因为它是一个不断包含的关系,所以我们从最内部一层一层剖析其结构。
1.邮箱构成(队列)
我们每个线程检查自己的邮箱确认是否有待完成的任务,而邮箱设置为队列的形式主要为待处理的任务排序(先进先出),而为了方便邮件准确到达我们的邮件不仅包含数据,还需要包含发送者的线程名和tid号以及接收者的线程名和tid号
如图所示:
typedef struct mail_data
{
pthread_t id_of_sender;
char name_of_sender[256];
pthread_t id_of_recver;
char name_of_recver[256];
DATATYPE data;
}MAIL_DATA;
而邮箱就是由一个个邮件串联起来的
如图所示:
typedef struct queue{
MAIL_DATA data;
struct queue* next;
}Que, *pQue;
2.线程任务节点构成
当我们每个线程任务以及拥有自己的邮箱时,再包含起来自己的线程名以及tid和用于串联链表的指针就构成了一个线程任务节点。
typedef struct thread_node
{
pthread_t tid; //线程id号
char name[256]; //线程名字 ,必须唯一
Que *mail_head, *mail_tail;
th_fun th;
}LIST_DATA;
最后我们将每一个链表节点串联起来就得到了整个线程邮箱的框架
如图所示:
typedef struct Link{
LIST_DATA elem;
struct Link *next;
}LIST_LINK;
三、线程邮箱的使用
通过刚才的讲述,想必大家脑海里以及有了整个线程邮箱的框架,本节主要讲述线程是如何发送“邮件”和接收“邮件”的,当然,具体的接收到邮件之后的操作(队列的入队)不过多赘述。
1.发送信息
发送信息时,第一步,当然是先写“邮件”,先将所发送的内容拷贝至申请的空间中,这时获得自己的tid号(发送者)也存入邮件中,第二步,先遍历整个链表节点查询是否存在所想要发送的线程任务(否则会报错)。第三步,找到之后将自己的名字(发送者)以及对方的名字(接收者)都放入邮件中,开始发送邮件(入队操作),当然重点是在入队操作时一定要加锁,防止多个任务同时给一个任务发送邮件造成阻塞。
代码如下:
int send_msg(MBS*msb, char*recvname, DATATYPE data)
{
MAIL_DATA* temp = malloc(sizeof(MAIL_DATA));
strcpy(temp->data, data);
temp->id_of_sender = pthread_self();
LIST_LINK *find = list_for_each(msb->thread_list, recvname);
if (find == NULL)
{
printf("can,t find msg \n");
}
char* name = get_th_name(msb);
strcpy(temp->name_of_sender,name);
strcpy(temp->name_of_recver,recvname);
ENTER_CRITICAL_AREA(&msb->mutex);
in_queue(find, temp);
QUIT_CRITICAL_AREA(&msb->mutex);
return 0;
}
2.接收信息
接收“邮件”时,与发送大同小异。第一步是肯定是申请可以接收这个邮件的空间,第二步需要注意
,接收邮件是需要拿着手里的收件人名(tid号)去遍历链表找到收件人否则退出(否则又会容易报错)。第三步,则进行接收邮件(出队操作),拿到邮件后将数据拿出。
代码如下:
int recv_msg(MBS*msb,char*sendname,DATATYPE data)
{
MAIL_DATA* temp = malloc(sizeof(MAIL_DATA));
pthread_t tid = pthread_self();
LIST_LINK *find = msb->thread_list;
while(find != NULL)
{
if( find->elem.tid == tid)
break;
find = find->next;
}
if( find->elem.tid == tid)
{
while (1)
{
if(find->elem.mail_head != find->elem.mail_tail)
{
ENTER_CRITICAL_AREA(&msb->mutex);
out_queue(find, temp);
QUIT_CRITICAL_AREA(&msb->mutex);
break;
}
}
}
strcpy(sendname, temp->name_of_sender);
strcpy(data, temp->data);
free(temp);
return 0;
}
四、线程邮箱的优势
通过上述我们不难发现,线程邮箱具备以下优势:
(1)降低资源消耗。避免了常规多线程任务获取资源时所进行的频繁加锁和解锁来降低资源损耗。
(2)提高响应速度。任务只需要查看自己的“邮箱”来判断是否有需要处理的任务。
(3)提高线程的可管理性。线程是稀缺资源,通过链表的形式将多个任务串联起来,使用线程邮箱可以进行统一的分配,调优和监控。
总结
本篇文章通过画图和代码展示的呈现了线程邮箱的结构以及具体的使用方法,比较简单的操作比如链表的遍历以及入队出队和线程邮箱的创建与销毁并没有展示,本人认为线程邮箱较为难的一方面就是实现复杂。不仅需要将其逻辑结构想清楚还要在编写代码是考虑一些特殊情况,如果某些读者对线程邮箱感兴趣建议自己动手去敲一次完成代码的实现,这样或许对线程邮箱就有了更深层次的理解,最后感谢阅读。