stl库里面的list使用经历总结

来源:互联网 发布:优化后讨鬼传2psv画质 编辑:程序博客网 时间:2024/05/17 10:55

最近要用到c++标准库里面的list,但是它又不是支持多线程的。所以我决定对其进行一下改造,变成支持多线程的。

思路上面比较简单,模仿C#下面的模式,继承list,然后对用到的方法进行重载,加入lock,变成支持多线程的。实现的时候还是发现了很多问题,主要还是c++可怕的语法,搞了我好几天天。下面就总结一下。

首先为了简便,我就不继承了。

首先是类的声明:
template<class T>
class SyncList
{
public:
 SyncList();
 virtual ~SyncList();
 bool empty();
 T setTobegin();
 T next();
 bool end();
 void push_front(const T& value);
 void push_back(congst T& value);
 void clear();
 void remove(const T& value);
private:
 list<T> _list;
 pthread_mutex_t _mutex;
 _List_iterator<T> head;
 _List_iterator<T> tail;
};

这里面还是有些东西要说明一下的。首先push_front和push_back的声明,相信大家看着一定非常眼熟,因为那是我从stl的库里原样copy过来的,嘿嘿。然后是head和tail的类型——_List_iterator<T>。其实它就是list<T>::iterator。但是如果用iterator来声明head和tail的话,会编译不过去。我去查过list的头文件,看到里面有个typedef _List_iterator<_Tp> iterator。既然iterator不能用,那就用_List_iterator<T>好了。事实证明,这个果然可以用的,呵呵。

接下来,我说一下上面如此设计的思路。其实这个设计一点亮点都没有,或者说很屎吧。既然如此,还要说思路,主要就是为了总结一下对list的使用经验。

首先,因为这是一个支持多线程的list,因此遍历它的时候,我们不能像以前那样,调用begin,返回一个iterator,然后给iterator不断++,直到它等于end为止。这是因为,在我们遍历list的时候,另外一个线程很可能已经修改了这个list,随便给iterator++的话,你一定会像我刚开始那样——死得很惨。连续好几天在“段错误”里面徘徊,真的很容易让人短命!

因此,所有的iterator++操作,都要被封装在SyncList里面,这样才能尽量保证它没有问题。注意我说的是尽量。

基于上面的原因,这个SyncList就提供了个setTobegin功能,意思是告诉这个list,我要遍历你了,你把自己的head指向开头,tail指向结尾。下面是这个方法的代码:

template<class T>//因为声明和实现分开,所以在每个方法前面都要加这一行,很不爽是吧?我也是感觉这代码看起来很不爽。
T SyncList<T>::setTObegin()
{
 T temp;//一定要声明一个局部变量,否则就无法支持多线程了。
 pthread_mutex_lock(&_mutex);//加锁
 head = _list.begin();//head指向开头
 tail = _list.end();//tail指向结尾,注意结尾不是NULL,它真的是个尾巴呢。这是与.net类库里面不一样的地方,.net类库里面,尾巴是null
 temp = *head;//取得head里面的数据
 pthread_mutex_unlcok(&_mutex);//解锁
 return temp;//设置好开头和结尾后,顺便返回头部的数据
}

既然可以开始了,那就得有个继续下去的道路,于是next方法又出现了:

template<class T>//每次都要把这行写一遍,太烦人了!!!
T SyncList<T>::next()
{
 T temp;
 pthread_mutex_lock(&_mutex);
 head++;
 temp = *head;
 pthread_mutex_unlock(&_mutex);
 return temp;
}
这里面其实藏着一个大问题,一个可以导致“段错误”的问题。所谓“段错误”,简单说,就是指针野了,引用了系统不让用的地方,然后出于保护的目的,程序会被系统直接挂掉,然后抛出一个“段错误”的异常。
那么隐患在哪里呢?就是那行head++!!!
可以看到,每次我调用next的时候,都会基于上次的位置,进行++操作,转到下一个。但是,如果,我突然调用了一下remove,正好删掉了当前的value,那现在的head就野了,再去调用就会出错了,大家可以试一下下面的代码,保证死得很壮烈:

list<T> ll;
list<T>::iterator current;
ll.push_back(100);
ll.push_back(200);
int a = *current;//ok,a是100
ll.remove(100);//current已经野了
current++;
a = *current;//error!不是200,而是崩溃

因此,为了避免这个问题,我改造了remove方法:
template<class T>
void SyncList<T>::remove(const T& value)
{
 pthread_mutex_lock(&_mutex);
 if (*head == value)//变化在这里,删除前判断一下,如果是当前的节点,就把当前节点后移一位
  head++;//在本质上,相当于把head++和remove的顺序对换了一下。
 _list.remove(value);
 pthread_mutex_unlock(&_mutex);
}

如此一来,就可以避免“段错误”了。

下面是end方法的代码,用来判断是否到结尾了。
template<class T>
bool SyncList<T>::end()
{
 bool temp = false;
 pthread_mutex_lock(&_mutex);
 if (head == tail)
  temp = true;
 else
  temp = false;
 pthread_mutex_unlock(&_mutex);
 return temp;
}

此外,还要在构造函数和析构函数里面处理互锁的问题。
template<class T>
SyncList<T>::SyncList()
{
 pthread_mutex_init(&_mutex);
}

template<class T>
SyncList<T>::~SyncList()
{
 pthread_mutex_destory(&_mutex);
}

其他的方法,都很简单,就不多说了。

 


 

原创粉丝点击