Ceph是一款优秀的分布式存储软件,通过学习它的源码,我们可以学到很多编程技巧,ceph中关于多线程的编程,是一个很好的学习对象。
Ceph中线程的在src/common/Thread.h中定义
class Thread {
private:
pthread_t thread_id;
public:
Thread(const Thread& other);
const Thread& operator=(const Thread& other);
Thread();
virtual ~Thread();
protected:
virtual void *entry() = 0;//子类需要实现此函数,作为线程入口
private:
static void *_entry_func(void *arg);
public:
const pthread_t &get_thread_id();
bool is_started();
bool am_self();
int kill(int signal);//杀掉自己
int try_create(size_t stacksize);
void create(size_t stacksize = 0);//创建线程,里面传入了entry函数
int join(void **prval = 0);//阻塞自己等待线程结束,回收资源
int detach();//父子线程分离
};
C开发者都知道线程编程接口,一个线程在创建时调用pthread_create函数来传入entry函数,杀死线程调用pthread_kill函数,当线程被杀死之后,必须调用pthread_join函数来进行线程资源的回收,如果不调用此函数,就会出现类似zombie process。如果要想让系统自己回收线程资源,就要将线程与父线程分离即调用pthread_detach.通过接口对比,我们发现src/common/Thread.h中定义的class thread,实际上是对线程代码ceph自己实现的一层封装。
Ceph中所有要用的线程必须继承Thread类,通过查找发现如下一些线程:
Accepter.h (src\msg):class Accepter : public Thread //用来socket bind的线程, accepter线程入口函数里定义了poll的网络通讯结构,用来放入管道
Admin_socket.h (src\common):class AdminSocket : public Thread
Ceph_context.cc (src\common):class CephContextServiceThread : public Thread
DispatchQueue.h (src\msg): class DispatchThread : public Thread //用来进行消息分发的线程, 在simpleMessenger中有dispatch_queue成员变量,
FileJournal.h (src\os): class Writer : public Thread //用来进行写数据到journal中的线程
FileJournal.h (src\os): class WriteFinisher : public Thread //当用aio异步模式写数据到journal完成后,此线程用来接管其他剩余操作
FileStore.h (src\os): struct SyncThread : public Thread //用来同步数据执行同步的线程,主要是将已经完成的journal的序列号写入到文件中
Finisher.h (src\common): struct FinisherThread : public Thread //公用的finisher线程,用来查看某些特定的操作是否结束,结束后进行后续处理工作
MDLog.h (src\mds): class ReplayThread : public Thread
OSD.h (src\osd): struct T_Heartbeat : public Thread //维系osd进程之间互相心跳连接的线程
OutputDataSocket.h (src\common):class OutputDataSocket : public Thread
Pipe.h (src\msg): class Reader : public Thread //用来处理所有对socket的读操作,由acepter线程将socket accept以后打入到SimpleMessenger::dispatch_queue中交由此线程处理
Pipe.h (src\msg): class Writer : public Thread //用来处理所有对socket的写操作,由acepter线程将socket accept以后打入到SimpleMessenger::dispatch_queue中交由此线程处理
Pipe.h (src\msg): class DelayedDelivery: public Thread //用来处理所有对socket的延时操作
Signal_handler.cc (src\global):struct SignalHandler : public Thread
SimpleMessenger.h (src\msg): class ReaperThread : public Thread //用来进行消息通信的主要线程 reaper是用来在通讯完成时拆除管道,其中成员有accepter线程(用来bind,accept socket文件放入管道),还有dispatch_queue线程
Throttle.cc (src\test\common): class Thread_get : public Thread
Timer.cc (src\common):class SafeTimerThread : public Thread
WorkQueue.h (src\common): struct WorkThread : public Thread
可以将这些线程分为四类线程
第一类是普通类线程:
使用此类线程类直接申明继承自Thread,重写一个entry函数,在进程启动最初时,调用了create函数创建了线程,同时使用它的人必须自己定义消息队列。上面大部分线程都是此类,比如FileJournal::write_thread就是一个FileJournal::Writer类对象,它自己定义了消息队列FileJournal::writeq
第二类是SafeTimerThread类线程:
此类线程使用者可以直接申明一个SafeTimer成员变量,因为SafeTimer中已经封装了SafeTimerThread类和一个消息队列(成员是Context回调类),并完成了entry函数的逻辑流程。使用者使用方法,就是设置回调函数,通过SafeTimer::add_event_after函数将钩子埋入,等待规定时间到达后执行。
第三类是FinisherThread类线程:
此类线程使用者可以直接申明一个Finisher成员变量,因为Finsher中已经封装了FinisherThread类和一个消息队列(成员是Context回调类),并完成entry函数的逻辑流程。使用者使用方法,就是设置回调函数,通过Finisher::queue函数将钩子埋入,等待某类操作完成后执行。
第四类是ThreadPool内部线程:
这类线程由于是具体工作类线程,所以他们一般都是以线程池形式一下创建多个。ThreadPool类内部有多个线程set
先说ThreadPool中的线程是WorkThread类型,它的入口函数是ThreadPool::worker函数,此函数内定义了WorkThread类线程的操作逻辑。基本流程就是轮询所有WorkQueue_,当发现某种类型WorkQueue_中有数据时拿出,然后依次调用该WorkQueue_自己定义的函数_void_process和_void_process_finish等函数来顺序执行操作。
void ThreadPool::worker(WorkThread *wt)
{
……
while (!_stop) {
……
if (!_pause && !work_queues.empty()) {
WorkQueue_* wq;
int tries = work_queues.size();
bool did = false;
while(tries--) {
last_work_queue++;
last_work_queue %= work_queues.size();
wq = work_queues[last_work_queue];
void *item = wq->_void_dequeue();
if (item) {
……
wq->_void_process(item,tp_handle);
……
wq->_void_process_finish(item);
……
}
……
}
……
}
……
}
再说一说ThreadPool中的WorkQueue_,这是一种抽象的类,只定义了一个队列应该有的一些特定的函数,这些函数几乎都是虚函数,目的是为了调用到自己三个子类BatchWorkQueue,WorkQueueVal,WorkQueue自己定义的函数。而在三个子类中对应函数_void_process,_void_process_finish中又分别调用了使用者自己继承它们而自己实现的具体操作函数如_process,_process_finish。
struct WorkQueue_ {
string name;
time_t timeout_interval, suicide_interval;
WorkQueue_(string n, time_t ti, time_t sti)
: name(n), timeout_interval(ti), suicide_interval(sti)
{ }
virtual ~WorkQueue_() {}
virtual void _clear() = 0;
virtual bool _empty() = 0;
virtual void *_void_dequeue() = 0;
virtual void _void_process(void *item, TPHandle &handle) = 0;
virtual void _void_process_finish(void *) = 0;
};
最后说一下使用者如何使用:
1.声明线程池成员ThreadPool *_tp
2.声明队列类型ThreadPool::WorkQueue_*_wq
3.重写WorkQueue中对应函数_void_process,_void_process_finish
4.调用*_tp.add_work_queue(*_wq)将队列传入
下图是WorkQueue_拥有三大继承:
从上图中我们可以看到ceph中目前本人知道的线程池有五种,OSD中四个,FileStore中一个
op_tp 处理client来的请求
disk_tp 处理scrub操作
recovery_tp处理recovery_tp操作
command_tp 处理命令行来的操作
FileStore::op_tp 处理底层数据操作
可以将上一篇介绍写流程底层操作那篇文章中的线程进行分类,可以发现拥有:
一个普通线程:FileJournal::write_thread
两个finisher线程:Journal::Finisher.finisher_thread,
FileStore::ondisk_finisher.finisher_thread,
FileStore::op_finisher.finisher_thread
两个线程池线程: OSD::op_tp,
FileStore::op_tp