IO | bRPC
一般有三种操作IO的方式:
- blocking IO: 发起IO操作后阻塞当前线程直到IO结束,标准的同步IO,如默认行为的posix read和write。
- non-blocking IO: 发起IO操作后不阻塞,用户可阻塞等待多个IO操作同时结束。non-blocking也是一种同步IO:“批量的同步”。如linux下的poll,select, epoll,BSD下的kqueue。
- asynchronous IO: 发起IO操作后不阻塞,用户得递一个回调待IO结束后被调用。如windows下的OVERLAPPED + IOCP。linux的native AIO只对文件有效。
linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,non-blocking IO不一定比blocking IO更高效,因为后者完全由内核负责,而read/write这类系统调用已高度优化,效率显然高于一般得多个线程协作的non-blocking IO。但当IO并发度愈发提高时,blocking IO阻塞一个线程的弊端便显露出来:内核得不停地在线程间切换才能完成有效的工作,一个cpu core上可能只做了一点点事情,就马上又换成了另一个线程,cpu cache没得到充分利用,另外大量的线程会使得依赖thread-local加速的代码性能明显下降,如tcmalloc,一旦malloc变慢,程序整体性能往往也会随之下降。而non-blocking IO一般由少量event dispatching线程和一些运行用户逻辑的worker线程组成,这些线程往往会被复用(换句话说调度工作转移到了用户态),event dispatching和worker可以同时在不同的核运行(流水线化),内核不用频繁的切换就能完成有效的工作。线程总量也不用很多,所以对thread-local的使用也比较充分。这时候non-blocking IO就往往比blocking IO快了。不过non-blocking IO也有自己的问题,它需要调用更多系统调用,比如epoll_ctl,由于epoll实现为一棵红黑树,epoll_ctl并不是一个很快的操作,特别在多核环境下,依赖epoll_ctl的实现往往会面临棘手的扩展性问题。non-blocking需要更大的缓冲,否则就会触发更多的事件而影响效率。non-blocking还得解决不少多线程问题,代码比blocking复杂很多。
阻塞:当我们调用read、send、write等等系统调用API时,会把内核的缓存区里的数据拷贝到用户态的缓存区里,如果这个时候内核缓存区里没有数据,则会等待内核把数据准备好,此时,应用进程处于一种挂起状态。
非阻塞:当我们的应用程序调用write、send、read(所有的IO接口都是的),如果仅仅是从内核缓存区里把数据copy走,或者没有数据copy,此时也会直接返回,明显我们的用户进程并没有任何的等待,或者说处于挂起的状态,这种就是非阻塞。
默认创建的socket是阻塞的,但是可以调用下面这个函数设置为非阻塞:
非阻塞意味着在调用IO接口时,都不会发送等待,会立即返回。
同步
int function(xxxxx)
{
char buf[1024] = {0};
int size = read(fd, buf, bsize);
}
int read(xxxxx)
{
xxxxxjj
kk
。。。
memcpy(buf, kener_buffer, size);
}
内核把数据准备好了之后,它也是通过read返回的,read一返回,其实也就意味着数据是否准备好。
int callback(char* buffer, size)
{
printf(buffer);
}
int gun(oooo)
{
char buffer[1024] = {0};
s_read(fd, callback, buffer, size);
}
内核:
int s_read(fd, callback, buffer, size)
{
update_fd_set(fd, callback) 更新了fd的结构,也就是设置了一个回调函数
send_signal(sig);
}
int process_signal(sig)
{
copy_data_from_disk_2_kener_buffer()
copy_data_from_kener_buffer_2_user_buffer();
callback(user_buffer, size);
}
异步,我调用read时,只是告诉你一个回调函数,并且read已经返回,但是此时并不代表数据准备好了,只有当内核调用了我们的callback才意味着数据已经准备好了。
信号,通知:
延时,IO的操作,同步和异步:
ssize_t read(int fd, void *buf, size_t count);
int aio_read(struct aiocb *aiocbp);
struct aiocb {
/* The order of these fields is implementation-dependent */
int aio_fildes; /* File descriptor */
off_t aio_offset; /* File offset */
volatile void *aio_buf; /* Location of buffer */
size_t aio_nbytes; /* Length of transfer */
int aio_reqprio; /* Request priority */
struct sigevent aio_sigevent; /* Notification method */
int aio_lio_opcode; /* Operation to be performed;
lio_listio() only */
/* Various implementation-internal fields not shown */
};
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with notification */
void (*sigev_notify_function) (union sigval); /* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes; /* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */
};