同步和互斥

一、同步和互斥的基本概念

现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行,同时运行可能是真的同时运行(SMP架构中),也可能仅仅是操作系统提供的服务(通过将CPU时间分片,并将时间片分给不同的任务)。在多任务操作系统中,同时运行的多个任务可能

  1. 都需要访问/使用同一种资源
  2. 多个任务之间有依赖关系,某个任务的运行依赖于另一个任务

这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,同步和互斥就是用于解决这两个问题的。

  • 互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行,最基本的场景就是对资源的同时写,为了保持资源的一致性,往往需要进行互斥访问。
  • 同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务,最基本的场景就是任务之间的依赖,比如A任务的运行依赖于B任务产生的数据。

显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个任务之 间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也 是一种互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任务是无序的。而同步的任务之间则有顺序关系。


在实际的设计、编码中,任务之间的相互依赖也就是情形2是比较容易发现的,而情形1就不那么 明显了,同步/互斥中出现的问题也多和情形1相关。情形1是和资源访问相关的,要避免此类问题或者解决已经出现的此类问题首先要解决的问题是识别那类资源 是被多个任务共享的,然后再使用同步/互斥的机制来保护这些资源的访问。简单的来说资源大概可以分为以下几类:

  1. 存储在存储器中的资源:这类资源不依赖于任务的运行,多以文件或数据库的形式存在于存储器中,它们对所有任务都是可见的(不考虑权限问题)。
  2. 内核中的资源:此类资源由内核创建和维护,对内核中的任务和获取了该资源的句柄的用户任务可见(对于内核创建的资源,如果用户任务想要使用它,往往都需要通过某种API类获取资源的句柄)。
  3. 用户任务中的资源:此类资源仅在该任务内可见,其它任务是无法访问该类资源的。

在进行设计、编码时我们可以首先对任务使用的资源进行分析,看它使用的资源属于那一类,如果有多个任务需要使用同一类资源,那么这里就需要进行同步/互斥 了。(从这里的分析也可以看出,如果用户任务不需要使用内核中的资源也不许要使用存储器中的资源,那么它就不存在情形1所涉及的同步互斥需求)

二、用户程序编程中常见的多任务

用户程序编程中常见的多任务有两种情形:
  1. 多进程
  2. 多线程

在这里可以将用户中的资源进行进一步的分类:
1. 进程中的资源:进程是操作系统分配资源的基本单位,进程的资源主要包括

  • 地址空间(涉及到同步互斥的地址段主要是数据段和堆栈段)
  • 打开的文件句柄

2. 线程中的资源:一个进程中的所有线程共享进程的地址空间(数据段/堆),打开的文件句柄,由线程独享的资源包括

  • 线程专有数据区

三、进程和线程的基本概念

1.进程

进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然, 程序是死的(静态的),进程是活的(动态的)。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状 态下的操作系统本身;其它进程就是用户进程。进程是操作系统进行资源分配的单位(比如文件句柄,虚拟地址空间等等)。

linux下创建子进程的调用是fork(),它功能就是产生子进程,其特别之处在于它会返回2次。

2.线程

线程是可执行代码的可分派单元。这个名称来源于“执行的线索”的概念。在基于线程的多任务的环境中,所有进程有至少一个线程,但是它们可以具有多个任务。这意味着单个程序可以并发执行两个或者多个任务。
简而言之,线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很 多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。
多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。

3.使用线程的好处

使用线程的好处有以下几点:

  1. .提高应用程序的响应:可以对任何一个包含许多相互独立的活动的程序进行重新设计,以便将每个活动定义为一个线程。例如,多线程 GUI 的用户不必等待一个活动完成即可启动另一个活动。
  2. 更有效地使用多处理器:通常,要求并发线程的应用程序无需考虑可用处理器的数量。使用额外的处理器可以明显提高应用程序的性能。具有高度并行性的数值算法和数值应用程序(如矩阵乘法)在多处理器上通过多个线程实现时,运行速度会快得多。
  3. 改进程序结构:许多应用程序都以更有效的方式构造为多个独立或半独立的执行单元,而非整块的单个线程。多线程程序比单线程程序更能适应用户需求的变化。
  4. 占用较少的系统资源:多进程与多线程相比,每个进程都有一个完整的地址空间和操作环境状态。每个进程用于创建和维护大量状态信息的成本,与一个线程相比,无论是在时间上还是空间上代价都更高。此外,进程间所固有的独立性使得程序员需要花费很多精力来处理不同进程间的通信。

4.线程唯一的资源

线程唯一的资源包括:

  1. 线程 ID
  2. 寄存器状态(包括 PC 和栈指针)
  3. 信号掩码
  4. 优先级
  5. 线程专用存储

四、进程及线程的比较以及注意事项

线程是多线程编程中的主编程接口。线程仅在进程内部是可见的,进程内部的线程会共享诸如地址 空间、打开的文件等所有进程资源。由于线程可共享进程指令和大多数进程数据,因此一个线程对共享数据进行的更改对进程内其他线程是可见的。一个线程需要与 同一个进程内的其他线程交互时,该线程可以在不涉及操作系统的情况下进行此操作。
进程是操作系统分配资源的单位,不同的进程拥有的资源不同,比如地址空间、打开的文件等等。

1.多线程编程的特殊之处

因为多线程共享进程的大多数数据,因此也引入了新的注意事项:

  1. 线程安全函数:在C语言中局部变量是在栈中分配的,任何未使用静态数据或全局数据的函数都是线程安全的。非线程安全的函数可以通过加锁的方式来使函数实现线程安全。
  2. 线程安全的(Thread-Safe):如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。线程安全函数解决多个线程调用函数时访问共享资源的冲突问题。
  3. 可重入(Reentrant):函数可以由多于一个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入性解决函数运行结果的确定性和可重复性。

可重入函数编写规范:

  1. 不在函数内部使用静态或全局数据
  2. 不返回静态或全局数据,所有数据都由函数的调用者提供。
  3. 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
  4. 如果必须访问全局变量,利用互斥机制来保护全局变量。
  5. 不调用不可重入函数。

线程安全与可重入函数之间的关系:

  1. 一个函数对于多个线程是可重入的,则这个函数是线程安全的。
  2. 一个函数是线程安全的,但并不一定是可重入的。【例如可以使用互斥锁实现的线程安全】
  3. 可重入性要强于线程安全性。

例如标准库中的malloc是不可重入的,但是标准库的实现一般都有提供线程安全的版本(具体怎么用线程安全的版本可以查看使用的库的手册,总之小心一点)。

线程安全和可重入函数针对的是多线程环境下,由多个线程共享的资源(静态数据、全局数据)的使用问题。之所以有这个问题是因为在某些函数会使用静态 或者全局数据,它运行的结果依赖于这些数据,这时如果此类函数在运行中被切换走,然后另一个线程也调用了该函数就会出问题,因此这类问题是特定存在于多线 程环境的(当然类似的问题在进程环境下也存在,存在的原因是信号处理函数可能在任意时刻被调用,因而它可能打断进程的正常运行;多个进程之间是不存在这类 问题的,因为每个进程看到的都是自己独立的地址空间)。

2.同步和互斥的实现

  • 多线程使用互斥锁、条件变量、自旋锁、读写锁和信号量来实现同步和互斥。
  • 多进程也使用互斥锁、条件变量、自旋锁、读写锁和信号量来实现同步和互斥,但是需要用进程间通信(IPC)来实现信息共享/传输。

需要注意的是,并没有所谓的线程间通信,因为同一个进程内部的线程会共享该进程的资源,比如堆空间、打开的文件等等,因而同一个进程内的多个线程之间不存在通信问题;如果是不同进程之间的线程,就按照进程之间通信方式进行通信即可。

3.何时采用多线程何时采用多进程

这是一个比较艰难的抉择:),取决于应用场景,它们的区别或许是有参考价值的:

    1. 一个进程中的所有线程都必须运行相同的可执行程序(它们最多能做到的是运行相同可执行程序的不同部分)。而一个子进程可以运行一个完全不同的可执行程序。
    2. 进程是操作系统分配资源的基本单位,而线程则不是,线程独立拥有的主要是栈和线程专用缓冲区
    3. 由于同一个进程内的多个线程共享同一块虚拟内存和其它资源,因而一个线程出错可能会影响到同一个进程中的其它线程。而多进程环境下,一个进程出错并不会影响其它进程,因为每一个进程都拥有自己独立的资源。
    4. 创建新进程时的资源拷贝使得创建新的进程比创建新的线程效率底下很多。不过由于写时拷贝机制的存在,因而如果子进程不产生写请求,这个影响就会很小。
    5. 如果一个任务可以被分解为多个几乎完全相同的子任务,则多线程就可能是一个很好的选择。
    6. 由于同一个进程内的多个线程共享进程的资源,因而多个线程共享资源非常简单(当然,代价是必须防止出现竞态)。而多进程之间共享资源则需要通过IPC机制

转载于:https://www.cnblogs.com/xiaozhi123/p/3915987.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/407365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql常用命令行大全

1、连接Mysql 格式: mysql -h主机地址 -u用户名 -p用户密码 1、连接到本机上的MYSQL。 首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u root -p,回车后提示你输密码.注意用户名前可以有空格也可以没有空…

React开发(113):git 操作记录

git reflog 可以查看所有分支的所有操作记录(包括(包括commit和reset的操作),包括已经被删除的commit记录,git log则不能察看已经删除了的commit记录 具体一个例子,假设有三个commit, git st: …

从printf谈可变参数函数的实现

作者:戎亚新 摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒。printf这种对参数个数和参数类型的强大适应性…

JS之数组删除/添加项目方法splice

用法:splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目 注:1:该方法会改变原始数组 语法:arrayObject.splice(index,howmany,item1,…..,itemX) 参数1:必需。整数,规定添加/删除项目的位置&…

Struts+DAO框架搭建完成!(源码)

今天做了这个StrutsDAO框架,可以说是又向SSH迈了一步。 做的过程中又发现了一些问题,但是在我和其他人的努力下一起解决了。这个问题是MyEclipse的Tomcat的启动问题。(已经解决并发帖解答了) 好了,下面切入正题&…

HTTP1.1中CHUNKED编码解析(转载)

HTTP1.1中CHUNKED编码解析 一般HTTP通信时,会使用Content-Length头信息性来通知用户代理(通常意义上是浏览器)服务器发送的文档内容长度,该头信息定义于HTTP1.0协议RFC 1945 10.4章节中。浏览器接收到此头信息后,接受…

解析可变参数函数的实现原理(printf,scanf)

From: http://hi.baidu.com/huifeng00/blog/item/085e8bd198f46ed3a8ec9a0b.html 学习C的语言的时候,肯定接触到标准输出和标准输入函数。 这个函数给人的感觉很强大,因为它很另类,就是这个函数的参数是可变的。 下面是一个自己编写的可变…

花生葫芦球 健身新运动

国民健康天后张淳淳老师率先再将风靡欧美的“花生葫芦球(FITNESS BALL)”,推广给日、港、台的朋友,同时结合国内外体适能教练与专家,研发出一套减压、塑身运动课程,引领全民健康塑身运动。 花生葫芦球 健身新运动美大腿后健肌群伸…

JS之数组元素排序方法sort

作用:sort() 方法用于对数组的元素进行排序 语法:arrayObject.sort(sortby) 参数:可选。规定排序顺序。必须是函数 返回值:对数组的引用。请注意,数组在原数组上进行排序,不生成副本 注意1:…

js中自己实现bind函数的方式

前言 最近由于工作比较忙,好久都没时间静下心来研究一些东西了。今天在研究 call 和 apply 的区别的时候,看到 github 上面的一篇文章,看完以后,感觉启发很大。 文章链接为 https://github.com/lin-xin/blog/issues/7 &#xff…

我的C语言可变参数的实现

实现环境&#xff1a;Fedora12 gcc 任务&#xff1a;用C语言实现一个参数可变的函数&#xff0c;以方便输出。 源代码如下&#xff1a; #include <stdio.h>#include <stdarg.h>#include <string.h>int sum(int data, ...){int i data, s 0;va_list vl;…

Leetcode刷题(1)两数之和

最好的种树是十年前,其次是现在。歌谣 每天一个前端小知识 提醒你改好好学习了 知乎博主 csdn博主 b站博主 放弃很容易但是坚持一定很酷 我是歌谣 喜欢就一键三连咯 你得点赞是对歌谣最大的鼓励 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…

使用WEB方式更改域用户帐户密码

使用WEB方式更改域用户帐户密码 <?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1、这个只是域帐户密码的一种更改方式&#xff0c;正规来说&#xff0c;域用户帐户的密码更改方式可以有6种。今天介绍给大家的只是其中一种&…

一个路径下挂载(匹配)多个子组件

效果图如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>Document</title><script type"text/javascript" src"./lib/vue-2.4.0.js"></script><scrip…

JS之字符串截取函数substr

作用&#xff1a;substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符 语法&#xff1a;stringObject.substr(start,length) 参数1&#xff1a;必需。要抽取的子串的起始下标。必须是数值。如果是负数&#xff0c;那么该参数声明从字符串的尾部开始算起的位置。…

面向对象中的修饰关键词

final:用来修饰类和方法&#xff0c;修饰类的时候表示这个类是终极类&#xff0c;不能被其他类继承&#xff0c;修饰方法的时候&#xff0c;表示这个方法是终极方法&#xff0c;不能被子类重写。 static:用来修饰属性和方法&#xff0c;修饰属性的时候表示这个属性是静态属性&a…

GDB命令大全

GDB的使用   当程序出错并产生core 时   快速定位出错函数的办法   gdb 程序名 core文件名(一般是core,也可能是core.xxxx)   调试程序使用的键   r run 运行.程序还没有运行前使用   c cuntinue 继续运行。运行中断后继续运行   q 退出   kill 终止调…

Leetcode刷题(2)回文数

最好的种树是十年前,其次是现在。歌谣 每天一个前端小知识 提醒你改好好学习了 知乎博主 csdn博主 b站博主 放弃很容易但是坚持一定很酷 我是歌谣 喜欢就一键三连咯 你得点赞是对歌谣最大的鼓励 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &…