C++线程安全的对象生命周期管理

       C/C++的指针一直是令人又爱又恨的特性。围绕指针产生了许许多多优雅的数据结构和系统实现,但又滋生了不少“脑细胞杀手”——内存Bug。C/C++指针问题(空指针、野指针、垂悬指针)的根本原因其实是,当你获得一个指针时是无法判断这个指针所指向的地址是否保存着一个有效的对象。如果每个指针都绑定着这么一个方法,IsValid(),用于判断指向的对象是否有效,那该多好。幸运的是,C++11给我们带来了与之类似的解决方案——shared_ptr/weak_ptr(共享指针/弱指针)。

shared_ptr/weak_ptr 简述

       shared_ptr 是引用计数型智能指针,每个 shared_ptr 副本都指向相同的内存对象,当最后一个 shred_ptr 副本析构时,对应的内存才会被释放。[1]

std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // 都指向同一内存.

p1.reset(); // 因为p2还在,所以内存没有释放.
p2.reset(); // 释放内存, 因为没有shared_ptr指向那块内存了.

由于 shared_ptr 使用引用计数,所以存在“循环引用”问题——当两个不同对象的内部互相拥有对方的共享指针时,会导致这两个对象永远无法自动释放。而 weak_ptr 就是被设计出来打破这种循环引用的。weak_ptr 是只引用,不计数的。当最后一个 shared_ptr 被析构时,不管有没有 weak_ptr 引用该对象,该对象都会被自动释放。

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // 还是只有p1有所有权.

{
  std::shared_ptr<int> p2 = wp1.lock(); // p1和p2都有所有权
  if(p2) // 使用前需要检查
  { 
    // 使用p2
  }
} // p2析构了, 现在只有p1有所有权.

p1.reset(); // 内存被释放.

std::shared_ptr<int> p3 = wp1.lock(); // 因为内存已经被释放了, 所以得到的是空指针.
if(p3)
{
  // 不会执行到这.
}

weak_ptr 不仅可以打破循环引用,更重要的是拥有原子性(线程安全)的 lock() 方法,获取其引用对象的 shared_ptr。合理的使用 shared_ptr/weak_ptr,我们就可以在避免循环引用问题的同时,达到前述的目标—— 判断指针所指向的对象是否有效。下面以我工程实践过程中遇到的一个问题的简化版为例,说明如何使用 shared_ptr/weak_ptr 实现线程安全的对象生命周期管理。

一个神奇的 core dump

       这是一个2006年上线的遗留系统,负责上层业务系统和外省节点之间的省间业务消息组包/解包和转发。近年来,随着业务量的不断上升,以及外省节点的不断性能升级改造,渐渐的该系统在业务峰值时段频繁出现业务拥堵的情况。重构系统之后,性能有了显著的提高,可是随之而来的是一个消息包处理过程中一个神奇的 core dump。简化如下:

std::string sSessionId("123456");
Session *pSession = NULL;
SESSION_DB()->Lookup(sSessionId, pSession));

if(pSession)
{
	pSession->OperateA();  // 可能是这里 core dump
		…………
	pSession->OperateB();  // 也可能是这里 core dump
}

在获取存储在全局 SESSION_DB 中的 Session 对象后,在顺序执行的代码段中,偶发性的出现调用对象方法时 core dump。当时就推测是对象在处理过程被其它线程给释放了,查找日志后证明了自己的推测。该 Session 对象在 OperateA() 操作之后,操作线程被挂起,在 OperateB() 操作之前,该 Session 被超时处理线程给释放了,引起 core dump。这种场景下有两种常用的解决方案:

  1. ​实现过程中保证 Session 的一个实例,有且仅有一个线程持有。调用 SESSION_DB()::Lookup 取出相应的 Session 之后,该 Session 就从 SESSION_DB 中删除。
  2. Session 的一个实例可以多个线程共享。调用 SESSION_DB()::Lookup 取出 Session 之后,该 Session 依旧在 SESSION_DB 中。后续需要的线程可以继续从 SESSION_DB 中取用该 Session。

方案1可以满足一些简单的业务场景,如 Session 仅需做一次业务处理,不存在前后的 Session 业务关联。但是现实中的业务 Session 处理都会更加复杂,Session 请求/响应的关联,同时还要处理 Session 超时等等。如何在超时处理线程清理了 Session 的同时,保证该 Session 在业务处理线程依旧有效?我们需要有一种线程安全的方案2实现来解决这些问题。

用 shared_ptr/weak_ptr 的实现方式

       首先,为保证 Session 的可重复取用,SESSION_DB 内部保存的应该是 Session 的 shared_ptr。其次,为了防止可能出现的“循环引用”问题,以及保证“线程安全”的使用 Session,取用 Session 的时候应该获取 Session 的 weak_ptr。在进行业务处理前,使用 weak_ptr::lock() 提升为 shared_ptr ,保证处理过程中 Session 的有效性。简易实现如下:

/**
 * @file Session.h
 */

#ifndef SESSION_H_
#define SESSION_H_

#include <iostream>
#include <string>

using namespace std;

class Session
{
public:
	Session(const string &_sessionId)
		: m_sSessionId(_sessionId) {}

	void doSomething()
	{
		cout << "My session id : " << m_sSessionId << endl;
	}

private:
	Session();

	string m_sSessionId;
};


#endif /* SESSION_H_ */
/**
 * @file SessionDB.h
 */

#ifndef SESSIONDB_H_
#define SESSIONDB_H_

#include <iostream>
#include <string>
#include <utility>
#include <boost/smart_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace boost;

class Session;

typedef shared_ptr<Session> SessionSharedPtr;
typedef weak_ptr<Session> SessionWeakPtr;
typedef unordered_map<string, SessionSharedPtr> SessionMap;

class SessionDB
{
public:
	bool Add(const string &_sessionId, SessionSharedPtr _session);

	bool Lookup(const string &_sessionId, SessionWeakPtr &session_);

	bool Remove(const string &_sessionId);

private:
	SessionMap m_cSessionMap;
	boost::mutex m_cSessionMapMutex;
};

class Global
{
public:
	static SessionDB m_cSessionDB;
};

#define SESSION_DB() Global::m_cSessionDB

#endif /* SESSIONDB_H_ */
/**
 * @file SessionDB.cpp
 */

#include "Session.h"
#include "SessionDB.h"

bool SessionDB::Add(const string &_sessionId, SessionSharedPtr _session)
{
	boost::mutex::scoped_lock cLock(m_cSessionMapMutex);

	try
	{
		pair<SessionMap::iterator, bool> cInsertResult
			= m_cSessionMap.insert(make_pair(_sessionId, _session));
		if(cInsertResult.second)
		{
			return true;
		}
	}
	catch(...)
	{
		cout << "SessionDB::Add, catch unknown exception." << endl;
	}

	return false;
}

bool SessionDB::Lookup(const string &_sessionId, SessionWeakPtr &session_)
{
	boost::mutex::scoped_lock cLock(m_cSessionMapMutex);

	try
	{
		SessionMap::iterator iter = m_cSessionMap.find(_sessionId);
		if(iter != m_cSessionMap.end())
		{
			session_ = static_cast<SessionWeakPtr>(iter->second);
			return true;
		}
	}
	catch(...)
	{
		cout << "SessionDB::Lookup, catch unknown exception." << endl;
	}

	return false;
}

bool SessionDB::Remove(const string &_sessionId)
{
	boost::mutex::scoped_lock cLock(m_cSessionMapMutex);

	try
	{
		if(m_cSessionMap.erase(_sessionId))
		{
			return true;
		}
	}
	catch(...)
	{
		cout << "SessionDB::Remove, catch unknown exception."  << endl;
	}

	return false;
}

SessionDB Global::m_cSessionDB;
/**
 * @file main.cpp
 */

#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include "Session.h"
#include "SessionDB.h"

using namespace std;
using namespace boost;

void WorkFunction(string _sessionId)
{
	SessionWeakPtr wpSession;
	SESSION_DB().Lookup(_sessionId, wpSession);

	// 提升在前,移除在后
	SessionSharedPtr spSession = wpSession.lock();  // 线程安全的提升操作

	sleep(3);
	if(spSession)  // 此处,Session 早已从 SESSION_DB 移除
	{
		spSession->doSomething();
	}
	else
	{
		cout << "WorkFunction, Session removed." << endl;
	}
}

void TimerFunction(string _sessionId)
{
	sleep(1);
	SESSION_DB().Remove(_sessionId);
}

int main(int argc, char *argv[])
{
	string sSessionId("0");
	SessionSharedPtr spSession(new Session(sSessionId));
	SESSION_DB().Add(sSessionId, spSession);
	spSession.reset();

	thread workThread(bind(&WorkFunction, sSessionId));
	thread timerThread(bind(&TimerFunction, sSessionId));

	workThread.join();
	timerThread.join();

	return (0);
}

从代码[3]可以看到,尽管 Session 已经从 SESSION_DB 中移除,由于工作线程在 Session 移除之前已将 weak_ptr 提升为 shared_ptr,那么该 Session 在工作线程操作过程中一定是有效的。shared_ptr/weak_ptr 的方案,有效的杜绝了内存泄露和无效指针的问题,是一种行之有效的线程安全的对象生命周期管理方法。

参考

[1] 智能指针 shared_ptr/weak_ptr,wikipedia,http://zh.wikipedia.org/wiki/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88#shared_ptr_.E5.92.8C_weak_ptr

[2] 当析构遇到多线程——C++中线程安全的对象回调,陈硕,http://blog.csdn.net/Solstice/article/details/5238671

[3]完整源码+Makefile见:https://github.com/lostaway/Woody-Ye-s-Code-Share/tree/master/CPlusPlusThreadsafelyManageObjectLifecycle

编辑记录:

       增加示例代码 Github 地址,2014-07-27

       初稿,2014-07-13

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值