详谈内存管理技术(三)、线程模型

来源:互联网 发布:大数据开发视频教程 编辑:程序博客网 时间:2024/04/30 11:35

  一、为什么需要线程模型?

  记得几年前,自己写高精度算法时,因为需要一个线程安全的后台(用来保存一些信息),便手动写了一个线程本地存储(TLS)(虽然,后来因为改了计算模型,弃用了);再后来,因为内存池的需要,亦手动再写了一个线程本地存储(TLS);很好,这样一来同一个库里,竟然有两套相同的TLS;于是,意识到了什么地方不对。

  不只是代码重复的问题(其实重复的不多);更重要的是,TLS应该是线程模型本身,来提供的功能;但,很可惜,C++并没有这样的东西(线程模型)。(PS:我无视了系统提供的TLS...)

  说了这么多;只想说一点:为了TLS,为了性能!  当然,统一的线程模型,会有一个更大的好处:统一且简洁的多线程接口。

 

  二、任务模型

  和Java类似,线程模型本身派生与任务模型(我的最初版本并不是);这样有一个好处:将实例化的线程,当作任务来使用(即:并不通过自身所持有的线程,来执行任务)。在一些情形下,会有这样的需求。

  作为最根本的接口,需要足够的简单:

class ITask :public Interface{public:    virtual void run() = 0;public:    virtual void interrupted(){}public:    virtual tick_t getWaitingTime()const{        return 0;    }};

  最重要、也最直白的便是 run() 这个接口函数;其便是我们将要执行的任务本身(所以无需多言)。值得一提的是:interrupted() 其是一个回调函数,用来通知该任务,执行过程中被意外中断(如抛出异常)。至于 getWaitingTime() 是为线程池准备的,如果你的线程池支持延时执行。

 

  三、线程模型

  首先,要明白两点:什么是线程? 它用来做什么??

  在面向对象里,线程(类)即能够开启另外一个线程(相对进程);而其目的无外乎,用来执行代码。所以,其包含有两套逻辑:开启新线程、被执行的任务接口。所以,便如下:

class Thread :public ITask{protected:    void execute();};

  其相对于 ITask,只需要提供开启新线程的接口:execute。调用这个函数,便让新线程开始执行 run 所定义的任务。

  当然,还远不止如此;我们可以提供更多的控制接口:suspend、resume、interrupt(用来中断线程),以及查询接口:isStarted、threadID。但,有一个接口,绝不能够被遗忘:

class Thread :public ITask{protected:    void execute();public:    void wait();// here~};

  一旦线程开始执行;其最上层(Thread的子类)必须等待,该线程停止运行后,才能够开始析构!!而提供这样等待的便是:wait。(有GC的语言,没有这个问题)

 

  四、线程本地存储

  TLS,在哪里??对,我们依旧没有看到其丝毫的身影。其实,就在 execute 的背后——其开启新线程的同时,会注册该新线程,以创建一个新的线程环境(TLS);同样,在该线程停止时,将取消注册,并删除该环境。

  这时,我们需要一个全局的线程运行时环境,用来管理所有线程,并存储相应的TLS(或保存其索引)。

  当然,有一个问题值得提出:TLS放在哪里? 是全局运行时环境,还是线程本身?(操作系统,一般通过线程自身的调用栈,来存放TLS)

  如果,我们真有一个统一的线程模型,二者都可以选择。可惜,我们不可能有!主线程,不可能被我们的任何模型所约束(整个程序的第一行代码执行前,主线程便存在了)。当然,我们可以回避这个问题,通过完全不使用主线程(即:我们的任何逻辑,都在非主线程里执行);而且需要相当的小心,比如静态初始化。

  所以,唯一的选择就是,放在全局的运行时环境。这时,我们所见到的大概这样:

void Thread::execute(){    GetServiceAs<ThreadService>()->login();
...}

  所有的一切,都存放在 ThreadService 里;那么,如何获得TLS,自然也是使用:

...GetServiceAs<ThreadService>()->getLocalAs<XXType>();...

  需要注意的一点是,我们并没有显示地使用线程ID,来索引;而是在接口内部,通过系统API:GetCurrentThreadId 来获取当前线程ID。

 

  五、其他

  很多细节问题,我并没有提及,如:interrupted(),如何使用?

  是的,我所有的文章里,都没有“细节”二字;原因有二:并非数十行代码就能够明了、我的库并没有开源。但,相应的,所有的设计过程及细节,都已呈现给各位;只要你不笨,并有一些兴趣和耐心,便不难实现(甚至比我所讲的更好)。

  嗯,这次的内容,足够的少;大概是,“累”了,写了一些后,并没有任何同类的人,来进行期待中的交流(主要是期待不同意见);但,没有。所以,我将要动笔的概率,大概也将越来越低......

  至此。

0 0
原创粉丝点击