`
cheeruplc
  • 浏览: 115051 次
  • 性别: Icon_minigender_2
  • 来自: 深圳
社区版块
存档分类
最新评论

我所认知的线程

阅读更多
本文转自http://www.cnblogs.com/yanlingyin/archive/2012/02/27/2369120.html

开篇

1、背景

之前的很长一段时间里,随着加工工艺的发展,cpu的处理速度一直在提升,直到04年cpu主频达到了4.0Mhz。除非技术有本质突破,才能进一步提高cpu的处理速度。然而需要处理的数据量并没有因此而停止增长,其中的一个方法就是采用多核、并行处理技术。这会成为并且正在成为未来发展的趋势。要理解并行技术,对线程有一定的了解是很必要的。这篇博客主要说一下自己对线程的看法,这只是从简单的角度来看问题,入门级文章,笔者认知有限,有不足之处还望不吝指正。

2、我的想法

关于并发编程,我觉得如果能有一种专门为并行而设计的语言,将是最好的解决方案。因为现有的语言大多的针对单核处理器的,现在的并发多数是由操作系统完成,用现行语言来编写并发程序的技术还显得不是很成熟。如果能有一种专门为并发而设计的语言,将会大幅度的提高程序的运行效率。当然这只是我的小想法而已。

3、内容一览:

1)程序和进程的概念区别

2)线程的概念

3)多线程的调度问题

4)线程安全问题(当多个线程同时访问一个变量时)

程序和进程

一般意义上的程序可以认为是在特定操作系统上的可执行文件。也就是源码经过编译、链接而形成的可执行文件,它依赖于执行的操作系统,因为正是操作系统提供了该程序运行的环境(运行库等)。它是一个静态的概念。

而进程,是一个动态的概念,它有自己的地址空间,能执行一些操作。程序的执行都会伴随着进程的生成,一个程序的执行会产生一个或多个进程。

所以可认为进程是程序的动态概念。

总结下程序和进程的区别

1)进程是动态的,而程序是静态的

2)进程有一个生命周期 ,而程序是指令集合,本身无“运动”含义。没有建立进程的程序不能作为一个单独的单位得到操作系认可

3)1个程序可产生多个进程,一个进程只对应一个程序

什么是线程

线程有时候又被称为轻量级进程,是程序执行的最小单元。和上文中一样的,一个进程可对应多个线程,而一个线程只属于一个进程。

进程的执行是以线程为单位进行得,比如说一个简单“hello world”程序只有一个线程,就是main()函数对应的线程。

线程的构成:

1)线程ID。用于标实线程

2)当前指令指针PC。标明下一指令执行点

3)寄存器集合和堆栈。该线程的可用空间

多线程

大多数软件应用中,线程的数量都不止一个。多线程可用并发的执行,并共享进程的全局便来那个和堆的数据。

多线程优势:

1)某个操作可能会陷入长时间的等待。采用多线程,当一个线程等待的时候,可执行其他线程,充分利用cpu。

2)某操作(如计算)可能会消耗大量时间,而导致和用户之间的交互中断。多线程可让一个线程负责计算,另一线程负责交互。

3)软件本身就要求并发操作,如多端下载软件

4)多核cpu,本身就具备同时执行多个线程的能力

线程访问权限

一般来说线程能访问进程内存中的所有数据,但实际应用中线程也有自己的空间
1)栈(可能被其他进程访问,但仍可认为是私有数据)

2)线程局部存储,一般只有很小容量

3)寄存器(包括PC寄存器)

线程调度

最好的情况是:当处理器数量大于要处理的线程数目的时候,所有线程都可以同时执行。实现真正意义上的并发。

而这种情况在现实中基本不可能。现实中的并发只是一种模拟出来的状态,特别是在单核处理对于多线程的时候。它通过让多个线程交替执行,每个线程执行很短时间,从表面上看,这些线程同时执行,实现并发。

每个线程都想被执行,但是每次执行的线程数量是有限的,所以就要有一种方法来从众多的线程中选出要执行的线程,现在讨论下单核的情况,多核的类似。

在操作系统中有专门的线程调度算法来实现,下面列几个简单的“调度算法”

1、“先进先出”策略。所有的线程组成一个队列,新生的线程加入到队列末尾,每次取队头执行。有一个缺陷就是,如果新生成的线程是紧急操作,需要操作系统尽快相应,这种调度方法就不能满足了。

2、按优先级调度。每个线程都有自己的优先级,并且是可以被操作系统修改的,调度时候每次选取优先级最高的执行。这种方法弥补了上一种方法的缺陷。对于需要及时相应的紧急事件,可以给他一个高优先级,这样就能在下次被调度。然而这种方法也有一个问题,也就是所谓的饥饿。如果某线程“看似”无关紧要,被给予一个低得优先级,以后每次产生的线程优先级都比他高,那么这个线程会一直得不到执行,成为饿死。一个解决的办法是随之事件的推移而提升线程的优先级。这样只要事件足够长,低优先级的线程也会获得高优先级而被执行。

从上面的分析来开,线程似乎有两个状态:执行和不执行(等待)。其实操作系统中的每个线程都对应三个状态。

线程的状态:

上面说了线程调度的问题,只有准备就绪的线程(这种线程称为就绪的线程),才能被调度。调度以后,线程就在处理器中被执行,这时线程的状态为运行时。如果该线程在等待某种事件的发生(如响应I/O),这种状态成为等待。

总结下线程的三种状态:

1)就绪:此时线程可以立刻运行i(如果该线程被调用的话)

2)运行:此时线程正在执行

3)等待:线程正在等待某件事的发生以便继续执行

在windows中,线程的状态有:

已初始化(Initialized):说明一个线程对象的内部状态已经初始化,这是线程创建过程中的一个内部状态,此时线程尚未加入到进程的线程链表中,也没有启动。
.
个线程来执行时,它只考虑处于就绪状态的线程。此时,线程已被加入到某个处理器的就绪线程链表中。

运行(Running):线程正在运行。该线程一直占有处理器,直至分到的时限结束,或者被一个更高优先级的线程抢占,或者线程终止,或者主动放弃处理器执行权,或者进入等待状态。

备用(Standby):处于备用状态的线程已经被选中作为某个处理器上下一个要运行的线程。对于系统中的每个处理器,只能有一个线程可以处于备用状态。然而,一个处于备用状态的线程在真正被执行以前,有可能被更高优先级的线程抢占。

已终止(Terminated):表示线程已经完成任务,正在进行资源回收。KeTerminateThread函数用于设置此状态。

等待(Waiting):表示一个线程正在等待某个条件,比如等待一个分发器对象变成有信号状态,也可以等待多个对象。当等待的条件满足时,线程或者立即开始运行,或者回到就绪状态。

转移(Transition):处于转移状态的线程已经准备好运行,但是它的内核栈不在内存中。一旦它的内核栈被换入内存,则该线程进入就绪状态。

延迟的就绪(DeferredReady):处于延迟的就绪状态的线程也已经准备好可以运行了,但是,与就绪状态不同的是,它尚未确定在哪个处理器上运行。当有机会被调度时,或者直接转入备用状态,或者转到就绪状态。因此,此状态是为了多处理器而引入的,对于单处理器系统没有意义。

门等待(GateWait):线程正在等待一个门对象。此状态与等待状态类似,只不过它是专门针对门对象而设计。

他们之间的转换图如下:

更多关于调度的知识可参见《现代操作系统》《windows内核分析》

可抢占线程和不可抢占线程

可抢占线程就是说,在该线程用完自己的时间片以后,操作系统会强制把该线程切出,以便执行其他线程。

而不可抢占线程则是线程不能强制切出,除非他自己放弃cpu的使用权而终止线程,而不是靠时间片的用尽而强制切出。不可抢占线程的线程切换时间是确定的,当该线程自愿切出时发生。

线程安全

多线程并发时,在访问数据方面会出现一些问题。特别是当多个线程访问同一个变量的时候。

下面将用一个例子来说明可能出现的问题:

线程A、线程B都对变量X进行操作,操作顺序如下:

1)线程A对X赋值

2)线程A对X自加

3)线程B使用X的值(比如说把它赋值给另一个变量)

代码经过编译之后,在处理器中执行代码时,通常一个很简单的运算(如自家运算)都会被分为多个步骤执行(指令流水)。

比如当线程A对X自加运算的时候,编译后的自加运算共分为三步,当没有执行完这三步的时候,可能线程A就会被切出(比如说有需要即时响应的操作发生)。也就是说线程A在对数据还没处理完全的时候被切出了,这样当线程B执行的时候,使用的X值将不是我们期望的值,显然,发生了错误。

解决策略:

上面问题的出现本质上是因为一个不应该被打断的操作被强行中断了,那么有一种解决的办法就是设置一种规定一些操作,在执行的时候不能被中断。这样就避免了操作还没完成就被换出的情况。把这些简单的操作称为原子的操作。windows中对于这种操作也有支持。

但是这种策略只适用于简单的情况。对于复杂的情况,我们用一种称为同步与锁的机制来实现。

同步与锁

简单的说,就是在一个线程对数据访问结束之前,其他线程不能对这个数据进行访问。这样的话,对数据的访问就原子化了。

这种机制的实现也很简单:每个线程对数据访问的时候都会尝试获取锁,当访问结束后释放。在获取锁的时候如果有线程在访问数据,就会获取失败,这时候线程会等待, 直到访问数据的那个线程释放锁。

二元信号量

是最简单的一种锁,它只有两个状态:占用与非占用。它用于只能被一个线程访问的资源。只有资源状态为非占用 的时候,才能被线程获取,获取之后修改资源状态为占用,访问结束后修改资源状态为非占用。

信号量

稍微复杂一些,他适用于可以被多个线程同时访问的资源。一个初始值为N=n的信号量能被n个线程同时访问。当要访问数据的时候,先查看N值,(N的值代表还有多少个线程能访问资源)如果N值大于0,该线程能访问资源,线程进入后把N值减一。当访问结束后N值+1.

如果信号量的值小于0,则进入等待状态。

互斥量

和二元信号相似,资源只能被一个线程访问,但是同一个信号量只能被获取该信号量的线程释放,也就是说对于二元信号量,同一个互斥量可以被别的(任意)线程释放。相对二元信号量来说更严格了。

临界区

是比互斥量更严格的同步手段。临界区和上面的区别在于,互斥量和信号量在任何进程都是可见的,也就是说,一个进程创建了互斥量和信号量,在其他进程都是可见的,而临界区的作用范围仅限于本进程。

读写锁

读写锁用于更加通用的场合。对于同一个数据,多个进程同时读是没问题的,但是如果有线程要对数据进行修改,就要使用同步手段来避免出错。对于同一个读写锁,有两种获取方式:

1、共享的,对数据只进行读操作,可以多个线程同时进行

2、独占的。会修改数据,在修改完成之前,不能有其他线程操作数据

当锁处于自由状态时,以任何一种方式获取锁都会成功,并将锁至于相应的状态。

如果锁处于共享状态,那么其他与以共享方式获取锁的线程都能成功,共同读数据。

对于独占式获取的,则要等到以共享方式获取的所有线程释放后(锁重新回到自由状态)才能获取。并且对于以独占方式获取的锁,其他任何对锁的请求都不会成功。

条件变量

条件变量类似于一个发令枪,可以有多个线程等待枪响,枪响的时候,这些等待枪响的线程会同时恢复执行。发令枪何时响也可由线程来决定。

也就是说,条件变量可以让多个线程等待某件事的发生,当时间发生时(条件变量被唤醒),所有的线程可以一起恢复执行。

小结

这篇文章主要介绍了线程的概念,然后简单的说了线程调度,最后叙述了线程的安全性问题,也是就是多线程共享资源时候的相关问题。而对于实际的例子这里没有给出。是因为网上关于线程的实例已经有很多了。

写这篇文章的目的是总结一下自己对线程的认识,从我的角度去理解线程,希望这篇文章能对各位园友有帮助。
分享到:
评论

相关推荐

    [『辅助』] 易编远航第一期-六套大漠多线程中级进阶视频教程

    并且对接下来脚本的线程处理,及监控线程起到一个较高的实际认知。 对多线程基 础及后续多线程课程有承前启后的作用 主要学习内容: 1.线程的启动及关闭 2.监控线程的运用 3.大漠多线程之参数传递 易语言调用大漠...

    java多线程编程实战指南 核心篇 代码

    在此背景下,以往靠单个处理器自身处理能力的提升所带来的软件计算性能提升的那种“免费午餐”已不复存在,这使得多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。故而,掌握多线程编程...

    一个线程oom会影响其他线程吗1

    在抛出 OOM 异常的时间点,堆的使用量突然下降,这表明抛出 OOM 异常的线程释放了它所占据的内存资源,从而不会影响其他线程的运行。 主线程和子线程的关系 在探讨主线程和子线程的关系时,我们需要注意到一个定义...

    认知Java,入门介绍

    - **多线程**: 内置的多线程支持使得开发复杂的应用程序变得更加简单。 2. **易于学习和使用**: - **健壮性**: Java设计时考虑到了安全性,如自动垃圾回收机制减少了内存泄漏的风险。 - **面向对象**: Java完全...

    java tensorflow图片认知学习

    这个"demo"可能只是展示了基本的图像分类流程,实际应用中可能需要考虑更多细节,如错误处理、性能优化、多线程等。 在实践中,使用Java进行图像识别的一个常见场景是将已训练好的模型嵌入到服务器或者移动应用中,...

    基于ThreadX与ARM平台实时嵌入式多线程应用详解

    适合人群:面向具有一定计算机基础知识和初步硬件认知能力的学生或者技术人员,尤其是对RTOS感兴趣的开发者。 使用场景及目标:① 学习ThreadX在实时操作系统中的作用和特性;② 掌握在嵌入式环境中高效管理和调度...

    JavaScript是否可实现多线程 深入理解JavaScript定时机制

    在探讨JavaScript是否可以实现多线程之前,我们首先要明确一个概念,即JavaScript通常是单线程的...在编写代码时,要避免依赖于定时器立即执行的错误认知,合理安排任务执行顺序和时间,以优化程序性能,提升用户体验。

    论文研究 - 基于线程的汉语体验式概念隐喻

    本研究旨在研究汉语中涉及线或弦的概念隐喻,并为汉语对关(xān-xì,关系)或lián-xì(联系)的认知提供一些启示。 根据从中国语言学中心(CCL)和《现代汉语词典》收集的数据,对LINK的原型和图像模式进行了...

    并发概念认知学习模型

    概念认知学习(CCL)涉及通过模仿人类的认知过程将新信息整合到自身中的方式。 基于形式概念分析,已经提出了许多CCL系统来满足不同的要求。 但是,这些方法大多数只能在小规模数据集上有效运行,并且缺乏分类能力。...

    操作系统实验41

    操作系统实验41主要探讨的是线程的状态转换,这是操作系统中核心的概念之一。线程是操作系统调度的基本单元,它们在不同的状态之间切换,以实现...此外,对内核级线程的调试经验也有助于提升对操作系统底层机制的认知。

    java常用书籍自学专用

    本书适合有一定Java语言基础的读者作为入门多线程编程之用,也适合有一定多线程编程经验的读者作为重新梳理知识结构以提升认知层次和参考之用。 多线程编程的优点: 1. 提高软件服务质量 2. 充分利用计算资源 3. ...

    Java Core Sprout:基础、并发、算法

    对锁的一些认知 ReentrantLock实现原理 ConcurrentHashMap 的实现原理 如何优雅地使用和理解线程池 深入理解线程通信 一个线程召集的诡异事件 线程池中你不可错过的一些细节 『ARM包入坑指北』之队列

    都江堰操作系统相关资料

    与传统操作系统不同,djyos不是以线程而是以事件为调度核心,这种调度算法使程序员摆脱模拟计算机执行过程编写程序的思维方式,而是按人类认知世界的方式编写应用程序,就如同在嵌入式编程中引入了VC似的。...

    JUC并发编程视频教程

    * 颠覆一些你以为"正确"的认知,纠正其它同类视频的错误* 100+ 张手绘图 & 流程图,帮助你形成正确的"多线程世界观"* 以知识点为主线、穿插讲解"应用","原理"和"多线程设计模式",多维度学懂并发主讲内容第一章:...

    Linux链表的解析与认知

    在多线程环境中,如果多个线程同时修改链表,可能会导致数据不一致。因此,通常需要配合锁机制,如自旋锁或读写锁,来确保链表操作的原子性和一致性。 总的来说,Linux链表是操作系统内核中不可或缺的数据结构,它...

    java线程池概念.txt

    线程安全,并发的知识有加深认知;当然,现在用过的东西并不是代表以后还能娴熟的使用,做好笔记非常重要; 1:必须明白为什么要使用线程池:(这点很重要)  a:手上项目所需,因为项目主要的目的是实现多线程的...

    操作系统 教学大纲.docx

    操作系统是计算机科学与技术专业的一门理论与实践并重的专业核心课程,旨在培养学生计算思维能力、算法分析及设计能力、大规模软件设计与实现能力,以及计算机软硬件系统的认知、分析、设计与应用能力。 二、课程...

    基于卷积神经网络的语义同时定位以及地图构建方法.pdf

    本文介绍了基于卷积神经网络的语义同时定位及地图构建方法,该方法旨在提升智能车在复杂室外交通场景中的环境感知和认知能力,以实现全自动驾驶。文章发表在《科学技术与工程》2019年第19卷第9期,由刘智杰等人撰写...

Global site tag (gtag.js) - Google Analytics