muduo库中的编程技巧总结

目录

1.将上锁和解锁封装到类的析构和构造函数中去

2.检测类型是否是完全类型

3.观察者模式

4.弱回调

5.如果调用的函数的参数规定是普通数组,但是使用的是vector如何转换

6.对于条件变量的使用


1.将上锁和解锁封装到类的析构和构造函数中去

程序如下

class MutexLockGuard
{
 public:
  explicit MutexLockGuard(pthread_mutex_t& mutex)
  {
    pthread_mutex_lock(&mutex_)
  }

  ~MutexLockGuard()
  {
    pthread_mutex_unlock(&mutex_);
  }

 private:

  pthread_mutex_t& mutex_;
};

好处如下:

2.检测类型是否是完全类型

在muduo库中经常出现这段程序:

typedef char type_must_be_complete[ sizeof(T) == 0? -1: 1 ];

这段程序判断类型T是否是完全类型,如果不是完全类型,sizeof(T)是0,那么 sizeof(T) == 0? -1: 1就是-1,所以 定义这样一个数组type_must_be_complete[ -1 ]是会报错的。通过这样来判断是否是完全类型。而且这一步是在编译时就会检测出来了,避免在运行时出现这种错误

完全类型:定义完全的类型称为完全类型

不完全类型:定义不完全的类型(比如只有声明没有定义,或者只有定义没有声明,比如前向声明忘记加头文件了)

3.观察者模式

这是一种设计模式,这篇博文介绍比较易懂:https://www.cnblogs.com/suzhou/p/dp16obsvr.html

用智能指针实现的线程安全版本的观察者模式:

class Observable;

class Observer : public boost::enable_shared_from_this<Observer>//观察者
//继承的类是可以将this指针变为只能指针,将this变为shared_ptr,使用shared_from_this()函数
{
 public:
  virtual ~Observer();
  virtual void update() = 0;

  void observe(Observable* s);

 protected:
  Observable* subject_;
};

class Observable//消息分发者
{
 public:
  void register_(boost::weak_ptr<Observer> x);

  void notifyObservers()
  {
    muduo::MutexLockGuard lock(mutex_);
    Iterator it = observers_.begin();
    while (it != observers_.end())
    {
      boost::shared_ptr<Observer> obj(it->lock());//这里使用了弱回调
      if (obj)
      {
        obj->update();
        ++it;
      }
      else
      {
        printf("notifyObservers() erase\n");
        it = observers_.erase(it);
      }
    }
  }

 private:
  mutable muduo::MutexLock mutex_;
  std::vector<boost::weak_ptr<Observer> > observers_;
  typedef std::vector<boost::weak_ptr<Observer> >::iterator Iterator;
};

Observer::~Observer()
{
  // subject_->unregister(this);
}

void Observer::observe(Observable* s)
{
  s->register_(shared_from_this());//将this变为shared_ptr
  subject_ = s;
}

void Observable::register_(boost::weak_ptr<Observer> x)
{
  observers_.push_back(x);
}


class Foo : public Observer
{
  virtual void update()
  {
    printf("Foo::update() %p\n", this);
  }
};

int main()
{
  Observable subject;
  {
    boost::shared_ptr<Foo> p(new Foo);//每一个观察者都是用shared_ptr指向的动态内存区域
    p->observe(&subject);//在消息分发者那里注册时,是用的weak_ptr指针注册的
    subject.notifyObservers();//每次广播消息时,会将weak_ptr指针提升为shared_ptr指针,如果观察者不存在,
				//就会提升失败,否则会提升成功,这时,shared_ptr里面的计数引用值至少为2
  }
  subject.notifyObservers();
}

4.弱回调

弱回调的中心思想就是:如果对象还活着,就调用它的成员函数,否则就忽略它。

实现方法就是利用一个weak_ptr指向所需调用的对象,在使用前先提升为shared_ptr,通过这一步来检测对象是否在。

5.如果调用的函数的参数规定是普通数组,但是使用的是vector如何转换

以epoll_wait为例

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

可以看到第二参数需要一个epoll_event类的数组指针,常规使用方法如下:

struct epoll_event all[300];

epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);

但是我想使用vector数组,应该是这样的:

vecotr<struct epoll_event> all(300);

epoll_wait(epfd,&*all.begin(),static_cast<int>(all.size()),-1);

以上是muduo中这样使用的例子,但是如果度过STL源码,可以知道其实vector容器的迭代器就是指针,那all.begin()得到的就是数组开始的迭代器,同样也是指针,是不是不需要这样麻烦的进行转换呢,下面使用一个小例子来测试。

#include <iostream>
#include <vector>
using namespace std;

void test(int *a)
{
	for (int i=0; i<3; i++)
		cout<<a[i]<<endl;
}

int main()
{
	int a[]={1,2,3,4};
	vector<int> b(a, a+4);
	test(a);
	test(b.begin());//错误
	test((int *)b.begin());//错误
	test(&*b.begin());//正确
	return 0;
}

 

编译错误结果图

可以看到,这里显示并不能由vector<int>::iterator转换成int *。那也就是说linux中vector的迭代器并不是普通指针。

所以我找到了源码进行探究。vector定义迭代器的源码如下(源码所在位置:/usr/include/c++/5/bits):

typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;
//这里的__normal_iterator是一个linux中STL自定义的迭代器模板
//而pointer就是指向数组的普通指针

__normal_iterator源码如下,可以看到这个迭代器模板中重载了许多运算符函数,所以不需要每个容器的迭代器进行额外编写,所以还是有用的:

    template<typename _Iterator, typename _Container>
    class __normal_iterator
    {
    protected:
      _Iterator _M_current;

      typedef iterator_traits<_Iterator>		__traits_type;

    public:
      typedef _Iterator					iterator_type;
      typedef typename __traits_type::iterator_category iterator_category;
      typedef typename __traits_type::value_type  	value_type;
      typedef typename __traits_type::difference_type 	difference_type;
      typedef typename __traits_type::reference 	reference;
      typedef typename __traits_type::pointer   	pointer;

      _GLIBCXX_CONSTEXPR __normal_iterator() _GLIBCXX_NOEXCEPT
      : _M_current(_Iterator()) { }

      explicit
      __normal_iterator(const _Iterator& __i) _GLIBCXX_NOEXCEPT
      : _M_current(__i) { }

      // Allow iterator to const_iterator conversion
      template<typename _Iter>
        __normal_iterator(const __normal_iterator<_Iter,
			  typename __enable_if<
      	       (std::__are_same<_Iter, typename _Container::pointer>::__value),
		      _Container>::__type>& __i) _GLIBCXX_NOEXCEPT
        : _M_current(__i.base()) { }

      // Forward iterator requirements
      reference
      operator*() const _GLIBCXX_NOEXCEPT//标注一
      { return *_M_current; }

      pointer
      operator->() const _GLIBCXX_NOEXCEPT
      { return _M_current; }

      __normal_iterator&
      operator++() _GLIBCXX_NOEXCEPT
      {
	++_M_current;
	return *this;
      }

      __normal_iterator
      operator++(int) _GLIBCXX_NOEXCEPT
      { return __normal_iterator(_M_current++); }

      // Bidirectional iterator requirements
      __normal_iterator&
      operator--() _GLIBCXX_NOEXCEPT
      {
	--_M_current;
	return *this;
      }

      __normal_iterator
      operator--(int) _GLIBCXX_NOEXCEPT
      { return __normal_iterator(_M_current--); }

      // Random access iterator requirements
      reference
      operator[](difference_type __n) const _GLIBCXX_NOEXCEPT
      { return _M_current[__n]; }

      __normal_iterator&
      operator+=(difference_type __n) _GLIBCXX_NOEXCEPT
      { _M_current += __n; return *this; }

      __normal_iterator
      operator+(difference_type __n) const _GLIBCXX_NOEXCEPT
      { return __normal_iterator(_M_current + __n); }

      __normal_iterator&
      operator-=(difference_type __n) _GLIBCXX_NOEXCEPT
      { _M_current -= __n; return *this; }

      __normal_iterator
      operator-(difference_type __n) const _GLIBCXX_NOEXCEPT
      { return __normal_iterator(_M_current - __n); }

      const _Iterator&
      base() const _GLIBCXX_NOEXCEPT
      { return _M_current; }
    };

由此可以分析出来,linux中vector确实无法直接转换成普通指针了 。但是在标注一处可以得到&*vector.begin()是有用的,因为经过*运算符,已经把迭代器转换成指针指向的值,再取地址,就得到了普通指针。

6.对于条件变量的使用

pthread_cond_signal在单核情况下,只会唤醒一个线程,并且根据等待线程优先级的高低确定哪个线程被唤醒。所以pthread_cond_signal一般不会出现惊群现象,但是在多核的情况下,就有可能唤醒多个线程。这种唤醒被称为虚假唤醒

所以在使用条件变量的阻塞等待时,最好使用while循环将pthread_cond_wait函数包括在内,主要是担心如果使用pthread_cond_signal函数去唤醒条件阻塞时,在有多个线程阻塞在pthread_cond_wait函数中时,本来只应该唤醒一个线程,但是可能会唤醒多个线程,具体原因见POSIX的RATIONALE:

1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.

pthread_mutex_lock(&mut);
while (x <= y) {//用while循环判断来确保只有一个函数被唤醒
    pthread_cond_wait(&cond, &mut);
}

另外注意如果一个线程执行pthread_cond_signal函数时,其他线程都在忙碌,没有一个线程阻塞在pthread_cond_wait情况下,pthread_cond_signal仍然会显示执行成功,相当于这个信号就被丢弃了,所以一定要在pthread_cond_signal函数执行之前,判断是否有线程阻塞等待,一般这种情况称之为条件变量信号丢失(唤醒丢失)。(网上都这么说)

而且感觉在muduo的线程池中,并没有保证线程不丢失的措施。

但是我自己做了一个测试,感觉并不是这样的:

程序如下:

#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
 
using namespace std;
pthread_mutex_t mutex;//定义互斥锁变量
pthread_cond_t cond;//定义条件变量
 
struct list{
	int value;
	list* next;
};//创建一个链表作为商品
 
list *Head=NULL;
int num=1;
void *thr_product(void *arg)
{
/*睡1秒,然后发送一个信号*/
		sleep(1);
		list *food= new list;
		food->value=num++;
		cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<food->value<<endl;
		pthread_mutex_lock(&mutex);
		food->next=Head;
		Head=food;
		pthread_cond_signal(&cond);//解除在条件变量上的阻塞
		pthread_mutex_unlock(&mutex);
/*再睡1秒,再发送一个信号*/
		sleep(1);
		food= new list;
		food->value=num++;
		cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<food->value<<endl;
		pthread_mutex_lock(&mutex);
		food->next=Head;
		Head=food;
		pthread_cond_signal(&cond);//解除在条件变量上的阻塞
		pthread_mutex_unlock(&mutex);

		sleep(3);

	return NULL;
}
 
void *thr_customer(void *arg)
{
	list *pro=NULL;
	while (1){
        /*每次都睡3秒,然后再进入条件等待*/
		sleep(3);
		pthread_mutex_lock(&mutex);
		if (Head==NULL) pthread_cond_wait(&cond, &mutex);
		num--;
		pro=Head;
		Head=Head->next;
		cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<pro->value<<endl;
		pthread_mutex_unlock(&mutex);
		delete pro;
	}
	return NULL;
}
 
int main()
{
	pthread_mutex_init(&mutex, NULL); 
	pthread_cond_init(&cond, NULL);
	pthread_t tid[2];
	pthread_create(&tid[0], NULL, thr_product, NULL);
	pthread_create(&tid[1], NULL, thr_customer, NULL);
	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	return 0;
}

 

在VS下面试一下

#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable

using namespace std;
//pthread_mutex_t mutex;//定义互斥锁变量
//pthread_cond_t cond;//定义条件变量

std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.

struct list{
	int value;
	list* next;
};//创建一个链表作为商品
 
list *Head=NULL;
int num=1;
void *thr_product()
{
/*睡1秒,然后发送一个信号*/
		sleep(1);
		list *food= new list;
		food->value=num++;
		cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<food->value<<endl;
		{
			std::unique_lock <std::mutex> lck(mtx);
			food->next=Head;
			Head=food;
			cv.notify_all();
		}
/*再睡1秒,再发送一个信号*/
		sleep(1);
		food= new list;
		food->value=num++;
		cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<food->value<<endl;
		{
			std::unique_lock <std::mutex> lck(mtx);
			food->next=Head;
			Head=food;
			cv.notify_all();
		}

		sleep(3);

	return NULL;
}
 
void *thr_customer()
{
	list *pro=NULL;
	while (1){
        /*每次都睡3秒,然后再进入条件等待*/
		sleep(3);
		{
			std::unique_lock <std::mutex> lck(mtx);
			if (Head==NULL) cv.wait(lck);
			num--;
			pro=Head;
			Head=Head->next;
			cout<<__FUNCTION__<<"---self="<<pthread_self()<<"---"<<pro->value<<endl;
		}
		delete pro;
	}
	return NULL;
}
 
int main()
{
	std::thread t1(thr_product);
	std::thread t2(thr_customer);
	t1.join();
	t2.join();
	return 0;
}

 

时间线就是一个线程先执行signal函数两次,另外一个线程再去条件等待,按理来说应该会一直阻塞在条件等待,并且之前的唤醒事件会丢失,但是现在的结果是消费者线程把之前唤醒信号都处理了,结果图如下:

就很迷惑?????到底在wait前signal会不会丢失呢?

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值