stl string对象多线程使用

  c程序所有对象类变量,包括string,stl所有对象,一般的结构体,一般的类等非bool i8 i16 i32 i64 int float double等基本类型的,如果要修改其,要注意给它加上写锁,一般的stl容器大家都知道要用读写锁来操作才安全,而string就很多人不知道也要一样的读写锁保护才安全.

    对于内存不会迁移的bool i8 i16 i32 i64char short int float double等基本类型变量和一些较为简单的结构体对象和类对象,这些对象或者对象里面的某个变量,内存"位置"始终不会变化的(固定内存变量),没有读写锁保护,可能会造成脏数据,但不会造成多线程安全死机;而对于stl容器/string,指针*内存,它们的内存是可能会变化的,这些内存会变化的变量(可变内存变量),必须要用锁来保护才能修改和使用,否则就不但可能造成脏数据,更严重的是会造成多线程安全死机.

 

我们来看看以下一段代码:(其中item->e_tag是string对象)

int32_t CHttpStatus::__update_url(uint64_t handle)
{
SHttpItem *item = NULL;

// 更新注册列表
__lock_reg();
HashmapReg::iterator itr_reg = m_reg_url_list.find(handle);
if (itr_reg != m_reg_url_list.end())
{
item = itr_reg->second;
++item->change_times;
item->update_time = vdl_get_utc();
// 拿到etag
item->e_tag = __generate_etag_string(handle, item->change_times);

__unlock_reg();
}
else
{
__unlock_reg();
LOG4CPLUS_DEBUG(g_oServerLogger, "[CHttpStatus::__update_url] handle id=" << handle << " is not found!");
return ERR;
}

__lock_write_tag();
// 更新etag列表
m_etag_list.insert(item->e_tag);
__unlock_write_tag();

return OK;
}

 

上述代码中,__lock_reg和__lock_write_tag是两个不同的写锁,用来保护m_reg_url_list和m_etag_list,也就是上面说的"一般的stl容器大家都知道要用读写锁来操作才安全".

但是,item->e_tag因为是string对象,在item->e_tag = __generate_etag_string(handle, item->change_times);要修改的地方,虽然在__lock_reg保护之下,但到了m_etag_list.insert(item->e_tag);使用的时候,却已经不在__lock_reg保护下了(暂且不说item对象本身是否存在多线程安全问题),此时,也就是下面在使用的时候,上面可能正在修改,对于"固定内存变量",这种写法可能会造成脏数据,而对于string这种"可变内存变量",不但可能会造成脏数据,更严重的是可能会造成死机.典型的"而string就很多人不知道也要一样的读写锁保护才安全".

 

这种情况,有以下几种修改思路:

1.给item->e_tag专门专门加一个读写锁,前面修改之前加写锁,后面使用的时候加读锁;
2.把__lock_reg()和__lock_write_tag()两把锁同时获得之后才进行item->e_tag = __generate_etag_string(handle, item->change_times);和m_etag_list.insert(item->e_tag);,然后同时两个解锁,这个方式效率低下,且还要保证除了这里之外,所有地方item对象任何修改使用时都要同时依赖__lock_reg()和__lock_write_tag()两把锁;
3.用局部变量保存tem->e_tag,到了下面的时候,用局部变量而不是直接tem->e_tag(最简单).

 

我们采用第3种方法,修改代码如下:

int32_t CHttpStatus::__update_url(uint64_t handle)
{
SHttpItem *item = NULL;

// 更新注册列表
string strNewEtag;
__lock_reg();
HashmapReg::iterator itr_reg = m_reg_url_list.find(handle);
if (itr_reg != m_reg_url_list.end())
{
item = itr_reg->second;
++item->change_times;
item->update_time = vdl_get_utc();
// 拿到etag
item->e_tag = __generate_etag_string(handle, item->change_times);
strNewEtag

= item->e_tag;//item->e_tag随时都可能再因多线程而修改,本线程后面要使用,必须用局部变量保存下来给本线程后面使用

__unlock_reg();
}
else
{
__unlock_reg();
LOG4CPLUS_DEBUG(g_oServerLogger, "[CHttpStatus::__update_url] handle id=" << handle << " is not found!");
return ERR;
}

__lock_write_tag();
// 更新etag列表
m_etag_list.insert(strNewEtag);//item->e_tag);
__unlock_write_tag();

return OK;
}

 

 

上述代码可能会导致内存破坏.再罗列如下可能会导致内存破坏的因素:

1.直接把"可变内存变量"用memcpy/memmove操作,破坏了对象变量,可能导致内存破坏;

2.数组越界,下标太大超过数组大小的情况,一般都能查到.但下标为负数的情况一般人就很少关注了,总以为系统会自动死机,其实不然,下标为负数时,如果往数组里面继续写就会将数组前面的内存破坏掉.

3.上面的string对象没有像stl容器一样多线程加锁保护;


关于string对象使用的思考:

在一般的程序设计中,字符串变量能用string的基本上都用了,而能够用char[]的基本上都没有用,虽然string用起来比较方便点,但是,导致:

1.程序运行效率实际上一般会有所降低.

2.内存破坏的时候(野指针为主),很难查到蛛丝马迹.

    特别是第2项,会严重威胁到进程的稳定可靠性.所以一般能用char[]的就尽可能用,而尽可能不用string,特别是结构体对象,希望每个结构体不到万不得已不要出现string对象,实际上,不能memset(this,0,sizeof(*this))的结构体都要仔细考虑,为什么不行,能不能改为可行?

    内存破坏导致进程死机的情况,我们估计大部分都是野指针导致的,特别是结构体野指针导致.假设搞破坏的结构体里面有字符串,且是char[]存储,这个对象搞了破坏之后,被破坏的地方在死机的时候,一般都很容易根据破坏者的蛛丝马迹找到它,进而修正野指针错误,而如果换作string对象就非常困难.原因就是字符串内容很容易看出来是谁,而带有char[]的破坏者往往其char[]的内容在野指针时仍然是看得到的,但是,破坏者的string对象在野指针时是无法查看了.

    结构体采用string vector是偷懒的办法,使用起来是相对方便点.但为了结构体内存的独立性和单纯性,一般还是尽可能少用.string尽可能用char[]代替,vector尽可能用[]数组代替.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值