/*
* Returns a fresh connection queue item.
*/
static CQ_ITEM *cqi_new(void) {
CQ_ITEM *item = NULL;
pthread_mutex_lock(&cqi_freelist_lock);
if (cqi_freelist) {
item = cqi_freelist;
cqi_freelist = item->next;
}
pthread_mutex_unlock(&cqi_freelist_lock);
if (NULL == item) {
int i;
/* Allocate a bunch of items at once to reduce fragmentation */
item = malloc(sizeof(CQ_ITEM) * ITEMS_PER_ALLOC);
if (NULL == item)
return NULL;
/*
* Link together all the new items except the first one
* (which we'll return to the caller) for placement on
* the freelist.
*/
for (i = 2; i < ITEMS_PER_ALLOC; i++)
item[i - 1].next = &item[i];
pthread_mutex_lock(&cqi_freelist_lock);
item[ITEMS_PER_ALLOC - 1].next = cqi_freelist;
cqi_freelist = &item[1];
pthread_mutex_unlock(&cqi_freelist_lock);
}
return item;
}
最近一直想给数据库代理模块做些优化,优化的点是将mysql阻塞的c api做成让epoll这种多路复用模型来进行非阻塞管理,提高系统的并发处理能力。google了下没找到有用的信息,本来打算agentzh大师里给nginx做的drizzle是怎么实现非阻塞的,下了代码也没来得及看,主要涉及nginx模块开发,不太了解 -_-。
最后还是用了多线程的方式来提高并发能力,充分利用mysql api阻塞时候cpu的能力。模型选的是memcached的多线程模型。由于多线程编程较少接触,在看memcached的线程通信部分代码时候被它惊艳到了(我太土了 T-T),memcached的多线程都在同一个进程空间(linux下的线程只是进程里的一个执行流,共享了进程大部分的数据)里,因此直接使用malloc()分配的堆空间构造的队列进行数据传递,通过pthread_mutex_t锁来解决互斥。而通知部分,主线程使用pipe()生成的管道来与每个工作线程来进行通信,这样主线程和工作线程都可以利用epoll这类事件驱动模型进行管理(进程打开的fd资源线程也是共享的)。
大概的工作流程:
1)主线程accept到新连接后,通过RR来选择工作线程,每个工作线程都会有个数据队列用于数据共享;
2)主线程将新连接push到被选择的工作线程的数据队列;
3)往工作线程的管道发送一个字节,以“唤醒”工作线程工作;
4)工作线程的eventloop返回与主线程的通信管道的读事件;
5)工作线程从数据队列队取出新连接,然后设置读事件以及相关回调函数,返回eventloop中;
另外,在看memcached源代码过程中,发现一个很trickky的地方,在给数据队列分配新数据时,如果空闲链表为空,则会一次性分配ITEMS_PER_ALLOC(默认64)个,返回第一个,然后将其他放到空闲链表中,以备下次使用。我也模仿它这么实现,防止内存碎片嘛,多帅。然后我在写测试代码时,发现我的程序在退出清理内存时,会出现double free的coredump,gdb查了十来分钟,一直没发现问题,指针明明是有内容的,但为什么释放就会core呢?各种折腾,都没发现问题。后来重新review这部分代码时才发现原来这部分空间是malloc分配的连续空间,但是在链表里用next指针连起来了,我的清理函数想当然的遍历整个链表进行free,结果杯具就发生了。可以看到,memcached为数据队列分配的这部分空间是不会有显式释放的,因为作为一个server,本来就不用考虑退出时候清理这些内存,由进程自己释放就好了。
memcached里有很多这种省内存或者是避免内存碎片化的细节,除了它本来为key-value做的内存管理之外,连接本身也会进行复用,close的或者是超时的连接都会被放到空闲连接链表中。