Go多进程编程

多进程编程

内核会把虚拟内存划分为若干页,而物理内存的划分则是由CPU负责的,一个物理内存单元称为一个页框
没有页框对应的页可能是没有数据或者是数据暂时还不需要使用,也可能是数据已经被换出磁盘(就是Linux的swap分区)
还有一种可能就是两个进程的页共享一个页框,这种情况是允许的,也就是共享内存的基础

系统调用:正常情况下用户无法直接访问系统底层,用户通过系统暴露的接口去访问底层功能的过程称为系统调用

系统调用类似函数,但本质不同
1. 系统调用会向内核空间发出申请,普通函数只是定义如何获取一个给定的服务
2. 系统调用会导致内核空间中数据的存取和指令的执行,普通函数只能在用户空间范围内执行

用户态和内核态:

大部分时间CPU处于用户态,当涉及到系统调用时CPU切回内核态,此时有权限访问内核进程,执行完系统调用再切换回用户态

进程的切换和调度

Linux会凭借CPU快速的进程多个进程之间的切换,也就是进程间的上下文切换,
造成多个进程同时运行的假象,而每个进程都认为自己独占了CPU,这个就是多任务操作系统的由来,但实际上无论切换的多快,同一时刻运行的进程只有一个
除了每次进程切换时,进程状态的保存和恢复,内核还要考虑下次切换时运运行哪个进程、何时进行切换、被换下的进程何时再换上等,这一系列的任务称为进程调度

命名管道其实是以文件的形式存在于文件系统中,使用与文件类似,使用mkfifo可以创建一个具名管道,命名管道默认阻塞

通过kill -l可以看到Linux支持的信号共有64种,不包括32和33
其中1-31的属于标准信号,也叫不可靠信号,34-64的属于实时信号,也叫可靠信号,每种标准信号只会被记录并处理一次,并且,如果发送给某一个进程的标准信号的种类有多个,它们的处理顺序也是不确定的,实时信号就解决了这个问题,它可以将多个同种类的信号记录在案,而且按照信号的发送顺序被处理

linux对信号的默认处理会根据信号种类的不同而不同,但一定是终止信号 忽略信号 终止进程并保存信息 终止进程 恢复进程 这几种中的一种

socket的通信域

AF_INET   Ipv4域 地址4字节 port2字节   在基于Ipv4协议通信的任意两台计算机之间进行通信
AF_INET6  Ipv4域 地址16字节 port2字节  在基于Ipv6协议通信的任意两台计算机之间通信
AF_UNIX   Unix域 路径名称              在同一台计算机上的两个应用程序
tcp是字节流,无边界,所以会发生粘包现象,是可靠传输
udp是数据报,有边界,不会发生粘包,是非可靠传输
raw是数据报,seqpacket是字节流,但发送方可以记录数据的边界,数据边界会同数据一起发送到接收方
数据报形式的是不可靠传输,但有边界
字节流形式的是可靠传输,但是无边界,会发生粘包(sock_seqpacket除外),面向连接

在面向连接的socket之间传输数据,由于连接中已包含通信双方的地址,所以,无需再传输数据时指定目标地址,注意,发送方只能发送数据,接收方只能接收数据

面向无连接的socket在通信时无需建立连接,传输的每一个数据包都是独立的,并且会直接发送到网络上,这些数据包中都包含有目标地址,数据流只能是单向的

SOCK_RAW提供了一个直接通过底层TCP/IP传输数据的方法,但为了安全,一般只有超级用户权限的程序才可以使用,使用成本也比较高,需要自己构建数据传输格式,所以,应用程序一般很少使用

一个线程对自身的两种控制:

1. 终止 若在创建线程时执行return语句,会使该函数随create的结束而终止
注意,若在主线程中使用return,当前进程中的所有进程都会被终止
2. 任意线程中调用exec都会使进程中的所有线程终止
3. 在主线程中调用pthread_exit也可以终止线程,注意只终止主线程本身,而不终止其它线程```

实际上,每一个CPU的运行队列都包含两个优先级阵列:其中一个用于存放正在等待运行的线程,称为激活的优先级阵列,另一个是用于存放已经运行过但还未执行完成的线程,称为过期的优先级阵列
更确切的讲,优先级阵列是一个由若干链表组成的数组,每个链表包含的线程具有相同的优先级,当某个线程被放入到某个优先级阵列时,实际上就是放到与其优先级相同的链表的末尾

下一个运行的线程总是激活队列中优先级最高的线程
线程会因为等待某个事件的发生而被阻塞,放入到等待队列,进入到睡眠状态

多个CPU在同时运行时,平衡多个CPU之间的负载也是调度器的职责,它会尽量的让一个进程同一个CPU上运行,这样的好处是可以维持高速缓存的命中率,高效的使用就近的内存,等,可是,有时一个CPU需要运行太多的线程,导致多个CPU之间的负载不均衡,一些CPU过于忙碌,一些CPU过于闲置,由于内核会为每个CPU建立一个运行队列,所以线程间的迁移并不难,事实上,每个运行队列中都会保存对应的CPU负载系数,调度器可以根据这一系数了解并及时调节各个CPU的负载

互斥量:

互斥:同一时刻只允许一个线程处于临界区的约束,每个线程在进入临界区之前都必须先锁定某个对象,只有成功锁定对象的线程才允许进入临界区,否则就会阻塞,这个对象称为互斥对象,或者互斥量

所以,互斥量只有锁定和未锁定两种可能状态,互斥量只能被锁定一次,已处于锁定状态的互斥量不能被再次锁定,只有互斥量的所有者才能解锁

Note:互斥量也属于共享资源,必须能够被所有相关线程访问,所以,代表互斥量的变量或者常量一般不是局部的,但是为了尽量少的暴露程序的实现细节,要在满足上述的条件下最小化互斥量的访问权限

初始化互斥量总是在任何线程使用它之前,处于未锁定状态,若多个线程的代码中都包含了对同一个互斥量的初始化操作,必须保证该互斥量只被初始化一次

关于死锁,一般有2种通用的解决方式:
1. 试锁定-> 回退
当需要2个或者多个互斥量时,先锁定其中一个,然后试锁定其它互斥量,锁定失败,立即返回而不是阻塞在那里,稍后重试(需要用到系统线程库)
2. 固定顺序锁定

条件变量

条件变量是在共享的数据状态发生变化时通知其它因此而阻塞的线程,它总是与互斥量组合使用,
因为线程锁定互斥量,得到共享数据访问权的时候,数据的状态不一定恰好满足它的要求

单播通知:唤醒某一个等待线程
广播通知:唤醒所有等待线程

通知只是发出去,并不保证有等待线程收到,也不存储其信息,仅仅只是发送一个信号出去,也不会阻塞在那里等待,若无等待线程,通知会被直接丢弃

多线程中数据同步的两个重要工具就是互斥量和条件变量:
互斥量可以实现对临界区的保护,并阻止竟态条件的发生,条件变量作为补充,可以让多方协作更加高效的进行

线程安全:若一个代码块总能被多个线程并发的执行,且产生预期的结果,它就是线程安全的,否则就可能是非线程安全的
若一个函数的返回结果包含了共享数据,它就是非线程安全的,此时可以使用互斥量把相关代码保护起来,如果可能,还应该把它们隔离开来,若一个线程仅包含对共享数据的访问而非修改,可以不使用互斥量,但必须在使用时copy一个副本到自己的线程栈

编程时应该注意:
1. 控制临界区的纯度:尽量只包含共享数据
2. 控制临界区的粒度:过细的临界区会加大底层协调工作的难度,若存在多个相邻的临界区,可以考虑合并
3. 减少临界区执行时间:耗时操作应注意
4. 避免长时间持有互斥量
5. 优先使用原子操作,而不是互斥量

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值