上篇说了多线程处理的概述,这篇说说具体实现。
muduo的多线程是由线程池中启动的。线程池类EventLoopThreadPool在TcpServer类中创建一个心得实例。发现在muduo中,各种类的关系基本上引用和包含即组合关系,很少有派生关系的,没有继承关系就没有虚函数的应用了。
可能陈硕觉得继承关系比较复杂,耦合度太高,破坏整体设计。但是我觉得muduo中那么多不同种类的智能指针,还有基于boost或std的函数绑定,本身就够复杂的了。所以我打算有时间用c语言来改写一下muduo,把那些智能指针,函数绑定等弱化,只关注网络框架本身。不过说实话,muduo本身就是基于C++的,用智能指针和函数绑定很正常,而且人家还用得非常到位。所以要想往C++方面发展,还是得精通上面的知识。我本身是搞C++,而且搞了很久,而且决定一直搞下去。不过在接触c项目,脚本语言,还有现代网络并发语言golang之后,还是觉得用C++写项目开发效率太低了,而且很难驾驭,指针就是一个雷区。精通C++的时间与其收获性价比是很低的,所以我决定以后不再画太多的时间在C++上,对于C++项目,我只是关注其框架本身。
好了废话少说。EventLoopThreadPool的start在TcpServer的start中调用,他会创建n个线程并启动,n是EventLoopThreadPool的成员变量,可以配置,也可以是0个。每个线程是EventLoopThread的实例。而EventLoopThread有个组合对象Thread,他才是线程创建和启动真正的地方。代码如下:
void TcpServer::start()
{
if (started_.getAndSet(1) == 0)
{
threadPool_->start(threadInitCallback_);
assert(!acceptor_->listenning());
loop_->runInLoop(
std::bind(&Acceptor::listen, get_pointer(acceptor_)));
}
}
void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
assert(!started_);
baseLoop_->assertInLoopThread();
started_ = true;
for (int i = 0; i < numThreads_; ++i)
{
char buf[name_.size() + 32];
snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
EventLoopThread* t = new EventLoopThread(cb, buf);
threads_.push_back(std::unique_ptr(t));
loops_.push_back(t->startLoop());
}
if (numThreads_ == 0 && cb)
{
cb(baseLoop_);
}
}
void Thread::start()
{
assert(!started_);
started_ = true;
// FIXME: move(func_)
detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_);
if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
{
started_ = false;
delete data; // or no delete?
LOG_SYSFATAL << "Failed in pthread_create";
}
else
{
latch_.wait();
assert(tid_ > 0);
}
}
Thread的线程函数已经绑定在了EventLoopThread::threadFunc里,这个是在EventLoopThread构造函数是初始化的。代码如下:
void EventLoopThread::threadFunc()
{
EventLoop loop;
if (callback_)
{
callback_(&loop);
}
{
MutexLockGuard lock(mutex_);
loop_ = &loop;
cond_.notify();
}
loop.loop();
//assert(exiting_);
loop_ = NULL;
}
这里有个问题是,主线程会加入新线程的loop实例,而这个loop实例又是在线程的线程函数里创建的。所以很显然主线程和新线程有个同步的过程,并且多个新线程之间有临界区的问题。这些是用条件变量pthread_cond_t和互斥变量mutext来实现的。
新线程线程函数在栈上申明一个EventLoop对象之后,便通知主线程了。这使得主线程返回,而新线程函数中loop开始运作循环了。那么主线程又是如何跨线程调用新线程的函数的呢?这个下篇再说。