红联Linux门户
Linux帮助

使用libevent多线程验证Linux上的服务器"惊群"现象

发布时间:2015-09-09 16:08:06来源:linux网站作者:雪峰流云

什么是惊群现象?

惊群(thundering herd)是指,只有一个子进程能获得连接,但所有N个子进程却都被唤醒了,这种情况将使性能受损。
举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。


对于操作系统来说,多个进程/线程在等待同一资源时,也会产生类似的效果,其结 果就是每当资源可用,所有的进程/线程都来竞争资源,造成的后果:
1)系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。
2)为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。


最常见的例子就是对于socket描述符的accept操作,当多个用户进程/线程监听在同一个端口上时,由于实际只可能accept一次,因此就会产生惊群现象.这个问题是一个古老的问题,新的操作系统内核已经解决了这一问题。

在多线程情况下,每个线程都监听同一个fd,当有数据来的时候,是否会有惊群现象呢?验证如下


服务器端代码:

//g++ -g libevent_server.cpp -o libevent_server -levent -lpthread 
//说明:服务器监听在本地19870端口, 等待udp client连接,有惊群现象: 当有数据到来时, 每个线程都被唤醒, 但是只有一个线程可以读到数据 
// 
 
#include <iostream> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <pthread.h> 
#include <event.h> 
#include <netinet/in.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/time.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
 
using namespace std; 
 
int init_count = 0; 
pthread_mutex_t init_lock; 
pthread_cond_t init_cond; 
 
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; 
pthread_attr_destroy(&attr); 
return 0; 

 
void process(int fd, short which, void *arg) 

mythread *p = (mythread *)arg; 
printf("I am in the thread: [%lu]\n", p->thread_id); 
 
char buffer[100]; 
memset(buffer, 0, 100); 
 
int ilen = read(fd, buffer, 100); 
printf("read num is: %d\n", ilen); 
printf("the buffer: %s\n", buffer); 

 
//设置libevent事件回调 
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); 
 
//在127.0.0.1:19870处监听 
struct in_addr s; 
bzero(&in, sizeof(in)); 
in.sin_family = AF_INET; 
inet_pton(AF_INET, "127.0.0.1", (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; //创建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++) 
{ //10个线程都监听同一个socket描述符, 检查是否产生惊群现象? 
setup_thread(&g_thread[i], fd); 

 
for(i=0; i<threadnum; i++) 

create_worker(worker_libevent, &g_thread[i]); 

 
//master线程等待worker线程池初始化完全 
pthread_mutex_lock(&init_lock); 
while(init_count < threadnum) 

pthread_cond_wait(&init_cond, &init_lock); 

pthread_mutex_unlock(&init_lock); 
 
 
printf("IN THE MAIN LOOP\n"); 
 
while(1) 

sleep(1); 

 
//没有回收线程的代码 
free(g_thread); 
return 0; 


客户端代码:

//g++ -g libevent_client.cpp -o libevent_client 
// 
 
#include <iostream> 
#include <string.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> 
 
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, "127.0.0.1", (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; 
}


测试效果图:

使用libevent多线程验证Linux上的服务器"惊群"现象

使用libevent多线程验证Linux上的服务器"惊群"现象


Linux下安装配置MemCached(以及libevent):http://www.linuxdiyf.com/linux/6103.html

使用libevent编写Linux服务:http://www.linuxdiyf.com/linux/5937.html