How to correctly read data when using epoll_wait
来源:互联网 发布:mac u盘 200m 编辑:程序博客网 时间:2024/05/17 07:24
I am trying to port to Linux an existing Windows C++ code that uses IOCP. Having decided to use epoll_wait
to achieve high concurrency, I am already faced with a theoretical issue of when we try to process received data.
Imagine two threads calling epoll_wait
, and two consequetives messages being received such that Linux unblocks the first thread and soon the second.
Example :
Thread 1 blocks on epoll_waitThread 2 blocks on epoll_waitClient sends a chunk of data 1Thread 1 deblocks from epoll_wait, performs recv and tries to process dataClient sends a chunk of data 2Thread 2 deblocks, performs recv and tries to process data.
Is this scenario conceivable ? I.e. can it occure ?
Is there a way to prevent it so to avoid implementing synchronization in the recv/processing code ?
5 Answers
If you have multiple threads reading from the same set of epoll handles, I would recommend you put your epoll handles in one-shot level-triggered mode with EPOLLONESHOT
. This will ensure that, after one thread observes the triggered handle, no other thread will observe it until you use epoll_ctl
to re-arm the handle.
If you need to handle read and write paths independently, you may want to completely split up the read and write thread pools; have one epoll handle for read events, and one for write events, and assign threads to one or the other exclusively. Further, have a separate lock for read and for write paths. You must be careful about interactions between the read and write threads as far as modifying any per-socket state, of course.
If you do go with that split approach, you need to put some thought into how to handle socket closures. Most likely you will want an additional shared-data lock, and 'acknowledge closure' flags, set under the shared data lock, for both read and write paths. Read and write threads can then race to acknowledge, and the last one to acknowledge gets to clean up the shared data structures. That is, something like this:
void OnSocketClosed(shareddatastructure *pShared, int writer){ epoll_ctl(myepollhandle, EPOLL_CTL_DEL, pShared->fd, NULL); LOCK(pShared->common_lock); if (writer) pShared->close_ack_w = true; else pShared->close_ack_r = true; bool acked = pShared->close_ack_w && pShared->close_ack_r; UNLOCK(pShared->common_lock); if (acked) free(pShared);}
I'm assuming here that the situation you're trying to process is something like this:
You have multiple (maybe very many) sockets that you want to receive data from at once;
You want to start processing data from the first connection on Thread A when it is first received and then be sure that data from this connection is not processed on any other thread until you have finished with it in Thread A.
While you are doing that, if some data is now received on a different connection you want Thread B to pick that data and process it while still being sure that no one else can process this connection until Thread B is done with it etc.
Under these circumstances it turns out that using epoll_wait() with the same epoll fd in multiple threads is a reasonably efficient approach (I'm not claiming that it is necessarily the most efficient).
The trick here is to add the individual connections fds to the epoll fd with the EPOLLONESHOT flag. This ensures that once an fd has been returned from an epoll_wait() it is unmonitored until you specifically tell epoll to monitor it again. This ensures that the thread processing this connection suffers no interference as no other thread can be processing the same connection until this thread marks the connection to be monitored again.
You can set up the fd to monitor EPOLLIN or EPOLLOUT again using epoll_ctl() and EPOLL_CTL_MOD.
A significant benefit of using epoll like this in multiple threads is that when one thread is finished with a connection and adds it back to the epoll monitored set, any other threads still in epoll_wait() are immediately monitoring it even before the previous processing thread returns to epoll_wait(). Incidentally that could also be a disadvantage because of lack of cache data locality if a different thread now picks up that connection immediately (thus needing to fetch the data structures for this connection and flush the previous thread's cache). What works best will sensitively depend on your exact usage pattern.
If you are trying to process messages received subsequently on the same connection in different threads then this scheme to use epoll is not going to be appropriate for you, and an approach using a listening thread feeding an efficient queue feeding worker threads might be better.
Previous answers that point out that calling epoll_wait() from multiple threads is a bad idea are almost certainly right, but I was intrigued enough by the question to try and work out what does happen when it is called from multiple threads on the same handle, waiting for the same socket. I wrote the following test code:
#include <netinet/in.h>#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/epoll.h>#include <sys/socket.h>#include <sys/types.h>#include <unistd.h>struct thread_info { int number; int socket; int epoll;};void * thread(struct thread_info * arg){ struct epoll_event events[10]; int s; char buf[512]; sleep(5 * arg->number); printf("Thread %d start\n", arg->number); do { s = epoll_wait(arg->epoll, events, 10, -1); if (s < 0) { perror("wait"); exit(1); } else if (s == 0) { printf("Thread %d No data\n", arg->number); exit(1); } if (recv(arg->socket, buf, 512, 0) <= 0) { perror("recv"); exit(1); } printf("Thread %d got data\n", arg->number); } while (s == 1); printf("Thread %d end\n", arg->number); return 0;}int main(){ pthread_attr_t attr; pthread_t threads[2]; struct thread_info thread_data[2]; int s; int listener, client, epollfd; struct sockaddr_in listen_address; struct sockaddr_storage client_address; socklen_t client_address_len; struct epoll_event ev; listener = socket(AF_INET, SOCK_STREAM, 0); if (listener < 0) { perror("socket"); exit(1); } memset(&listen_address, 0, sizeof(struct sockaddr_in)); listen_address.sin_family = AF_INET; listen_address.sin_addr.s_addr = INADDR_ANY; listen_address.sin_port = htons(6799); s = bind(listener, (struct sockaddr*)&listen_address, sizeof(listen_address)); if (s != 0) { perror("bind"); exit(1); } s = listen(listener, 1); if (s != 0) { perror("listen"); exit(1); } client_address_len = sizeof(client_address); client = accept(listener, (struct sockaddr*)&client_address, &client_address_len); epollfd = epoll_create(10); if (epollfd == -1) { perror("epoll_create"); exit(1); } ev.events = EPOLLIN; ev.data.fd = client; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(1); } thread_data[0].number = 0; thread_data[1].number = 1; thread_data[0].socket = client; thread_data[1].socket = client; thread_data[0].epoll = epollfd; thread_data[1].epoll = epollfd; s = pthread_attr_init(&attr); if (s != 0) { perror("pthread_attr_init"); exit(1); } s = pthread_create(&threads[0], &attr, (void*(*)(void*))&thread, &thread_data[0]); if (s != 0) { perror("pthread_create"); exit(1); } s = pthread_create(&threads[1], &attr, (void*(*)(void*))&thread, &thread_data[1]); if (s != 0) { perror("pthread_create"); exit(1); } pthread_join(threads[0], 0); pthread_join(threads[1], 0); return 0;}
When data arrives, and both threads are waiting on epoll_wait(), only one will return, but as subsequent data arrives, the thread that wakes up to handle the data is effectively random between the two threads. I wasn't able to to find a way to affect which thread was woken.
It seems likely that a single thread calling epoll_wait makes most sense, with events passed to worker threads to pump the IO.
I believe that the high performance software that uses epoll and a thread per core creates multiple epoll handles that each handle a subset of all the connections. In this way the work is divided but the problem you describe is avoided.
Generally, epoll
is used when you have a single thread listening for data on a single asynchronous source. To avoid busy-waiting (manually polling), you use epoll
to let you know when data is ready (much like select
does).
It is not standard practice to have multiple threads reading from a single data source, and I, at least, would consider it bad practice.
If you want to use multiple threads, but you only have one input source, then designate one of the threads to listen and queue the data so the other threads can read individual pieces from the queue.
I apologize if I should not post this on LKML, but there seems to be
some lack of documentation for using epoll/AIO with threads. Are
these interfaces thread-safe? Can I use them safely in the following
way:
They are thread safe.
Thread A: while(1) { io_getevents(); ... }
// wait forever until an event occurs, then handles the event and loop
Thread B: while(1) { epoll_wait(); ... }
// same as thread A
Thread C: ... io_submit(); ...
Thread D: ... epoll_ctl(); ....
Suppose thread B calls epoll_wait and blocks before thread D calls
epoll_ctl. Is it safe to do so? Will thread B be notified for the
event submitted by thread D? Thread A and C pose the same question
for AIO.
Using the interfaces this way is pretty much their entire point. They'd be
almost useless if you couldn't use them in this way.
- How to correctly read data when using epoll_wait
- How To Read and Write BLOB Data by Using ADO.NET with Visual C# .NET
- How To Read and Write BLOB Data by Using ADO.NET with Visual C# .NET
- How to read ini file using Java
- ABAP Using a Cursor to Read Data
- How to display Chinese characters exactly when using Tomcat.
- how to show source code when using gdb
- How to Avoid ConcurrentModificationException when using an Iterator
- How to prevent crash when selecting specific contact using AdressBookUI
- How to represent ROC curve when using Cross-Validation
- How to output the message when using the function 'printf'
- How to Keep Thread-Safe When Queuing Your Data
- How to use Deep Learning when you have Limited Data
- Using R to read and plot the csv data
- how inputstream read data?
- How to read pixel data of BMP file
- How to parse / read JSON data into a Android ListView
- How-to read data from selected tree node
- iOS-NSComparisonResult和NSComparator介绍,比较,排序
- Hdu 1061 Rightmost Digit
- Android 6.0 运行时权限处理
- 陷阱题
- 向android studio中导入带有jni的eclipse项目
- How to correctly read data when using epoll_wait
- POJ2431优先队列
- Stereo Vision 常用网站
- 【python】'''用法
- Python 高级特性 : 生成器
- android.hardware.camera2使用指南
- string.valueof和string 的区别 遇到错误才记忆深刻
- 学习Redis多数据库
- 写xml布局的时候 用adt看不了视图的时候,提示adt更新什么的。注意要看targetSdkVersion的值跟当前adt的版本是否相同