目录
5.如果调用的函数的参数规定是普通数组,但是使用的是vector如何转换
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会不会丢失呢?