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

    xiaoxiao2021-03-25  58

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

      记得几年前,自己写高精度算法时,因为需要一个线程安全的后台(用来保存一些信息),便手动写了一个线程本地存储(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(),如何使用?

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

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

      至此。

    转载请注明原文地址: https://ju.6miu.com/read-35491.html

    最新回复(0)