半同步半异步I/O的设计模式(half sync/half async)
1.动机:
众所周知,同步模式编程简单,但是I/O的利用利率低;而异步模式编程复杂,但是I/O利用率高。综合同步异步的有优点,就有了半同步半异步的设计模式。
这个模式中,高层使用同步I/O模型,简化编程。低层使用异步I/O模型,高效执行。
half sync/half async可以很好的使得"变成复杂度"和"执行效率"之间达到一种平衡.
2.应用场景:
半同步半异步模式在下面的场景中使用:2.1 一个系统中的进程有下面的特征:
系统必须响应和处理外部异步发生的事件,如果为每一个外部资源的事件分派一个独立的线程同步处理I/O,效率很低。
如果上层的任务以同步方式处理I/O,实现起来简单。
2.2 一个或多个任务必须在单独的控制线程中执行,其它任务可以在多线程中执行:
上层的任务(如:数据库查询,文件传输)使用同步I/O模型,简化了编写并行程序的难度。而底层的任务(如网络控制器的中断处理)使用异步I/O模型,提供了执行效率。
一般情况下,上层的任务要比下层的任务多,使用一个简单的层次实现异步处理的复杂性,可以对外隐藏异步处理的细节。另外,同步层次和异步层次任务间的通信使用一个队列来协调。
3.实现方案:
可以分为三层:同步任务层,队列层,异步任务层。3.1 同步任务层(用户级的进程):
本层的任务完成上层的I/O操作,使用同步I/O模型,通过队列层的队列中传输数据。和异步层不同,同步层的任务使用活动对象执行,这些活动对象有自己运行栈和寄存器状态。当执行同步I/O的时候,他们会被阻塞/睡眠。3.2 队列层:
这个层在同步任务层和异步任务层之间,提供了同步控制和缓存的功能。异步任务的I/O 事件被缓存到消息队列中,同步任务层在队列中提取这些事件(相反方向亦然)3.3 异步任务层:
处理低层的事件,这些事件由多个外部的事件源产生(例如网卡,终端)。和异步任务不同,此层的实体是被动对象,没有自己的运行栈,要求不能被阻塞。
4.优缺点:
4.1 半同步半异步模式有下面的优点:
上层的任务被简化不同层可以使用不同的同步策略
层间的通信被限制在单独的一点,因为所有的交互使用队列层协调。
在多处理器环境中提高了性能。
4.2 半同步半异步模式有下面的缺点:
跨边界导致的性能消耗,这是因为同步控制,数据拷贝和上下文切换会过度地消耗资源。上层任务缺少异步I/O的实现。
5.一个样例:
#include <pthread.h> // for pthread.
#include <semaphore.h> // for sem.
#include <queue> // for queue.
#include <vector> // for vector.
#include <string.h> // for memset.
// for file operation.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// for print log.
#include <iostream>
using namespace std;
// 队列层
class TaskQueue
{
public:
/*
* 定义item。
*/
typedef struct _taskItem
{
int start_offset;
int size;
void *buffer;
sem_t *sem;
} TaskItem;
public:
TaskQueue()
{
pthread_mutex_init(&m_mutex, NULL);
}
~TaskQueue()
{
pthread_mutex_destroy(&m_mutex);
}
/*
* 添加任务。
*/
void append(TaskItem &item)
{
pthread_mutex_lock(&m_mutex);
m_items.push(item);
pthread_mutex_unlock(&m_mutex);
}
/*
* 抛出一个任务;
*/
bool pop(TaskItem &item)
{
pthread_mutex_lock(&m_mutex);
if(m_items.empty())
{
pthread_mutex_unlock(&m_mutex);
return false;
}
item.start_offset = m_items.front().start_offset;
item.size = m_items.front().size;
item.buffer = m_items.front().buffer;
item.sem = m_items.front().sem;
m_items.pop();
pthread_mutex_unlock(&m_mutex);
return true;
}
/*
* 获取item的size;
*/
int getSize()
{
int size = 0;
pthread_mutex_lock(&m_mutex);
size = m_items.size();
pthread_mutex_unlock(&m_mutex);
return m_items.size();
}
private:
queue<TaskItem> m_items; // 任务队列。
pthread_mutex_t m_mutex; // 任务队列的互斥量。
};
// 异步任务层
class AioProcessor
{
public:
AioProcessor(int fd, TaskQueue *queue)
{
m_fd = fd;
m_queue = queue;
m_isStartup = false;
}
~AioProcessor()
{
shutdwon();
}
void startup(int thread_count)
{
if(!m_isStartup)
{
/*
* 启动指定数目的线程。
*/
m_isStartup = true;
for(int i=0; i<thread_count; ++i)
{
pthread_t tid;
pthread_create(&tid, NULL, process, this);
m_tids.push_back(tid);
}
}
}
void shutdwon()
{
if(m_isStartup)
{
/*
* 结束启动的线程。
*/
m_isStartup = false;
vector<pthread_t>::iterator iter = m_tids.begin();
for(; iter<m_tids.end(); ++iter)
{
pthread_join(*iter, NULL);
}
}
}
private:
static void *process(void *param)
{
AioProcessor *processor = (AioProcessor *)param;
/*
* 处理读文件请求,读取完毕发送完毕信号。
*/
TaskQueue::TaskItem item_to_be_process;
while(processor->m_isStartup)
{
if(processor->m_queue->pop(item_to_be_process))
{
pread(processor->m_fd, item_to_be_process.buffer, item_to_be_process.size, item_to_be_process.start_offset);
sem_post(item_to_be_process.sem);
}
else
{
usleep(1000);
}
}
/*
* 线程结束。
*/
return NULL;
}
private:
int m_fd; // 文件句柄
TaskQueue *m_queue; // 任务队列
vector<pthread_t> m_tids; // 线程Id
bool m_isStartup; // true:已启动;false:未启动。
};
// 应用层
class Reader
{
public:
Reader(int fd)
: m_fd(fd), m_queue(), m_processor(new AioProcessor(m_fd, &m_queue))
{
/*
* 启动processor。
*/
m_processor->startup(2);
}
~Reader()
{
/*
* 停止processor,并释放相关资源。
*/
m_processor->shutdwon();
delete m_processor;
}
void read(const int start_offset, int size, void *buffer)
{
/*
* 构造一个item。
*/
sem_t sem;
sem_init(&sem, 0, 0);
TaskQueue::TaskItem item;
item.start_offset = start_offset;
item.size = size;
item.buffer = buffer;
item.sem = &sem;
/*
* 将上面的item加入到任务队列中。
*/
m_queue.append(item);
/*
* 等待读文件操作完成。
*/
sem_wait(&sem);
sem_destroy(&sem);
/*
* 读文件操作完成。
*/
return;
}
private:
int m_fd; // 文件句柄.
TaskQueue m_queue; // 任务队列.
AioProcessor* m_processor; // 任务处理器.
};
// 测试样例。
int main()
{
int fd = open("./a.file", O_RDONLY);
Reader reader(fd);
int size = 10;
char *buf = new char[size + 1];
memset(buf, '\0', size+1);
for(int i=0; i<10; i++)
{
reader.read(i*size, size, buf);
cout<<buf<<endl;
}
close(fd);
return 0;
}
注:
上述代码比较简单,有很多地方需要改进,比如:
1.以上代码的编译环境是:RedHat6.3(g++ (GCC) 4.5.2)。
1.以上代码的编译环境是:RedHat6.3(g++ (GCC) 4.5.2)。
2.pthread_mutex_init,pthread_create,open,pread等,都需判断返回值。
3.AioProcessor一个线程一次处理一个read request,不是很高效,可能使得queue中的锁成为瓶颈。
4.肯定还有其他的问题,欢迎提出宝贵意见……
3.AioProcessor一个线程一次处理一个read request,不是很高效,可能使得queue中的锁成为瓶颈。
4.肯定还有其他的问题,欢迎提出宝贵意见……