当前位置: 首页 > news >正文

深入理解 Go 语言并发编程之系统调用底层原理

        用户协程是如何执行系统调用的?系统调用有可能会阻塞线程 M,如果所有的线程 M 都因系统调用阻塞了,这时候谁来调度协程呢?

1. 系统调用会阻塞线程吗

        系统调用会阻塞线程吗?在这回答这个问题之前,我们先模拟一个 Go 程序执行阻塞式系统调用的情况。

        第一个程序就是普通的 Go 程序,没有执行任何系统调用,代码如下所示:

package mainimport ("runtime""time"
)func main() {//设置逻辑处理器 P 的数目为 4runtime.GOMAXPROCS(4)//创建 10 个用户协程for i := 0; i < 10; i++ {go func() {for {}}()}time.Sleep(time.Second * 1000)
}

        上面的 Go 程序非常简单,本身没有任何意义,只是显式地设置逻辑处理器 P 的数目为 4,随后创建了多个用户协程。

        参考上面的结果,Go 程序总共创建了 5 个子线程,暂时记录下这个结果,待会和第二个程序的输出做一个对比。当然,你可能会好奇为什么实际的线程数大于逻辑处理器 P 的数目,这里我们大不必纠结背后的原因(Go 语言还会创建辅助线程)。

        第二个程序会执行一些阻塞式的系统调用,代码如下所示:

package main
import ("net""runtime""syscall""time"
)func main(){//目的 IP 地址var addr = "x.x.x.x"//设置逻辑处理器 P 的数目为 4runtime.GOMAXPROCS(4)for i :=0; i <10;i++{go func(){sa := ipv4Sockaddr(addr)fd,_:=syscall.Socket(syscall.AF_INET,syscall.SOCK_STREAM,syscall.IPPROTO_TCP)//阻塞式系统调用_= syscall.Connect(fd,sa)}()//普通的用户协程go func(){for {}}()}time.Sleep(time.Second*1000)
}//解析IP地址
func ipv4Scockaddr(addr string) syscall.Sockaddr {ip := net.ParseIP(addr)sa := &syscall.SockaddrInet4{Port:80}copy(sa.Addr[:],ip.To4())return sa
}

        上面的程序稍微有点复杂。同样,显式地设置逻辑处理器 P 的数目为 4,并且创建了多个用户协程,只是部分协程执行了一些阻塞式系统调用。注意,这里使用了原生的套接字相关系统调用,并没有使用 Go 语言基于 I/O 多路复用封装的函数(这些函数都是非阻塞调用,不会阻塞线程 M)。函数 syscall.Connect 对应的就是系统调用 connect,用于向远端地址发起 TCP 连接请求,那如果目的 IP 不存在或者与当前节点网络不通呢?这样是不是就会长时间阻塞线程 M 了呢?编译并运行 Go 程序,同时通过 pstree 命令查看该进程所有的线程,结果如下所示:

        参考上面的结果,这一次 Go 程序总共创建了 15个子线程,对比第一个程序的输出结果,多创建了 10 个子线程,为什么会多创建这么多子线程呢?是因为用户协程执行了系统调用 syscall.Connect,导致线程 M 阻塞了,Go语言才创建了更多的线程 M 吗?有可能是。

        再次回到最初的问题,系统调用阻塞线程 M 之后,Go 语言是如何处理的?参考上面两个程序的输出结果,我们是不是能猜测Go语言会创建更多的线程 M 用于调度协程?只是,线程 M 需要绑定逻辑处理器 P 才能调度协程,所以可想而知,Go语言还需要解除因系统调用而阻塞的线程与逻辑处理器 P 之间的绑定关系。  

2. 系统调用与调度器

        思考一下,既然系统调用有可能会阻塞线程 M 这一事实无法改变,那么在执行可能阻塞

//辅助线程 sysmon 每 10ms 执行一次该函数
func retake(now int64) uint32 {//遍历所有的逻辑处理器 Pfor i := 0;i< len(allp); i++{if s==_Psyscall {//如果不等于,说明系统调度已结否t := int64(_p_.syscalltick)if !sysretake && int64(pd.syscalltick) != t {pd.syscalltick = uint32(t)pd.syscallwhen = nowcontinue}//更改逻辑处理器 P 的状态if atomic.Cas(&_p_.status,s,_Pidle){//创建新的线程 M 以执行调度程序handoffp(_p_)}}}
}

        参考上面的代码,逻辑处理器 P 有两个变量:变量 syscalltick 用于计数,每执行一次系统调用都会加 1 ;变量 syscallwhen 记录最近一次系统调用的执行时间,当然这个时间其实不是真正的执行时间,可以理解为检测时间。检测的整体逻辑是,如果逻辑处理器 P 处于系统调用状态(_Psyscall),并且自从上次检测之后计数器 syscalltick 没有发生变化,说明当前逻辑处理器  P 近 10ms 内一直处于系统调用状态,即与其绑定的线程 M 近 10ms 内一直处于阻塞状态。此时,辅助线程就会将逻辑处理器 P 的状态修改为空闲状态(_Pidle),并调用函数 runtime.handoffp 创建新的线程  M 以执行调度程序。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • IP子网划分之网络工程师软考中级
  • 分子属性梯度引导的3D分子生成扩散模型 TAGMOL - 评测
  • 【celery-2】python-Django发送邮件-短信-钉钉通知
  • 软件架构设计——关联对象
  • 【初阶数据结构】顺序表和链表算法题(上)
  • Python和MATLAB及R平均意见得分导图
  • # 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群
  • 太阳方向角/高度角/赤纬角/太阳时角/真平太阳时差/理论计算方法(matlab)
  • Vue中的this.$emit()方法详解【父子组件传值常用】
  • 影刀上传文件api
  • 08:导数-导数的定义及几何意义
  • 《深度学习》OpenCV 计算机视觉入门 (上篇)
  • P(查准率) R(查全率) AP mAP最通俗准确的讲解
  • Django使用视图动态输出CSV以及PDF的操作详解例子解析
  • sheng的学习笔记-AI-生成式方法
  • 30秒的PHP代码片段(1)数组 - Array
  • Angular 4.x 动态创建组件
  • Electron入门介绍
  • EOS是什么
  • ES10 特性的完整指南
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • Java,console输出实时的转向GUI textbox
  • Javascript 原型链
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • NSTimer学习笔记
  • Rancher如何对接Ceph-RBD块存储
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Shadow DOM 内部构造及如何构建独立组件
  • Vue实战(四)登录/注册页的实现
  • Vultr 教程目录
  • windows下如何用phpstorm同步测试服务器
  • 笨办法学C 练习34:动态数组
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 前端知识点整理(待续)
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 我有几个粽子,和一个故事
  • 小李飞刀:SQL题目刷起来!
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ​flutter 代码混淆
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​Java基础复习笔记 第16章:网络编程
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (06)金属布线——为半导体注入生命的连接
  • (07)Hive——窗口函数详解
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (poj1.2.1)1970(筛选法模拟)
  • (八)Docker网络跨主机通讯vxlan和vlan