深入浅出多线程和线程安全

时间:2023-01-27 20:43:05

线程,不神秘的东西,简单的讲,当出现一件事情很复杂很紧急的时候,一个人来完成,效率和时间上无法保证,那么理所当然的要雇用更多的人来共同完成这项工作。那么多人共同完成一件事情,就能称为多线程或者多进程了。既然说了线程,不得不讲下进程了。

进程,行而上说,可以当作一个普通的EXE程序(当然我是背负着误导的骂名这样子写的,事实深入之后就发现不是这样子的,为了先有个概念,暂时这么说吧),那么我们使用到的多进程的情况就很多了,开两个一模一样的程序做同一件事情,例如同时登录两个或以上的QQ,也算是广义上的多进程了。进程和线程之间的关系差别不大,但是可以这么理解,进程好像是家庭,线程好像是家庭成员,两个家庭之间的合作叫多进程,成员之间的合作叫多线程。既然这么举例,是不是可以说进程包含多个线程呢?答案是80%可以这么说。因为甚至在早期的linux系统中就没有线程的概念,从某种意义上线程是由进程来模拟的,称为伪线程了。(最近很长时间没有关注linux,听说新的内核已经有了线程了,未经考证)。既然进程和线程这么相似,是不是可以取消其中的一个呢?答案是否定的,或者说会让系统不容易管理。继续使用上面的例子,因为线程同属于一个家庭,那么它们之间公有的家庭生产资料很多,也就说在进程中分配的内存和CPU时钟,绝大部分是进程中的每一个线程共用的(线程也有自己独立的内存空间等)。进程之间,因为超脱了同一家庭之间的概念了,所以进程之间的交互频率要小得多,除非其中一个家庭发了疯,破坏规则去抢占别人的生产资料(某个进程中异常或者刻意,越界访问别的进程分配的内存空间,所以这样的操作会引起自己和别人的异常,危险级别很高的操作),那么进程之间的信息共享就只能通过系统这个“大社会”来做了,安全的做法一般就是通过磁盘文件,数据库,网络端口(Socket)等来做数据的交互。

上面说明了进程和线程之间的关系,因为线程本身的特性,线程之间的交互和访问简单而且频繁得多,既然线程之间的访问这么简单和频繁,那么肯定会带来一些负面的东西,那就是安全。

线程之间的合作(所谓的线程同步,就是共同完成一项工作),不可以避免地涉及到生产资料的抢夺,如果不设置这种生产资料的惟一性抢夺,那么有可能会出现,两个或者多个线程操作同一段内存的想象,这是致命的也是危险的,拿整数举例,如果两个线程同时操作一个整数的值,如果一个线程在修改它的高位,另一个在修改它的低位,那么出来的结果是两个线程都不可预期的。典型的案例,就是库存量了,有一个线程在买入,一个在卖出,保证库存量数字的正确性就很重要了。

为了保证线程的安全,通常有两种方式,一个限制行为,一个限制工作对象,下面有更详细的解释。以挖煤举例,只存在一座煤山,因为某些不知名的原因,要线程同步。1.限制行为的做法:工头拿出一个腰牌,然后对下面的矿工说,要进山的时候,先取这个腰牌,然后出山的时候还这个腰牌。这样子下一个矿工一定要等到上一个矿工出来之后自己才能进去,这样子就避免了同时挖山这种危险的行为。2.限制工作对象:另外一个工头提出了两一个可行性的方案,将矿山动点手脚,在矿山上加上一个门,当有一个人在里面的话,门就关闭,再也没有人能进去了,人出来了门才打开,才允许新的矿工进入进行操作。两种方法只是限制的对象不一样而以,一个对矿工挖煤的行为作了规定,一个只是限制矿山,矿工没有附加的限制。所有的线程安全的模式,都可以用这两种方式来解释。如果说某一个对象是线程安全的,一定是这个对象的操作函数都是安全的。

线程的创建,调用CreateThread()或者_beginthreadex来玩,其实是一个东东的,创建一个线程,其实就是不停的执行一个回调函数而已。回调函数也不神秘,就是一个static的函数而已。在这里对于函数就不再赘述,有机会我会写点关于代码段和数据段之间关系的东西。

ps:本文章的目的是深入浅出,所以难免有些不完全正确的地方,希望达人们能一起探讨交流。之所以用“深入浅出”这个词,主要想对候俊杰老先生表示敬意,那本《深入浅出MFC》让我百看不厌,老先生很有古风,每篇每章开头都会古诗词引证下,有点红楼的风格,记得还是那经典的一句“山高月小,水落石出”