自己实现多线程 2009年12月25日

转自 http://zeze0556.tk/2009/12/25/自己实现多线程

 

在开发BREW程序的时候,SDK中包含了线程(我记得是3.1以上)模型,但不推荐使用。而移植的很多内容,都使用了线程。由于BREW也不允许使用静 态变量,死循环或者嵌套很深的占用大量时间的运算,如果要随时暂停的话,只能把变量临时存储起来,然后一层一层的往外退。首先,修改的话比较困难,出错的 几率很大,其次,每次恢复和暂停都需要占用时间,进行额外的操作,影响效率。这个时候,线程就起作用了,不是不推荐使用么,我们就自己实现一个。

先贴代码,ARM部分的,一个日本人写的,我很早之前就明白其中的做法,由于不能拿手机随便整(整坏了赔不起)。幸好别人已经做了,安全性瞬间提高100%。同时,我稍加修改

01          CODE32
02      AREA ||i.StartThread||, CODE, READONLY
03      EXPORT StartThread
04 StartThread PROC
05      STMFD sp!, {r0-r12, lr}
06      ADD r4, r0, #4
07      SWP sp, sp, [r4]
08      MOV r5, #1
09      STR r5, [r0, #8]
10      STR r0, [sp, #-4]!
11      MOV lr, pc
12      BX r1
13  
14      LDR r0, [sp], #4
15      MOV r5, #0
16      STR r5, [r0, #8]
17      ADD r4, r0, #4
18      SWP sp, sp, [r4]
19      LDMFD sp!, { r0-r12, lr }
20      MOV pc, lr
21      ENDP
22  
23      CODE32
24      AREA ||i.SwitchThread||, CODE, READONLY
25      EXPORT SwitchThread
26 SwitchThread PROC
27      STMFD sp!, {r0-r12, lr}
28      ADD r4, r0, #4
29      SWP sp, sp, [r4]
30      LDMFD sp!, {r0-r12, lr }
31      MOV pc, lr
32      ENDP

说明:
实现两个接口函数,StartThread和SwitchThread,编译时使用armasm编译,调用按照C函数的方式调用(原因可能在以后的文章中会提到的)
先看函数接口的声明:

1         extern "C" void StartThread( void *, void * /*(*callback)( void* )*/ );
2 extern "C" void SwitchThread( void * );

对于StartThread函数,传递两个指针,第一个传递的是线程结构的内存指针,第二个是调用函数的内存地址,为什么不声明的更明了一些。最后就会明白的。
StartThread的那十几行做了什么事?
其实很简单,它从第一个指针(线程结构体中)得到堆栈的栈顶,然后将栈指针指向栈顶,接着,调用回调函数,回调函数调用结束之后,返回到调用 StartThread函数的上一级函数,接着执行。看上去仅仅是普通的调用,仅仅进行了栈指针的交换而已。是的,就是这么回事。
对于SwitchThread函数,就更简单了,保存几乎所有关键的寄存器,然后交换栈指针,然后读取被保存的寄存器(不是刚刚被保存的,因为栈已经被切换了),然后返回。
线程?就这样?是的,这就是最简单的线程,最核心的代码。总共32行,包含注释和无用的内容。一个模拟的环境进行一个模拟的调用:
1、外部函数调用StartThread函数
2、StartThread首先保存栈(stack1),设置栈指针到自定义堆栈(stack2),然后开始调用回调。
3、如果回调函数中没有需要暂停的内容,直接返回,执行StartThread中BX之后的语句,StartThread正常返回。否则,继续。
4、回调函数暂停时,触发SwitchThread函数,SwitchThread函数先保存寄存器内容到栈中(stack2),然后切换栈。
5、恢复保存的栈,对于第一次执行,使用的栈是StartThread中保存的栈(stack1)。
6、使用新的栈继续执行,对于第一次执行,由于已经恢复了栈,那么,继续的调用,将会返回到调用StartThread函数的上一级函数。
7、如果要继续之前被暂停的线程,重新调用SwitchThread函数,然后会切换栈,跳转到5。
8、如果函数正常返回,则返回到调用回调函数的上一级函数。
很混乱?是的,我在写的时候也是很混乱,我的建议是,看完下面的代码,然后用一个简单的图例画出来,就会发现很简单的。我不画是因为在emacs下,我的 这一技能还未学会。下面是线程模型的Thread模板,我不但要使用自己的栈,还要能定义它的栈大小,简单的来说,我不想让每个线程的栈都是一样的大小。

       class Runnable

{

public:

virtual ~Runnable() {}

virtual void run() = 0;

virtual void resume() = 0;

virtual void suspend() = 0;

};



extern "C" void StartThread( void*, void* /*(*callback)( void* )*/ );

extern "C" void SwitchThread( void* );



template< int LENGTH >

class Thread : public Runnable

{

public:



Thread() :_pStack( NULL ), _bSuspend( false ), _bExecute( 0 )

{

DBGPRINTF("LENGTH=%d", LENGTH);

}



virtual ~Thread(){};



virtual void start(){

if( isExecute() ) return;

DBGPRINTF("LENGTH=%d", LENGTH);

_pStack = &_stack[LENGTH];

StartThread( this, (void*)(&StartCallback) );

}



virtual void suspend()

{

if( isSuspend() || !isExecute() ) return;

_bSuspend = true;

SwitchThread( this );

}



virtual void resume()

{

if( !isSuspend() ) return;

_bSuspend = false;

SwitchThread( this );

}



virtual bool isExecute()

{

return ( _bExecute != 0 );

}



virtual bool isSuspend()

{

return _bSuspend;

}

private:

byte* _pStack;

int _bExecute;

byte _stack[LENGTH];

bool _bSuspend;



static void StartCallback( Thread< length >* pThread )

{

pThread->run();

}

};

上面是线程的全部结构,简单的来说,为什么要返回上上级函数(我嘴巴没有打结)。主要是基于栈平衡和实现简单性的考虑,虽然有些时候,我较容易的写汇编代 码,但我还是很想尽可能少的写,这样烦人的事情就会少很多,同时少了很多为什么和进行修改也会很少了,而不用每次都要我亲自动手。
使用模板来确定Stack的大小,为什么不动态分配?因为它不能和其他参数保存在同一个相对地址,对于确定更加麻烦,更加容易出错,我试过,我不能用简单 的32行代码来做相同的事情。模板有什么好处,可以确定栈的大小的同时,相对于Thread的指针地址来说,它的地址是固定的,而且,不用考虑内存释放, 只用把Thread释放之后,Stack数组也自然释放,即使是基于寻址来说,也比动态分配要高效。简单,高效,不易出错。何乐而不为?
上面是模板的好处,下面就是不要的地方了,也不是很重要,声明的StartThread和SwitchThread函数的参数,变成了两个void指针,难看。
将Runnable和Thread分开,只是我想或许我会试试其他的做法也不一定。
调用的例子:

01    class Graphics;
02 class test_Thread_Class: public Thread< 10*1024 >
03 {
04 public :
05          static const int THREAD_INIT = 0;
06          static const int THREAD_RUN = 1;
07          static const int THREAD_PAUSE = 2;
08          static const int THREAD_QUIT = 3;
09          virtual void run();
10           void quit();
11           int getState();
12          int getValue();
13          void setState( int state);
14          void draw(Graphics* g, int x, int y, int width, int height, int color);
15          int state;
16          int value;
17 };
18 void test_Thread_Class::run()
19 {
20          state = THREAD_RUN;
21          while ( true ) {
22  
23                  for ( int i = 0; i < 100; i++) {
24                          value = i;
25                          DBGPRINTF( "state=%d" , state);
26                          if (state == THREAD_QUIT)
27                                  return ;
28                          suspend();
29                  }
30          }
31 }
32  
33 int test_Thread_Class::getValue()
34 {
35          return value;
36 }
37 int test_Thread_Class::getState()
38 {
39          return state;
40 }
41 void test_Thread_Class::setState( int state)
42 {
43          this ->state = state;
44 }
45 void test_Thread_Class::quit()
46 {
47          state = THREAD_QUIT;
48 }
49  
50 void test_Thread_Class::draw(Graphics* g, int x, int y, int width, int height, int color)
51 {
52          g->setColor(Graphics::getColorOfRGB((color&0xFF0000)>>16, (color&0xFF00)>>8, color&0xFF));
53          g->fillRect(x, y, width, height);
54          g->setColor(Graphics::getColorOfRGB(0xFF, 0x0, 0x0));
55          g->drawString((String( "" )+value).c_str(), x + 4, y+4, Graphics::LEFT|Graphics::TOP);
56 }

test_Thread_Class类使用地方法:
初始化:

1 Runable* thread = new test_Thread_Class();

如果你想将Start和以后的执行分开的话,可以先调用thread->start(),然后在其他的地方只用调用 thread->resume()就可以了。不用调用suspend()是因为在run()函数的内部调用,而不是外部调用,suspend()和 resume()调用要一一对应(除非删除线程)。如果你不关心这些,和我一样,每帧都让它执行,可以进行简单的这样调用:

1 if (!( thread ->isExecute()))
2 thread ->start();
3 else
4 thread ->resume();

如果线程执行完毕,让它重新执行。
上面的线程核心都是手机上可以执行的代码,理论上来说,你可以移植到任何ARM平台,因为和系统完全没有关系。我们大部分的开发,刚开始的时候都是在 x86的模拟器上的。我知道你想要的是什么,下面的就是StartThread和SwitchThread的x86的代码,这下完美了。

01    .386P
02 .MODEL FLAT, STDCALL
03          StartThread PROTO C  :D WORD :D WORD
04 _TEXT   SEGMENT
05 StartThread PROC C pthis: DWORD , pfun: DWORD
06          mov eax, pthis
07          mov DWORD PTR [eax+8], 1
08          pushad
09          xchg esp, [eax + 4]
10          push pthis
11          call pfun
12          pop eax
13          mov DWORD PTR[eax+8],0
14          xchg DWORD PTR [eax+4], esp
15          popad
16          mov esp, ebp
17          pop ebp
18          db 0c3h
19 StartThread ENDP
20 _TEXT   ENDS
21          SwitchThread PROTO C :D WORD
22 _TEXT   SEGMENT
23 SwitchThread PROC C pthis: DWORD
24          mov eax, pthis
25          pushad
26          xchg DWORD PTR [eax + 4], esp
27          popad
28          mov esp, ebp
29          pop ebp
30          db 0c3h
31 SwitchThread ENDP
32 _TEXT   ENDS
33 END

比ARM稍多一些,db 0c3h是ret的意思,我没有记错的话,这样写是因为栈要自己处理,VC的编译器在编译的时候,会将ret语句之前添加栈恢复的代码,完全不合我的意思。
线程很好,很强大,原来我们的噩梦都不复存在了。世界是美好的,有线程实在太好了。但是,这是一个相对的世界。十全十美的事情总是很难发生。下面开始了黑暗之旅。
上面的代码看上去完美的,执行上应该是完美的,但是,就真机的测试来说,大部分机型上是完美的,个别机型上会出问题的,我曾经试图解决这个问题,就像我之 前提到的,我没办法乱整真机,我也没办法进行真机调试,如果谁知道在任何BREW手机上如何真机调试的话,务必告诉我。出问题的表现就是手机重启,简单的 来说,主要集中的放置一段时间之后,屏保出现的时候,其他时候都是正常的。就我的经验来说,机型是固定的,这是庆幸的。新的BREW 4.0的手机则暂时没有发现问题。
我前几天没有更新博客,因为发生了些无法预料的灾祸,刚开始是我的老笔记本出现问题,开不了了。开始修理,最后不了了之。接下来是我的老台式机,放置了半 年之后,准备当服务器。结果,也开不了了。真是祸不单行,福无双降啊。想想我笔记本硬盘和移动硬盘同时挂掉,道理相同啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值