什么是惊群现象?
惊群(thundering herd)是指,只有一个子进程能获得连接,但所有N个子进程却都被唤醒了,这种情况将使性能受损。
举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。对于操作系统来说,多个进程/线程在等待同一资源时,也会产生类似的效果,其结 果就是每当资源可用,所有的进程/线程都来竞争资源,造成的后果:
1)系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。
2)为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。
最常见的例子就是对于socket描述符的accept操作,当多个用户进程/线程监听在同一个端口上时,由于实际只可能accept一次,因此就会产生惊群现象.这个问题是一个古老的问题,新的操作系统内核已经解决了这一问题。
在多线程情况下,每个线程都监听同一个fd,当有数据来的时候,是否会有惊群现象呢?验证如下
服务器端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
//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; } |
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
//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; } |
测试效果图
发表评论
要发表评论,您必须先登录。