总结——操作系统

文章目录

  • 源码到可执行文件的过程
  • 进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的
  • 多线程和多进程的不同
  • 进程和线程的区别,你都使用什么线程模型
    • 1、Future模型
    • 2、fork&join模型
    • 3、actor模型
    • 4、生产者消费者模型
    • 5、master-worker模型
  • 有了进程,为什么还要有线程/li>
  • 游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么/li>
  • 多进程和多线程的使用场景
  • 单核机器上写多线程程序,是否需要考虑加锁,为什么/li>
  • 线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的
  • 线程间的同步方式,最好说出具体的系统调用
  • 协程
  • 进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
  • 进程间怎么通信
    • 1.管道:
    • 2. 系统IPC:
    • 3. 套接字SOCKET:
  • 多线程,线程同步的几种方式
    • 1、临界区:
    • 2、互斥量 Synchronized/Lock:
    • 3、信 量 Semphare:
    • 4、事件(信 ),Wait/Notify:
  • 如何设计server,使得能够接收多个客户端的请求
  • 死循环+来连接时新建线程的方法效率有点低,怎么改进/li>
  • 怎么实现线程池
  • 怎么唤醒被阻塞的socket线程/li>
  • 怎样确定当前线程是繁忙还是阻塞/li>
  • 请问就绪状态的进程在等待什么/li>
  • 多线程的同步,锁的机制
  • 两个进程访问临界区资源,会不会出现都获得自旋锁的情况/li>
  • 互斥锁(mutex)机制,以及互斥锁和读写锁的区别
    • 1、互斥锁和读写锁区别:
    • 2、Linux的4种锁机制:
      • 互斥锁:mutex,
      • 读写锁:rwlock,
      • 自旋锁:spinlock,
      • RCU:
  • 死锁发生的条件以及如何解决死锁
  • 静态变量什么时候初始化
  • 一个类,里面有static,virtual,之类的,来说一说这个类的内存分布
  • Linux虚拟地址空间
  • 虚拟内存和物理内存怎么对应
  • 虚拟内存置换的方式
  • 操作系统中的程序的内存结构
  • `A* a = new A`; `a->i = 10`;在内核中的内存分配上发生了什么/li>
  • 操作系统中的缺页中断
  • fork和vfork的区别
  • 如何修改文件最大句柄数/li>
  • 并发(concurrency)和并行(parallelism)
  • MySQL的端口 是多少,如何修改这个端口
  • 操作系统中的页表寻址
  • OS缺页置换算法
  • 操作系统中的结构体对齐,字节对齐
  • 软链接和硬链接区别
  • 什么是大端小端以及如何判断大端小端
  • 用户态和内核态区别
  • windows消息机制
  • 内存溢出和内存泄漏
  • 系统调用是什么,你用过哪些系统调用
  • fork调用示例
  • 操作系统为什么要分内核态和用户态
  • 用户态到内核态的转化原理
  • 微内核与宏内核
  • 僵尸进程
  • GDB调试用过吗,什么是条件断点
  • 5种IO模型
  • 异步编程的事件循环
  • 为什么要有page cache,操作系统怎么设计的page cache
  • server端监听端口,但还没有客户端连接进来,此时进程处于什么状态/li>
  • Linux下怎么得到一个文件的100到200行
  • awk的使用
  • linux内核中的Timer 定时器机制

源码到可执行文件的过程

1)预编译
主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下

1、删除所有的#define,展开所有的宏定义。

2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。

3、处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。

4、删除所有的注释,“//”和“/**/”。

5、保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。

6、添加行 和文件标识,便于编译时编译器产生调试用的行 信息,和编译时产生编译错误或警告是能够显示行 。

2)编译

把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。

1、词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记 。

2、语法分析:语法分析器对由扫描器产生的记 ,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。

3、语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。

4、优化:源代码级别的一个优化过程。

5、目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。

6、目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。

3)汇编

将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows下)、xxx.obj(Linux下)

4)链接

将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接:

1、静态链接:

函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。

空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本;

更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。

运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

2、动态链接:

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。

共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;

更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。

性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的

基本概念:
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;

线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

区别:

1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)

3.进程是资源分配的最小单位,线程是CPU调度的最小单位

4.系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。

5.通信:**由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。**进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预

6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。

7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉

8.进程适应于多核、多机分布;线程适用于多核

进程间通信的方式:

进程间通信主要包括管道系统IPC(包括消息队列、信 量、信 、共享内存等)、以及套接字socket。

1.管道:

管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信

1.1 普通管道PIPE

1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端

2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)

3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

1.2 命名管道FIFO

1)FIFO可以在无关的进程之间交换数据

2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

  1. 系统IPC:

2.1 消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。 (消息队列克服了信 传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;

特点:

1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2.2 信 量semaphore

信 量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问信 量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

特点:

1)信 量用于进程间同步,若要在进程间传递数据需要结合共享内存。

2)信 量基于操作系统的 PV 操作,程序对信 量的操作都是原子操作。

3)每次对信 量的 PV 操作不仅限于对信 量值加 1 或减 1,而且可以加减任意正整数。

4)支持信 量组。

2.3 信 signal

信 是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生

2.4 共享内存(Shared Memory)

它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信 量等

特点:

1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取

2)因为多个进程可以同时操作,所以需要进行同步

3)信 量+共享内存通常结合在一起使用,信 量用来同步对共享内存的访问

3.套接字SOCKET:

socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。

线程间通信的方式:

**临界区:**通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;

互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问

信 量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。

事件(信 ),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

多线程和多进程的不同

进程是资源分配的最小单位,而线程时CPU调度的最小单位多线程之间共享同一个进程的地址空间,线程间通信简单,同步复杂线程创建、销毁和切换简单,速度快,占用内存少,适用于多核分布式系统但是线程间会相互影响,一个线程意外终止会导致同一个进程的其他线程也终止,程序可靠性弱。而多进程间拥有各自独立的运行地址空间,进程间不会相互影响,程序可靠性强,但是进程创建、销毁和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步简单,适用于多核、多机分布。

线程 进程
最小单位 CPU调度 资源分配
内存 共享内存 各自独立
通信 简单 复杂
同步 复杂 简单
相互 相互影响可靠性差 不影响可靠性好
创建切换销毁 简单开销小 复杂开销大
使用场景 单机多核 多机多核

进程和线程的区别,你都使用什么线程模型

1)进程和线程区别
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

2、进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)

3、进程是资源分配的最小单位,线程是CPU调度的最小单位。

4、系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。

5、通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预 。

6、进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。

7、进程间不会相互影响;线程一个线程挂掉将导致整个进程挂掉。

8、进程适应于多核、多机分布;线程适用于多核。

2、常用线程模型

1、Future模型

该模型通常在使用的时候需要结合Callable接口配合使用。

Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。

Callable是类似于Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常没有返回值,而call方法则可以抛出受检异常并可设置返回值。两者的方法体都是线程执行体。

2、fork&join模型

该模型包含递归思想和回溯思想,递归用来拆分任务,回溯用合并结果。可以用来处理一些可以进行拆分的大任务。其主要是把一个大任务逐级拆分为多个子任务,然后分别在子线程中执行,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。

这里模拟一个摘苹果的场景:有100棵苹果树,每棵苹果树有10个苹果,现在要把他们摘下来。为了节约时间,规定每个线程最多只能摘10棵苹树以便于节约时间。各个线程摘完之后汇总计算总苹果树。

3、actor模型

actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架。

4、生产者消费者模型

生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务。开启一个/多个线程来生产任务,然后再开启一个/多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。

5、master-worker模型

master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。

有了进程,为什么还要有线程/h2>

线程产生的原因:
进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点:

进程在同一时间只能干一件事

进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行。

因此,操作系统引入了比进程粒度更小的线程,作为并发执行的基本单位,从而减少程序在并发执行时所付出的时空开销,提高并发性。和进程相比,线程的优势如下:

从资源上来讲,线程是一种非常”节俭”的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。

从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。

从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。

除以上优点外,多线程程序作为一种多任务、并发的工作方式,还有如下优点:

1、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序才会利于理解和修改。

游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么/h2>

游戏服务器应该为每个用户开辟一个进程**。因为同一进程间的线程会相互影响,一个线程死掉会影响其他线程,从而导致进程崩溃**。因此为了保证不同用户之间不会相互影响,应该为每个用户开辟一个进程

多进程和多线程的使用场景

多进程模型的优势是CPU
多线程模型主要优势为线程间切换代价较小,因此适用于I/O密集型的工作场景,因此I/O密集型的工作场景经常会由于I/O阻塞导致频繁的切换线程。同时,多线程模型也适用于单机多核分布式场景

多进程模型,适用于CPU密集型。同时,多进程模型也适用于多机分布式场景中,易于多机扩展

单核机器上写多线程程序,是否需要考虑加锁,为什么/h2>

在单核机器上写多线程程序,仍然需要线程锁。因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据不使用线程锁的前提下,可能会导致共享数据修改引起冲突。

线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的

线程在切换的过程中需要保存当前线程Id线程状态、堆栈、寄存器状态等信息。
其中寄存器主要包括SP PC EAX等寄存器,其主要功能如下:
SP:堆栈指针,指向当前栈的栈顶地址

PC:程序计数器,存储下一条将要执行的指令

EAX:累加寄存器,用于加法乘法的缺省寄存器

线程间的同步方式,最好说出具体的系统调用

信 量
信 量是一种特殊的变量,可用于线程同步。它只取自然数值,并且只支持两种操作:

P(SV):如果信 量SV大于0,将它减一;如果SV值为0,则挂起该线程。

V(SV):如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。

其系统调用为:

:以原子操作的方式将信 量减1,如果信 量值为0,则sem_wait将被阻塞,直到这个信 量具有非0值。

:以原子操作将信 量值+1。当信 量大于0时,其他正在调用sem_wait等待信 量的线程将被唤醒

互斥量

互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。其主要的系统调用如下:

pthread_mutex_init:初始化互斥锁

pthread_mutex_destroy:销毁互斥锁

**pthread_mutex_lock:**以原子操作的方式给一个互斥锁加锁,如果目标互斥锁已经被上锁,pthread_mutex_lock调用将阻塞,直到该互斥锁的占有者将其解锁。

pthread_mutex_unlock:以一个原子操作的方式给一个互斥锁解锁。

条件变量

条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。即,当某个共享变量等于某个值时,调用 signal/broadcast。此时操作共享变量时需要加锁。其主要的系统调用如下:

pthread_cond_init:初始化条件变量

pthread_cond_destroy:销毁条件变量

**pthread_cond_signal:唤醒一个等待目标条件变量的线程。**哪个线程被唤醒取决于调度策略和优先级。

pthread_cond_wait:等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。该函数中在进入wait状态前首先进行解锁,然后接收到信 后会再加锁,保证该线程对共享资源正确访问。

信 量
信 量强调的是线程(或进程)间的同步:“信 量用在多线程多任务同步的,一个线程完成了某一个动作就通过信 量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在那里)。当信 量为单值信 量时,也可以完成一个资源的互斥访问。信 量测重于访问者对资源的有序访问,在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

有名信 量
可以用于不同进程间或多线程间的互斥与同步

创建打开有名信 量

name是文件路径名,但不能写成/tmp/a.sem这样的形式,因为在linux下,sem都是在/dev/shm目录下,可写成”/mysem”或”mysem”,创建出来的文件都是”/dev/shm/sem.mysem”,mode设置为0666,value设置为信 量的初始值.所需信 灯等已存在条件下指定O_CREAT|O_EXCL却是个错误。

关闭信 量,进程终止时,会调用它

int sem_unlink(const char *name);
等待信 量,测试信 量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值变为大于0就将它减1,并返回

当信 量的值为0时,sem_trywait立即返回,设置errno为EAGAIN。如果被某个信 中断,sem_wait会过早地返回,设置errno为EINTR

发出信 量,给它的值加1,然后唤醒正在等待该信 量的进程或线程

int sem_post(sem_t *sem);
成功返回0;失败返回-1,不会改变它的值,设置errno,该函数是异步信 安全的,可以在信 处理程序里调用它

无名信 量
用于进程体内各线程间的互斥和同步,使用如下API(无名信 量,基于内存的信 量)

(1)sem_init
功能:用于创建一个信 量,并初始化信 量的值。
函数原型:

int sem_init (sem_t* sem, int pshared, unsigned int value);
函数传入值: sem:信 量。pshared:决定信 量能否在几个进程间共享。由于目前LINUX还没有实现进程间共享信息量,所以这个值只能取0。

(2)其他函数

功能:sem_wait和sem_trywait相当于P操作,它们都能将信 量的值减一,两者的区别在于若信 量的值小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。sem_post相当于V操作,它将信 量的值加一,同时发出唤醒的信 给等待的进程(或线程)。

sem_getvalue 得到信 量的值。

sem_destroy 摧毁信 量。

如果某个基于内存的信 灯是在不同进程间同步的,该信 灯必须存放在共享内存区中,这要只要该共享内存区存在,该信 灯就存在。

互斥锁
互斥锁(又名互斥量)强调的是资源的访问互斥:互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信 量会同时使用的。也就是说,信 量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。

在linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:

对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.

对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.

原型:

说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解.

首先说一下加锁函数:

头文件:

说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态.

再说一下解锁函数:

原型:

条件变量常与互斥锁同时使用,达到线程同步的目的:条件变量通过允许线程阻塞和等待另一个线程发送信 的方法弥补了互斥锁的不足。在发送信 时,如果没有线程等待在该条件变量上,那么信 将丢失;而信 量有计数值,每次信 量post操作都会被记录。

互斥锁必须是谁上锁就由谁来解锁,而信 量的wait和post操作不必由同一个线程执行。
互斥锁要么被锁住,要么被解开,和二值信 量类似
sem_post是各种同步技巧中,唯一一个能在信 处理程序中安全调用的函数
互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信 量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性
互斥锁,条件变量都只用于同一个进程的各线程间,而信 量(有名信 量)可用于不同进程间的同步。当信 量用于进程间同步时,要求信 量建立在共享内存区。
信 量有计数值,每次信 量post操作都会被记录,而条件变量在发送信 时,如果没有线程在等待该条件变量,那么信 将丢失。
读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。

读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。

读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。

初始化和销毁:

同互斥量以上, 在释放读写锁占用的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.

读和写:

这3个函数分别实现获取读锁,获取写锁和释放锁的操作.获取锁的两个函数是阻塞操作, 同样,非阻塞的函数为:

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//可以获取则返回0, 否则返回错误的EBUSY.

                                                        

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年2月18日
下一篇 2020年2月18日

相关推荐