一个子功能设计的思维过程

花了三天实现了一个子功能设计,描述如下:从硬盘中搜索一个时间段抓拍的图片(jpeg格式),并在GUI上列一个表格显示所抓拍的图片的条目,当鼠标置于form表格上某个条目的上方,该抓拍图片要在GUI窗口中显示出来。

本来流程很简单,但我用单线程模型实现后,发现鼠标在移动的过程中特别卡,后来测试了下jpeg图片解码的时间,居然有100ms左右,所以鼠标卡也就正常了。

我的解决方法是,启一个解ma线程,与GUI线程独立开来。如图所示:GUI线程通过响应鼠标事件,将form条目中的jpeg图片路径传递给解码线程,解码线程读取路径并解码该文件,将解码后的数据存储到一块buffer上,再由GUI去读数据,并显示该图片。

                                                                          



当然,jpeg图片路径和解码数据都加了锁的。
但是,测试后发现显示的图片顺序是乱的,后来分析了一下,解码写buffer 和GUI 读buffer是两个独立的流程,当GUI读buffer时,很可能该条目正在解ma,所读的buffer只不过是上一次解ma后的数据。

有没有很好的方法可以让GUI正确显示该条目的解码数据呢,这看起来是一个同步问题,实则不是。因为我不能让GUI线程去等待解码线程解出数据并显示,因为这样做,开解码线程就是多此一举。我要求GUI线程是非阻塞的。

最后的解决方案是,在解码线程里面加了一个解码状态(解码中、解码完成),由GUI线程开一个定时器每隔1s判断这个状态一次,如果解码完,则在定时器处理函数内读取buffer数据并刷新控件,否则显示Loading Picture...

之于GUI的定时器,外行人可能不太理解。因为GUI一般都是事件驱动的,比如鼠标触发、网络信息到达、定时器等。如果同时有几种事件到达,那么该响应哪个呢?很显然对事件源的并发处理是GUI的核心功能之一。既然是并发,就不难理解定时器响应处理函数时,鼠标事件依然可以进行。


延伸:

不知道大家是怎么设计多线程的程序,在此之前我没有多线程的经验。但经过这两天的摸索,有一个非常好的多线程思路和大家分享。

作为C++的迷恋者,我很乐意把线程设计成为一个类,网上有很多有关线程基类的源码,大致是这样:

#ifndef THREAD_H_
#define THREAD_H_

namespace Common
{

typedef enum
{
	THREAD_STATUS_START = 0,
	THREAD_STATUS_RUNNING = 1,
	THREAD_STATUS_STOPPING = 2,
	THREAD_STATUS_STOPPED = 3,
} EnumThreadStatus;

class CThread
{
public:

	CThread();

	virtual ~CThread();

private:

	virtual void *ThreadFuncImpl(void) = 0;

	virtual void PreBeginThread(void);

	virtual void PostExitThread(void);

	static void *ThreadRoutine(void *param);

public:

	void Start(int stacksize = 0);

	void Stop();

	void InternalStop();

private:

	volatile EnumThreadStatus m_RunningStatus;

	bool m_InternalStop;
};

}

#endif /* THREAD_H_ */

我们只需继承这个线程基类,并实现线程处理函数ThreadRoutine(void *param)即可。当然,既然是线程,构造器不能够公开,只需提供一个单例模式的接口就可以了。

比如下面这个:

class MyThread::Public CThread
{
	protected:
		MyThread();
		virtual ~MyThread();
		static MyThread *m_pThis;
	private:
	 	virtual void *ThreadFuncImpl();
	public:
		static MyThread * GetInstance()
		{
			if(!m_pThis)
			{
				m_pThis = new MyThread();
			}

			return m_pThis;
		}

		static void DelInstance()
		{
			if(m_pThis)
			{
				delete m_pThis;
				m_pThis = NULL;
			}
		}
		
};

因为仅仅是举例,所以这里面并没有涉及业务数据及处理。但对单例模式的理解真的很重要。
最后,你只需在主线程里面这样启动子线程:MyThread::GetInstance();
简单吧?
另外,处理多线程难免遇到加锁的情况,如果锁太多,很容易被隐藏的复杂流程(特别是N多线程)造成死锁,这里我们可以采用一个锁类简化加锁的处理:

#ifndef __AUTOCRI_H__
#define __AUTOCRI_H__
#include <pthread.h>

class CAutoCri
{
public:
	CAutoCri( pthread_mutex_t& cs )
	{
		m_pCs = &cs;
		pthread_mutex_lock(m_pCs);
	}
	
	~CAutoCri()
	{
		Release();
	}
	
/*protected:
	CAutoCri();
	CAutoCri(const CAutoCri& cri);
	CAutoCri& operator= (const CAutoCri& cri);
	*/
public:	
	bool Release()
	{
		if( m_pCs )
		{
			pthread_mutex_unlock(m_pCs);
			m_pCs = NULL;
			return true;
		}
		return false;
	}

private:
	pthread_mutex_t* m_pCs;
};
#endif

尼玛,CSDN的编辑器有木有这么2 啊,排版比游戏论坛都差~~~ 

下班,回去咯~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值