memcahed使用libevent库来处理网络socket的读写事件。
(1)处理tcp:
memcached有一个main thread,n个worker thread,mainthread和 work thread都有自己的event loop。
每个workthread都有一个给pipe fd,并使用libevent为该fd注册了一个回调函数,用于主线程收到新连接后唤醒workthread的eventloop
main threa最开始调用socket bind listen函数创建socket fd,然后调用conn_new函数,该函数主要fd创建一系列维护该fd信息的数据,比如当前状态,分配的读写缓冲区,当前缓冲区可用来读写数据的位置等等,并为该fd注册一个读事件的回调函数event_handler,该mainthread 收到新连接时,该回调函数会执行,具体为accept,并把新连接的fd值写到一个队列里。然后找到其中一个worker thread,并向该thread的 pipe写端写入一个字符c
该work thread的pipe fd回调函数开始执行,执行过程为:读取队列获取连接fd,然后给该fd调用con_new,该函数前面提到了,它会为新的fd创建用于维护连接信息的数据,然后给该fd注册一个事件回调函数event_handler。注意socket fd的回调函数都是event_handler,该函数会读取当前fd的状态,不同的状态会执行不同的过程。
这样main thread主要处理listen fd的读事件,而work thread处理main thread收到连接后分配给work thread的连接fd的读事件。
(2)处理udp:
udp没有连接的概念,如何分别处理客户端发来的所有数据呢?
同样memcached有一个main thread,n个work thread,mainthread和 work thread都有自己的event loop。
每个workthread都有一个给pipe fd,并使用libevent为该fd注册了一个回调函数,用于主线程收到新连接后唤醒workthread的eventloop
main threa最开始调用socket bind创建socket fd,但是对于udp,使用dup(fd)来给这个socket fd复制了n-1个fd(n是线程的个数),然后把这n个fd分别写到n个work thread的队列中,并通过pipe fd的写调用通知work thread这n个fd的值
work thread的pipe fd读回调函数开始执行,执行过程为:读取队列获取连接fd,然后给该fd调用con_new。与tcp不同的是,调用con_new时,给该fd指明的状态是conn_read状态,前面提到了虽然所有fd的回调函数都是event_handler,但是状态不同,执行结果不同。
但n个work thread虽然拥有的socket fd值不同,但因为都是一个fd dup来的,实际上他们都是指向同一个socket,那多线程执行同一个socket会不会有问题呢?通过查资料发现,对于udp,多线程读写同一个socket fd是线程安全的,不会有问题。之前以为memcached这种实现方法以为会引起惊群,即当有数据到来时,每个线程都被唤醒了,但是经过测试发现当发送一条数据时发现只有一个线程唤醒并读到数据。
(3)测试使用多线程同时读取一个socket是否引起惊群:
代码参考自<基于libevent多线程监听同一个文件句柄惊群现象验证>:
http://blog.chinaunix.net/uid-26575352-id-3075103.html
服务器代码为:
#include <iostream>
#include <pthread.h>
#include <event.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int init_count = 0;
pthread_mutex_t init_lock;
pthread_cond_t init_cond;
using namespace std;
typedef struct {
pthread_t thread_id; /* unique ID of this thread */
struct event_base *base; /* libevent handle this thread uses */
struct event notify_event; /* listen event for notify pipe */
} mythread;
void *worker_libevent(void *arg)
{
mythread *p = (mythread *)arg;
pthread_mutex_lock(&init_lock);
init_count++;
pthread_cond_signal(&init_cond);
pthread_mutex_unlock(&init_lock);
event_base_loop(p->base, 0);
}
int create_worker(void*(*func)(void *), void *arg)
{
mythread *p = (mythread *)arg;
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid, &attr, func, arg);
p->thread_id = tid;
return 0;
}
void process(int fd, short which, void *arg)
{
mythread *p = (mythread *)arg;
cout << "I am in the thread: " << p->thread_id << endl;
char buffer[100];
memset(buffer, 0, 100);
int ilen = read(fd, buffer, 100);
cout << "read num is :" << ilen << endl;
cout << "the buffer: " << buffer;
}
int setup_thread(mythread *p, int fd)
{
p->base = event_init();
event_set(&p->notify_event, fd, EV_READ|EV_PERSIST, process, p);
event_base_set(p->base, &p->notify_event);
event_add(&p->notify_event, 0);
return 0;
}
int main()
{
struct sockaddr_in in;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
struct in_addr s;
bzero(&in, sizeof(in));
in.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.117", (void *)&s);
in.sin_addr.s_addr = s.s_addr;
in.sin_port = htons(19870);
bind(fd, (struct sockaddr*)&in, sizeof(in));
int threadnum = 10;
int i;
pthread_mutex_init(&init_lock, NULL);
pthread_cond_init(&init_cond, NULL);
mythread *g_thread;
g_thread = (mythread *)malloc(sizeof(mythread)*10);
for(i=0; i<threadnum; i++)
{
setup_thread(&g_thread[i], fd);
}
for(i=0; i<threadnum; i++)
{
create_worker(worker_libevent, &g_thread[i]);
}
pthread_mutex_lock(&init_lock);
while(init_count < threadnum)
{
pthread_cond_wait(&init_cond, &init_lock);
}
pthread_mutex_unlock(&init_lock);
cout << "IN THE MAIN LOOP" << endl;
string test = "I am michael";
write(fd, test.c_str(), test.size());
while(1)
{
sleep(1);
}
free(g_thread);
return 0;
}
客户端代码为:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
using namespace std;
int main()
{
struct sockaddr_in in;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
struct in_addr s;
bzero(&in, sizeof(in));
in.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.117", (void *)&s);
in.sin_addr.s_addr = s.s_addr;
in.sin_port = htons(19870);
string str = "I am Michael";
sendto(fd, str.c_str(), str.size(), 0, (struct sockaddr *)&in, sizeof(struct sockaddr_in));
return 0;
}
客户端或者用命令 nc -u 192.168.1.117 19870
也可。
虽然该链接说使用多线程同时读取一个socket会引起惊群现象,但我运行的结果却是发现使用多线程同时读取一个socket只有一个线程会唤醒并读数据,没有惊群现象。
我服务端运行结果为:
IN THE MAIN LOOP
I am in the thread: 140691719919360
read num is :12
即只要一个线程唤醒了。