Muduo is the most learning-based network library I have encountered in the learning process, and the infrastructure--thread and ThreadPool in the Muduo library will be analyzed below.
First, the synchronization measures--mutex and condition that are indispensable in multithreaded programming are introduced.
- Mutex
```
/Mutex.h/
Class Mutexlock:boost::noncopyable
{
Public
Mutexlock ()
: Holder_ (0)
{
Mcheck (Pthread_mutex_init (&mutex_, NULL)); What's the role of//mcheck?
}
~mutexlock ()
{
ASSERT (Holder_ = = 0);
Mcheck (Pthread_mutex_destroy (&mutex_));
}
Must be called when locked, i.e. for assertion
BOOL Islockedbythisthread () const//is locked by the current thread
{
return Holder_ = = Currentthread::tid ();//prevent cross-threading calls
}
void assertlocked () const
{
ASSERT (Islockedbythisthread ());
}
Internal usage
void Lock ()
{
Mcheck (Pthread_mutex_lock (&mutex_));//Locking
Assignholder ();///locking Gets the thread number of the current thread, that is, the current thread has this lock
}
void Unlock ()
{
Unassignholder ();//indicates that no thread currently owns this lock
Mcheck (Pthread_mutex_unlock (&mutex_));//Go to lock
}
pthread_mutex_t* Getpthreadmutex ()/* Non-const */
{
Return &mutex_;
}
Private
Friend class condition;//condition variable must hold lock before use
Class unassignguard:boost::noncopyable//This inner class appears inexplicably
{
Public
Unassignguard (mutexlock& owner)
: Owner_ (owner)
{
Owner_.unassignholder ();
}
~UnassignGuard(){ owner_.assignHolder();}
Private
mutexlock& Owner_;
};
void Unassignholder ()
{
Holder_ = 0;
}
void Assignholder ()
{
Holder_ = Currentthread::tid ();
}
pthread_mutex_t mutex_;
pid_t Holder_;
};
Use as a stack variable, eg.
int foo::size () const
// {
Mutexlockguard Lock (MUTEX_);
return Data_.size ();
// }
This class is responsible for managing mutex locking and de-lock
Class Mutexlockguard:boost::noncopyable
{
Public
Explicit Mutexlockguard (mutexlock& mutex)
: Mutex_ (Mutex)
{
Mutex_.lock ();
}
~mutexlockguard ()
{
Mutex_.unlock ();
}
Private
mutexlock& mutex_;
};
有四种操作互斥锁的方式:创建,销毁,加锁,解锁。在muduo中,用一个低级的资源管理类MutexLock来实现这四种操作,再用一个较高级的资源管理类MutexLockGuard来管理MutexLock,即用RAII手法对资源进行两次封装,防止资源泄漏。两个类都具有nocopy的属性,试想对Mutex的拷贝会在多线程程序中造成什么样的结果?有至少两个线程在同一时间拥有对一份资源的使用资格,后果不可设想。在MutexLock中有一个好玩的私有变量:holder_. 该变量在一个线程对资源加锁时,将holder_设置为使用资源线程的索引;解锁时将holder_设置为0。初始化Mutex时将holder_设置为0;销毁时检查holder_是否为0。以上四个步骤保证了Mutex在某一个时间段内能被一个线程使用。MutexLock与Condition是友元关系,具有很强的耦合度。+ Condition
/Condition.h/
Class Condition:boost::noncopyable
{
Public
Explicit Condition (mutexlock& mutex)
: Mutex_ (Mutex)
{
Mcheck (Pthread_cond_init (&pcond_, NULL));
}
~condition ()
{
Mcheck (Pthread_cond_destroy (&pcond_));
}
void Wait ()
{
Mutexlock::unassignguard ug (mutex_);
Mcheck (Pthread_cond_wait (&pcond_, Mutex_.getpthreadmutex ()));
}
Returns true if time out, false otherwise.
BOOL Waitforseconds (double seconds);
void Notify ()
{
Mcheck (Pthread_cond_signal (&pcond_));
}
void Notifyall ()
{
Mcheck (Pthread_cond_broadcast (&pcond_));
}
Private
mutexlock& mutex_;
pthread_cond_t Pcond_;
};
条件变量有五种操作方式:创建,销毁,等待,单一通知,全部通知。在MutexLock中有一个内部类:UnassignGuard,该类的实例对象在Condition等待时创建,将holder_设置为0;当等待事件结束,又将holder_设置为原值。用MutexLock的析构函数检查等待事件是否发生在同一个线程中。Condition类中有一个waitForSecond函数,用于实现pthread_cond_timewait的封装。接下来,聊一聊主题--Thread。+ Thread
/Thread.h/
Class Thread:boost::noncopyable//No copy
{
Public
typedef boost::function
Explicit thread (const threadfunc&, const string& name = string ());//Normal thread constructors
Ifdef
gxx_experimental_cxx0x
explicit thread (threadfunc&&, const string& name = string ());//Move the threading constructor, which is more resource-efficient than the above std::move
endif
~thread ();//destructor
void start ();//Start thread
int join (); Similar to Pthread_join ()
BOOL Started () const {return started_;}
pthread_t Pthreadid () const {return pthreadid_;}
pid_t tid () const {return *tid_;}//Return thread index
Const string& Name () const {return name_;} Return thread Name
static int numcreated () {return numcreated_.get ();}
Private
void Setdefaultname ();
BOOL Started_; Whether to start
BOOL Joined_; Whether to terminate
pthread_t pthreadid_; Thread Index
Boost::shared_ptr
Static AtomicInt32 Numcreated_; The static variable is shared among all the thread objects, and the thread ordering is generated by the class
};
1. 在muduo的线程对象封装中,最精彩的是使用boost::function函数对象将线程函数以回调的方式传递进线程对象中。` typedef boost::function<void ()> ThreadFun; `2. 在多线程情况下,避免在对象外操作指向对象的指针的情形,可以在一定程度上保证了线程安全。
/thread.cc/
AtomicInt32 Thread::numcreated_;
Two kinds of thread constructors
The movable properties of a thread object are interesting.
Thread::thread (const threadfunc& func, const string& N)
: Started_ (False),
Joined_ (False),
Pthreadid_ (0),
Tid_ (new pid_t (0)),
Func_ (func),
Name_ (N)
{
Setdefaultname ();
}
Ifdef
gxx_experimental_cxx0x
Thread::thread (threadfunc&& func, const string& N)
: Started_ (False),
Joined_ (False),
Pthreadid_ (0),
Tid_ (new pid_t (0)),
Func_ (Std::move (func)),
Name_ (N)
{
Setdefaultname ();
}
endif
Thread::~thread ()
{
if (started_ &&!joined_)//Set the thread as detached property
{
Pthread_detach (PTHREADID_); Thread end will automatically reclaim resources
}
}
void Thread::setdefaultname ()//set thread name, such as THREAD1,THREAD2, etc.
{
int num = Numcreated_.incrementandget ();
if (Name_.empty ())
{
Char buf[32];
snprintf (buf, sizeof buf, "thread%d", num);
Name_ = BUF;
}
}
void Thread::start ()
{
ASSERT (!started_); Assert whether the thread has started running
Started_ = true; Assertion failure sets the flag for the thread to start running
Fixme:move (FUNC_)
detail::threaddata* data = new Detail::threaddata (func_, name_, tid_); Get the required parameters for the thread to run
if (Pthread_create (&pthreadid_, NULL, &detail::startthread, data))//The thread starts running and the control flow of the thread stops again here.
{//thread runs end, thread runs itself and logs itself
Started_ = false;
printf ("Blockdim.x:%d\n", blockdim.x);
Delete data; Or no delete?
Log_sysfatal << "Failed in Pthread_create";
}
}
int Thread::join ()
{
ASSERT (Started_); Assert whether the thread is running
ASSERT (!joined_); Asserts whether the thread has been terminated
Joined_ = true;
Return Pthread_join (Pthreadid_, NULL); Wait for thread to end
}
在线程的析构函数中只设置线程的分离属性,即等待线程运行结束后自动回收线程资源,不强行终止线程。
struct Threaddata//used as thread data to save thread run-related data to the struct, a bit of abstract callback meaning
{
typedef MUDUO::THREAD::THREADFUNC THREADFUNC;
ThreadFunc func_;
String name_;
Boost::weak_ptr
Threaddata (const threadfunc& func,
Const string& Name,
Const BOOST::SHARED_PTR
void Runinthread ()//Core function
{
pid_t tid = Muduo::currentthread::tid (); Get the current thread flag
boost::shared_ptr<pid_t> Ptid = Wktid_.lock (); Determines whether the thread stored in the Threaddata exists if (Ptid)//If present, ptid the thread identity before releasing {*ptid = Tid; Ptid.reset ();} Muduo::currentthread::t_threadname = Name_.empty ()? "Muduothread": Name_.c_str (); Get current thread Name::p rctl (Pr_set_name, muduo::currentthread::t_threadname); try{func_ ();//Run thread function muduo::currentthread::t_ ThreadName = "Finished";} catch (const exception& EX)//Exception snap section {Muduo::currentthread::t_threadname = "crashed"; fprintf (stderr, "Exception caught in Thread%s\n", Name_.c_str ()); fprintf (stderr, "Reason:%s\n", Ex.what ()); fprintf (stderr, "stack trace:%s\n", Ex.stacktrace ()); Abort ();} catch (const std::exception& ex) {muduo::currentthread::t_threadname = "crashed"; fprintf (stderr, "Exception caught in Thread%s\n", Name_.c_str ()); fprintf (stderr, "Reason:%s\n", Ex.what ()); Abort ();} catch (...) {muduo::currentthread::t_threadname = "crashed"; fprintf (stderr, "Unknown exception caught in Thread%s\n", Name_.c_str ()); Throw Rethrow
}
};
void* startthread (void* obj)//This function is the most interesting
{
threaddata* data = static_cast
将线程中的若干数据保存到ThreadData中,然后将ThreadData作为传递给`pthread_create(...,void* arg)`中的最后一个数据参数传递给`void Thread(void* )`标准的线程启动函数。然后在标准的线程启动函数内将`void* arg`强行转化为ThreadData,然后使用ThreadData启动线程。在使用muduo的接口时,使用bind将线程运行函数再打包,然后传递进Thread.最后,向大家介绍muduo库中对于线程池的封装的理解。1. 最重要的想法就是线程池将线程看为自己可执行的最小并且可随时增加的单位。2. 整个线程池对象维持两个任务队列,threads_表示目前正在运行中的线程池,queue_表示位于存储队列中的等待线程。3. thread_在运行的过程中使用while循环+条件变量判断当前的活动线程池中是否有空位,以及新的等待线程进入线程池。4. 线程池从一开始就确定了自己将要运行的线程数目,不能在后面的运行中更改。
/ThreadPool.h/
Class Threadpool:boost::noncopyable
{
Public
typedef boost::function
Explicit ThreadPool (const string& Namearg = string ("ThreadPool"));
~threadpool ();
Must be called before start ().
Set the maximum load that the thread pool runs and the thread that will be running in the thread pool
void setmaxqueuesize (int maxSize) {maxqueuesize_ = maxSize;} //
void Setthreadinitcallback (const task& CB)
{threadinitcallback_ = cb;}
void start (int numthreads);//Start a certain number of threads
void Stop ();//thread pool operation stopped
Const string& name () const
{return name_;}
size_t queuesize () const;//returns the thread task that is waiting to be queued
Could block If maxqueuesize > 0
void run (const task& f);//The task queue that puts a thread that you want to run into the threads pool
Ifdef
gxx_experimental_cxx0x
void run (task&& f);//c++11 method of movement to conserve resources
endif
Private
BOOL Isfull () const;//determine if the thread queue is full
void Runinthread ();//The function that actually lets the thread run up
Task take ();//Get the first thread of the task queue
mutable mutexlock mutex_;//Mutual exclusion lock
Condition notempty_;//Condition Variable
Condition Notfull_;
String name_;
The thread object that is executed in the Task threadinitcallback_;//thread pool
Boost::p Tr_vector
5. 每一个加入线程池的线程都带有一个while循环,保证线程等待队列中的线程不会等待太久。即所有将加入线程池的线程都会进入线程等待队列接受检查。6. start():线程池启动函数保证在调用时启动一定数量的线程。7. stop():保证所有的正在运行的线程停止8. queueSize():返回此时线程等待队列中的个数,用于判断线程等待队列是否为空9. run():如果线程池为空,直接跑传入的线程。如果线程池等待队列满了,则当前控制流(线程)在notFull_上等待;否则将传入的线程加入线程等待队列,并且使用条件变量notEmpty_通知一条阻塞在该条件变量上的控制流(线程)。10. take():如果当前线程等待队列为空并且线程池正在跑,则控制流(线程)阻塞在notEmpty_条件变量上。当条件变量被激活时(有线程对象加入呆线程等待队列),判断是否可以从线程等待队列中拿出一个线程对象,如果可以,则将使用条件变量notFull_通知run()中阻塞在--想加入队列但是队列没有空余位置的变量上。11. isFull():返回在线程等待队列中的个数,用于判断是否可以将想要运行的线程放到线程等待队列中。12. runInThread():如果线程启动函数不为空,则在此将线程的控制流交给用于初始化线程池的线程对象。当此线程对象运行结束的时候,并且此时的线程池还在运行,则线程池离开初始化模式,进入线程池的循环线程补充模式。这种模式控制着线程池中的线程数量:当有新的线程对象进入线程池,则当前的线程控制流交给将要执行的线程对象。也就是说线程池中的线程对象要么主动结束自己的‘life’,然后由线程池的线程补充模式决定将要进入线程池运行的线程对象。然后在后面的take()中使用条件变量完成新的线程进入线程池的同步。
/threadpool.cc/
Threadpool::threadpool (const string& NAMEARG)
: Mutex_ (),
Notempty_ (Mutex_),
Notfull_ (Mutex_),
Name_ (Namearg),
Maxqueuesize_ (0),
Running_ (False)
{
}
Threadpool::~threadpool ()
{
if (running_)
{
Stop ();
}
}
void Threadpool::start (int numthreads)
{
ASSERT (Threads_.empty ());//First boot, assertion that the thread pool is empty
Running_ = true;
Threads_.reserve (numthreads);//pre-allocating space, and the allocated space is immutable.
for (int i = 0; i < numthreads; ++i)
{
Char id[32];
snprintf (ID, sizeof ID, "%d", i+1);
Threads_.push_back (New Muduo::thread (
Boost::bind (&threadpool::runinthread, this), Name_+id));
Threads_[i].start ();//start thread directly
}
if (numthreads = = 0 && threadinitcallback_)//start only one thread
{
Threadinitcallback_ ();
}
}
void Threadpool::stop ()
{
{
Mutexlockguard Lock (MUTEX_);
Running_ = false;
Notempty_.notifyall ();
}
For_each (Threads_.begin (),
Threads_.end (),
Boost::bind (&muduo::thread::join, _1));
}
size_t threadpool::queuesize () const
{
Mutexlockguard Lock (MUTEX_);
return Queue_.size ();
}
void Threadpool::run (const task& Task)
{
if (Threads_.empty ())//If thread pool is empty, run this thread directly
{
Task ();
}
Else
{
Mutexlockguard Lock (MUTEX_);
while (Isfull ())//If the thread pool is full, wait on the notfull condition variable
{
Notfull_.wait ();
}
ASSERT (!isfull ());
queue_.push_back(task);//现在线程池中有空位了notEmpty_.notify();//notempty条件变量通知信息
}
}
Ifdef
gxx_experimental_cxx0x
void Threadpool::run (task&& Task)
{
if (Threads_.empty ())
{
Task ();
}
Else
{
Mutexlockguard Lock (MUTEX_);
while (Isfull ())
{
Notfull_.wait ();
}
ASSERT (!isfull ());
queue_.push_back(std::move(task));notEmpty_.notify();
}
}
endif
Threadpool::task Threadpool::take ()
{
Mutexlockguard Lock (MUTEX_);
Always use a while-loop, due to spurious wakeup
while (Queue_.empty () && running_)//If thread queue is empty and thread pool is running
{//wait on notempty condition variable
Notempty_.wait ();//When the front stop waits, when the queue is not empty continue to run
}//then get a new mission
Task task;//Create a new task
if (!queue_.empty ())
{
task = Queue_.front ();//Get header task in queue
Queue_.pop_front ();//Header task in the popup queue
if (Maxqueuesize_ > 0)//If the queue maximum length is greater than 0
{
Notfull_.notify ();//notify Thread can run
}
}
Return task;//back to task
}
BOOL Threadpool::isfull () const
{//is used to determine if the thread queue has
mutex_.assertlocked ();
Return maxqueuesize_ > 0 && queue_.size () >= maxqueuesize_;
}
void Threadpool::runinthread ()//Generate a ThreadFunc object
{
Try
{
if (threadinitcallback_)//If the thread start function is not empty, start directly
{
Threadinitcallback_ ();//Open a new thread here, the program's running process stops here, and when the thread is finished, it goes to the following while loop
}
while (Running_)//This loop ensures that when the upper thread runs or does not initialize the thread, the loop mode enters the thread pool
{
Task Task (take ());
if (Task)
{
Task ();
}
}
}
catch (const exception& EX)//exception capture process
{
fprintf (stderr, "Exception caught in ThreadPool%s\n", Name_.c_str ());
fprintf (stderr, "Reason:%s\n", Ex.what ());
fprintf (stderr, "stack trace:%s\n", Ex.stacktrace ());
Abort ();
}
catch (const std::exception& ex)
{
fprintf (stderr, "Exception caught in ThreadPool%s\n", Name_.c_str ());
fprintf (stderr, "Reason:%s\n", Ex.what ());
Abort ();
}
catch (...)
{
fprintf (stderr, "Unknown exception caught in ThreadPool%s\n", Name_.c_str ());
Throw Rethrow
}
```
Analysis of threading facilities in Muduo Library