none
g++ -O2 引发的崩溃,debug正常,,vs一样的情况! RRS feed

  • 问题

  • 我在做一个内存消息队列,目前的情况是如题,崩溃是我自己引发的throw,因为运行的结果不大对。

    下面我先上数据添加的代码,就是把数据写入到队列,这个写入是多线程下的,一共2段核心代码:

    template<class ELEMENT>
    		bool Add(const ELEMENT& element)
    		{
    			class Safe
    			{
    			public:
    				Safe(std::atomic<__uint32_>* ptr) :_ptr(ptr){ ++(*_ptr); }
    				~Safe(){ --(*_ptr); }
    			private:
    				std::atomic<__uint32_>*_ptr;
    			};
    
    			// 如果检查准备读取数据,则不能再写入,否则可能造成崩溃或者数据丢失
    			// 如果开始扩容,则禁止再写入数据
    #define CHECK_ADD_CONDITION				\
    			if (_is_read || _is_extend)	\
    			{							\
    				return false;			\
    			}
    
    			CHECK_ADD_CONDITION
    
    			Safe safe(&_writing_queue_size);
    
    			CHECK_ADD_CONDITION
    
    			return Add_(element);
    		}

    template<class ELEMENT>
    		bool Add_(const ELEMENT& element)
    		{
    			if ((!_is_able_extension) && (_queue_size >= _max_queue_size))
    			{
    				// 队列已满
    				return false;
    			}
    
    			if (_is_able_extension && 
    				(_queue_size + _RESIZE_CONDITION >= _max_queue_size) && (!_is_extend))
    			{
    				if (CheckExtend() && _is_extend)
    				{
    					// 可以扩容
    					// 不能再插入,因为万一每次都扩容失败,那么到最后,肯定会超出容器范围
    					// return false;
    				}
    			}
    
    			// test
    			if (!_writing_queue_size)
    			{
    				using namespace std;
    				cout << "*** _writing_queue_size is " << _writing_queue_size << endl;
    			}
    
    			if (_is_read)
    			{
    				// 正在读取数据
    				return false;
    			}
    
    			// test
    			if (_is_test && _is_read)
    			{
    				using namespace std;
    				cout << "_is_test is " << _is_test << " and _is_read is " << _is_read << endl;
    				assert(false);
    				throw;
    			}
    
    			_queue[_queue_size++] = element;
    			++g_queueunit_input_count;
    			return true;
    		}

    ok,上面就是数据写入到队列的代码,// test下面的代码为测试代码。

    ---------

    下面再传一段代码,这段代码为读取代码,在读取的时候,程序保证过程是单线程的!

    // @brief 如果要读取队列中的数据,那么你需要先调用此方法,一直到此方法返回true
    		// 才能开始真正的读取操作.这是一个阻塞操作。
    		bool ReadyRead()
    		{
    			if (!_queue_size)
    			{
    				// 没有可以读取的内容
    				return false;
    			}
    
    			_is_read = true;
    
    			// 每次睡眠1ms,当满足WAIT_READ_READ_TIME ms时,调用失败
    #define WAIT_READ_READ_TIME 200
    			__uint32_ elapse = 0;
    
    			// 等待写入完成 || 或者执行扩容完成
    			while ((__uint32_)_writing_queue_size || _is_extend)
    			{
    				MESSAGE_QUEUE_SLEEP(1);
    				elapse += 1;
    
    				if (elapse >= WAIT_READ_READ_TIME)
    				{
    					return _is_read = false;
    				}
    			}
    
    			// test
    			__uint32_ val1 = _queue_size;
    			// test
    			_is_test = true;
    			g_queueunit_output_ready_count += _queue_size;
    			// test
    			__uint32_ val2 = _queue_size;
    			// test
    			if (val2 != val1)
    			{
    				//assert(false);
    				using namespace std;
    				cout << "!!!R val1 is different from val2. val1 is " << val1 << ", val2 is " << val2 << " and _is_read is " << _is_read << endl;
    			}
    
    			return _is_read;
    		}

    // test下面的为测试代码。

    现在描述下环境和问题所在。

    这三段代码是核心队列的一部分,这个队列是用的vector + 原子操作,也就是预先分配resize,然后一个个[]去赋值的。

    这3段代码描述的是写和读,写和读一次只能有一种操作, 也就是同一时间,要么只能读,要么只能写,不能读写同时进行。

    读取的线程和写的线程是不一样的。分开的。

    现在是当读取的时候,_is_read就被设置为true,并且等待正在写入的数据写入完毕,然后再写数据的函数中,if检查_is_read为true的话,就不在写入数据,而是写数据函数返回false的这样一个过程。

    现在通过release编译,使用选项-O2/-O3都可以,编译后运行程序,程序在:

    			// test
    			if (_is_test && _is_read)
    			{
    				using namespace std;
    				cout << "_is_test is " << _is_test << " and _is_read is " << _is_read << endl;
    				assert(false);
    				throw;
    			}
    

    经常throw,我不能理解,优化了什么东西导致我的逻辑出现错误。求指导,谢谢!

    2015年6月16日 13:11

全部回复

  • 你是说debug编译是可以的,release编译就出现这个问题吗?不太清楚你的场景是怎么样的。您给出的几段代码也很难理解你的项目是否有逻辑问题。考虑有没有什么机会_is_test 和 _is_read都为true,/O2优化应该不会改变你的C/C++程序的逻辑吧。不知道你是怎么初始化和使用_is_test 和 _is_read参数的,如果可能的话,也许你能在一个简单的测试项目中重现这个问题,分享你的测试代码可能让我们更好理解你的场景。

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    2015年6月17日 7:05
  • 你好,非常感谢你能关注我的问题,这个问题我已经困扰了三天多了:

    链接:http://pan.baidu.com/s/18pv22 密码:vgtn

    这是代码的地址,百度云!

    代码使用boost库,其中使用了线程相关的和bind/function,这些在std中应该都是有的,如果你没有boost库,可以相应替换掉,谢谢,给你带来麻烦了!

    boost157!

    代码在windows/linux下都可以编译运行。

    代码d版本正常,因为要在20秒内操作4000w数据(目前代码里设置的测试量),所以d版本运行时是处理不完这些数据的。但是r版本可以处理完毕,但是会有数据丢失,为了避免数据丢失和方便测试,我在代码里加入了throw,所以用r版本运行多次就会出现throw的情况,几率非常高,如果提示出现:input  thread exit 则可以重新跑了!

    	typedef std::shared_ptr<Obj> __tmp_obj;
    	auto func = [](){
    		__tmp_obj obj = std::make_shared<Obj>();
    		const int EXEC_COUNT = 10000000;
    		for (int i = 0; i < EXEC_COUNT; ++i)
    		{
    			if (!mq.Add((HandleFunc)(&Execfunc::FuncImpl), obj, &exec_func)){
    				//cout << "add data failed.\n";
    				MESSAGE_QUEUE_SLEEP(1);
    				if (!mq.Add((HandleFunc)(&Execfunc::FuncImpl), obj, &exec_func)){
    					cout << "add data failed.\n";
    				}
    			}
    		}

    上面的一段代码在main.cpp,你可以通过修改EXEC_COUNT的值,来修改输入的数据量!

    再次感谢你能帮助我!谢谢


    • 已编辑 oiooooio 2015年6月17日 8:42
    2015年6月17日 8:39
  • 我现在发现了新大陆,但是我解决不了。我还是不知道问题出在哪里。

    因为我使用的是虚拟环境的linux,也就是vmware里的虚拟机,我现在换了一个linux环境,还是ubuntu14,之前用的是desktop版本的,现在是server版本的。

    下面先上代码,在说问题:

    		template<class ELEMENT>
    		bool Add(const ELEMENT& element)
    		{
    			class Safe
    			{
    			public:
    				Safe(boost::atomic<__uint32_>* ptr) :_ptr(ptr){ (*_ptr).fetch_add(1); }
    				~Safe(){ (*_ptr).fetch_sub(1); }
    			private:
    				boost::atomic<__uint32_>*_ptr;
    			};
    
    			// 如果检查准备读取数据,则不能再写入,否则可能造成崩溃或者数据丢失
    			// 如果开始扩容,则禁止再写入数据
    #define ADD_CHECK_CONDITION	\
    			if (_is_read || _is_extend)		\
    			{								\
    				return false;				\
    			}
    
    			ADD_CHECK_CONDITION
    			Safe safe(&_writing_queue_size);
    			ADD_CHECK_CONDITION
    
    			// test
    			if (_writing_queue_size.load() == 0)
    			{
    				using namespace std;
    				while (true)
    				{
    					if (_writing_queue_size > 0)
    					{
    						cout << "[nMessageQueue.QueueUnit.Add]_writing_queue_size is " << _writing_queue_size << endl;
    						assert(false);
    						break;
    					}
    					MESSAGE_QUEUE_SLEEP(10);
    					cout << "[nMessageQueue.QueueUnit.Add]_writing_queue_size is " << _writing_queue_size << endl;
    				}
    			}
    
    
    			return Add_(element);
    		}


    在linux下运行测试,debug版本, makefile的编译选项是:

    CXXFLAGS=-Wall
    ifeq ($(DR),release)
    	CXXFLAGS=-O3
    endif
    CXXFLAGS+=-g -std=c++11


    运行时,有时候会走到//test下面的if里面。

    这里的奇怪执行可能跟我另一处的测试代码有关系,我另一处的测试代码是先++_writing_queue_size再--的,所以在我认为,不管怎么样,当执行完毕:

    			ADD_CHECK_CONDITION
    			Safe safe(&_writing_queue_size);
    			ADD_CHECK_CONDITION

    这里的代码后,_writing_queue_size的值始终应该是大于0 的,不管是有多少个线程。即便我另一处的测试有影响,那应该也是大于1的吧? 我不明白这里为什么会走到if里面。

    这段代码是数据添加过程。

    -------------------

    另外一处代码:

    		bool ReadyRead()
    		{
    			// test
    			if (_is_read)
    			{
    				using namespace std;
    				assert(false);
    			}
    
    			if (!_queue_size)
    			{
    				// 没有可以读取的内容
    				return false;
    			}
    
    			_is_read = true;
    
    			// 每次睡眠1ms,当满足WAIT_READ_READ_TIME ms时,调用失败
    #define WAIT_READ_READ_TIME 200
    			__uint32_ elapse = 0;
    
    			// 等待写入完成 || 或者执行扩容完成
    			// 这里如果把while提前则会出现问题,问题是_writing_queue_size在while检查的
    			// 时候是0, 如此一来便会直接跳过while,当执行完毕while后,继续往下执行时,
    			// _writing_queue_size可能会变的非0,这里的原因一直没有找到。如果_writing_queue_size
    			// 非0,则表示肯定有数据正在插入,这时候如果还继续插入数据,则会造成数据丢失,
    			// 如果vec缓冲区的_queue_size正好指向最后一个元素,则可能会引起程序崩溃!s
    			__uint32_ val_before_while = _writing_queue_size;
    			{
    				MESSAGE_QUEUE_SLEEP(1);
    				elapse += 1;
    
    				if (elapse >= WAIT_READ_READ_TIME)
    				{
    					return _is_read = false;
    				}
    			}while (_writing_queue_size > 0 || _is_extend);
    
    			// test
    			__uint32_ val = _writing_queue_size;
    			if (_writing_queue_size.load() > 0)
    			{
    				using namespace std;
    				cout << "_writing_queue_size is " << _writing_queue_size << endl;
    				cout << "[nMessageQueue.QueueUnit.ReadyRead] val is " << val << endl;
    				cout << "[nMessageQueue.QueueUnit.ReadyRead] val_before_while is " << val_before_while << endl;
    				assert(false);
    			}
    
    			// test
    			if (!_is_read)
    			{
    				using namespace std;
    				assert(false);
    			}
    
    			// test
    			_is_test = true;
    			// test
    			__uint32_ val1 = _queue_size;
    			g_queueunit_output_ready_count += _queue_size;
    			// test
    			__uint32_ val2 = _queue_size;
    			// test
    			if (val2 != val1)
    			{
    				//assert(false);
    				using namespace std;
    				cout << "!!!R val1 is different from val2. val1 is " << val1 << ", val2 is " << val2 << " and _is_read is " << _is_read << endl;
    			}
    
    			return _is_read;
    		}


    这段代码是数据读取过程。值得注意的是:这段代码里的while循环跟百度云的代码不一样,百度云的代码的while是while(){},而这里的while是{}while();也就是这里的while必须先睡眠1ms再执行检查。这前后有2中情况:

    第一种情况是while(){}这种,这种一开始就检查条件,判断检查_writing_queue_size为0并且_is_extend为false的时候直接跳过循环,但是问题来了,while检查的时候,_writing_queue_size为0,跳过while的时候,_writing_queue_size就不为0。就会走到下面的一段代码:

    			// test
    			__uint32_ val = _writing_queue_size;
    			if (_writing_queue_size.load() > 0)
    			{
    				using namespace std;
    				cout << "_writing_queue_size is " << _writing_queue_size << endl;
    				cout << "[nMessageQueue.QueueUnit.ReadyRead] val is " << val << endl;
    				cout << "[nMessageQueue.QueueUnit.ReadyRead] val_before_while is " << val_before_while << endl;
    				assert(false);
    			}

    这段代码在我这里的打印是:

    _writing_queue_size is 0

    [nMessageQueue.QueueUnit.ReadyRead] val is 1

    [nMessageQueue.QueueUnit.ReadyRead] val_before_while is 0

    我依然不知道为什么会是这样的结果~~~!!不明白问题在哪~~!!!

    第二点 {}while(),这种情况,就是上面代码的情况,先睡眠1ms,再去执行时,则没有测试出来那些奇怪的问题,一个都没有出现~~! 这种方法应该是治标不治本。

    跪求指点!谢谢


    • 已编辑 oiooooio 2015年6月18日 9:05
    2015年6月18日 9:04
  • soga,,我不知道有这玩意啊~~~!

    一般你们搜索3rd lib怎么搜索的,~~! 谢谢,~~

    不过可以的话,我还是想知道我的这个问题所在!

    2015年6月18日 12:53
  • 好吧,谢谢,
    2015年6月18日 15:01