最近在学习陈硕大神的muduo库,感觉写的很专业,以及有一些比较“高级”的技巧和设计方式,自己写会比较困难。
于是打算自己写一个简化版本的Reactor模式网络库,就取名叫mini吧,同样只基于Linux平台,不使用boost库,去掉一些比较复杂的部分,只实现比较基本的功能。
写作的过程中,参考了https://github.com/chenshuo/muduo(原始版本的实现),以及https://github.com/AlexStocks/muduo(去掉boost库的依赖,改用C++11)
就先从用于线程同步的互斥锁和条件变量的封装开始吧,基础部分还会包括一个很简单的日志类、线程封装和简单的线程池。
Linux环境下线程同步的方式有很多,互斥锁、读写锁、自旋锁、条件变量、屏障等都可以作为同步的方式,muduo库使用的是互斥锁+条件变量的方式,原因也很简单,就是简单易用,同时也不失高效性。
为了通用性,使用的都是POSIX的同步原语以及线程实现。
首先是对互斥量的封装:
1 #ifndef MUTEX_H
2 #define MUTEX_H
3 #include <pthread.h>
4 namespace mini
5 {
6 //used as class member
7 class MutexLock
8 {
9 public:
10 MutexLock()
11 {
12 pthread_mutex_init(&mutex_,NULL);
13 }
14 ~MutexLock()
15 {
16 pthread_mutex_destroy(&mutex_);
17 }
18 void lock()
19 {
20 pthread_mutex_lock(&mutex_);
21 }
22 void unlock()
23 {
24 pthread_mutex_unlock(&mutex_);
25 }
26
27 pthread_mutex_t* getPthreadMutex()
28 {
29 return &mutex_;
30 }
31
32 private:
33 pthread_mutex_t mutex_;
34 };
35 //used as RAII obj
36 class MutexLockGuard
37 {
38 public:
39 MutexLockGuard(MutexLock& mutex)
40 :mutex_(mutex)
41 {
42 mutex_.lock();
43 }
44 ~MutexLockGuard()
45 {
46 mutex_.unlock();
47 }
48
49 private:
50 MutexLock& mutex_;
51 };
52 }
53
54 #endif // MUTEX_H
MutexLock是对pthread_mutex的简单封装,包括初始化、加锁、解锁以及销毁,主要用作类的成员变量(比如Condition类、ThreadPool类等)。
MutexLockGuard是一个RAII类,构造时自动加锁,析构时自动解锁,一般用在整个过程都需要加锁的块内(比如一个作用于临界区的函数),可以避免忘记解锁引起的死锁。
然后是对于条件变量的封装:
1 #ifndef CONDITION_H
2 #define CONDITION_H
3 #include "Mutex.h"
4 #include <pthread.h>
5 namespace mini
6 {
7 class Condition{
8 public:
9 Condition(MutexLock& mutex)
10 :mutex_(mutex)
11 {
12 pthread_cond_init(&cond_,NULL);
13 }
14
15 ~Condition()
16 {
17 pthread_cond_destroy(&cond_);
18 }
19
20 void wait()
21 {
22 pthread_cond_wait(&cond_,mutex_.getPthreadMutex());
23 }
24
25 void notify()
26 {
27 pthread_cond_signal(&cond_);
28 }
29
30 void notifyAll()
31 {
32 pthread_cond_broadcast(&cond_);
33 }
34
35 private:
36 MutexLock& mutex_;//reference, not hold
37 pthread_cond_t cond_;
38 };
39 }
40
41 #endif // CONDITION_H
Condition类是对pthread_cond的封装,因为条件变量本来就要与mutex配合使用,故而持有一个MutexLock的引用,主要操作是lock()、notify()和notifyAll()。
下面通过一个简单的例子来看看这两个类的使用:
1 #include <iostream>
2 #include "Condition.h"
3 #include <unistd.h>
4
5 using namespace std;
6 using namespace mini;
7
8 mini::MutexLock mutex;
9 mini::Condition cond(mutex);
10
11 int count=0;
12
13
14 void* threadFuncAdd(void*)
15 {
16 sleep(1);
17 cout<<"ThreadAdd run!"<<endl;
18 while(count<=1000)
19 count++;
20 cout<<"ThreadAdd finish!"<<endl;
21 cond.notify();
22 }
23 void* threadFuncPrint(void*)
24 {
25 mutex.lock();
26 while(count<=1000)
27 {
28 cout<<"ThreadPrint wait!"<<endl;
29 cond.wait();
30 }
31
32 cout<<"ThreadPrint wake up!"<<endl;
33 }
34
35
36 int main()
37 {
38 //count=0;
39 pthread_t p1,p2;
40 pthread_create(&p1,NULL,threadFuncAdd,NULL);
41 pthread_create(&p2,NULL,threadFuncPrint,NULL);
42
43 sleep(2);
44
45 return 0;
46 }
简单来说,主线程创建了两个线程,一个用来对count进行自加,一个用来等待count值到达100并输出一句话。为了确保print线程会先等待条件,让add线程睡眠了1s。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLzYTO4cTO4cjMx0SMxcDNzQTMxIDMxITM4EDMy0yN5AjM5ETMvwlMxgTMwIzLcdTOwITOxEzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
结果与预期一致,threadprint先进入等待状态,然后threadadd开始执行,并在count自加到1000后,notify() threadprint,threadprint被唤醒。