Day863.协程 -Java 并发编程实战

news2024/11/15 23:55:28

协程

Hi,我是阿昌,今天学习记录的是关于协程的内容。

Java 语言里解决并发问题靠的是多线程,但线程是个重量级的对象,不能频繁创建、销毁,而且线程切换的成本也很高,为了解决这些问题,Java SDK 提供了线程池

然而用好线程池并不容易,Java 围绕线程池提供了很多工具类,这些工具类学起来也不容易。那有没有更好的解决方案呢?

Java 语言里目前还没有,但是其他语言里有,这个方案就是协程(Coroutine)。

可以把协程简单地理解为一种轻量级的线程。

从操作系统的角度来看,线程是在内核态中调度的,而协程是在用户态调度的,所以相对于线程来说,协程切换的成本更低。

协程虽然也有自己的栈,但是相比线程栈要小得多,典型的线程栈大小差不多有 1M,而协程栈的大小往往只有几 K 或者几十 K。

所以,无论是从时间维度还是空间维度来看,协程都比线程轻量得多。

支持协程的语言还是挺多的,例如 Golang、Python、Lua、Kotlin 等都支持协程。

下面就以 Golang 为代表,看看协程是如何在 Golang 中使用的。


一、Golang 中的协程

在 Golang 中创建协程非常简单,在下面的示例代码中,要让 hello() 方法在一个新的协程中执行,只需要go hello(“World”) 这一行代码就搞定了。

可以对比着想想在 Java 里是如何“辛勤”地创建线程和线程池的吧,感觉一直都是:

每次写完 Golang 的代码,就再也不想写 Java 代码了。

import (
  "fmt"
  "time"
)
func hello(msg string) {
  fmt.Println("Hello " + msg)
}
func main() {
    //在新的协程中执行hello方法
  go hello("World")
    fmt.Println("Run in main")
    //等待100毫秒让协程执行结束
  time.Sleep(100 * time.Millisecond)
}

在Thread-Per-Message 模式,利用协程能够很好地实现 Thread-Per-Message 模式。

Thread-Per-Message 模式非常简单,其实越是简单的模式,功能上就越稳定,可理解性也越好。

下面的示例代码是用 Golang 实现的 echo 程序的服务端,用的是 Thread-Per-Message 模式,为每个成功建立连接的 socket 分配一个协程,相比 Java 线程池的实现方案,Golang 中协程的方案更简单。

import (
  "log"
  "net"
)

func main() {
    //监听本地9090端口
  socket, err := net.Listen("tcp", "127.0.0.1:9090")
  if err != nil {
    log.Panicln(err)
  }
  defer socket.Close()
  for {
    //处理连接请求  
    conn, err := socket.Accept()
    if err != nil {
      log.Panicln(err)
    }
        //处理已经成功建立连接的请求
    go handleRequest(conn)
  }
}
//处理已经成功建立连接的请求
func handleRequest(conn net.Conn) {
  defer conn.Close()
  for {
    buf := make([]byte, 1024)
        //读取请求数据
    size, err := conn.Read(buf)
    if err != nil {
      return
    }
    //回写相应数据  
    conn.Write(buf[:size])
  }
}

二、利用协程实现同步

其实协程并不仅限于实现 Thread-Per-Message 模式,它还可以将异步模式转换为同步模式。

异步编程虽然近几年取得了长足发展,但是异步的思维模式对于普通人来讲毕竟是有难度的,只有线性的思维模式才是适合所有人的。而线性的思维模式反映到编程世界,就是同步。

在 Java 里使用多线程并发地处理 I/O,基本上用的都是异步非阻塞模型,这种模型的异步主要是靠注册回调函数实现的,那能否都使用同步处理呢?显然是不能的。

因为同步意味着等待,而线程等待,本质上就是一种严重的浪费。

不过对于协程来说,等待的成本就没有那么高了,所以基于协程实现同步非阻塞是一个可行的方案。

OpenResty 里实现的 cosocket 就是一种同步非阻塞方案,借助 cosocket 可以用线性的思维模式来编写非阻塞的程序。

下面的示例代码是用 cosocket 实现的 socket 程序的客户端,建立连接、发送请求、读取响应所有的操作都是同步的,由于 cosocket 本身是非阻塞的,所以这些操作虽然是同步的,但是并不会阻塞。

-- 创建socket
local sock = ngx.socket.tcp()

-- 设置socket超时时间
sock:settimeouts(connect_timeout, send_timeout, read_timeout)

-- 连接到目标地址
local ok, err = sock:connect(host, port)
if not ok then
-  -- 省略异常处理
end

-- 发送请求
local bytes, err = sock:send(request_data)
if not bytes then
  -- 省略异常处理
end

-- 读取响应
local line, err = sock:receive()
if err then
  -- 省略异常处理
end

-- 关闭socket
sock:close()   
-- 处理读取到的数据line
handle(line)

三、结构化并发编程

Golang 中的 go 语句让协程用起来太简单了,但是这种简单也蕴藏着风险。

要深入了解这个风险是什么,就需要先了解一下 goto 语句的前世今生。

各种各样的编程语言书籍中都会谈到不建议使用 goto 语句,原因是 goto 语句会让程序变得混乱,对于这个问题也没有多想,不建议用那就不用了。

那为什么 goto 语句会让程序变得混乱呢?混乱具体指的又是什么呢?

多年之后,后来才了解到所谓的混乱指的是代码的书写顺序和执行顺序不一致

代码的书写顺序,代表的是思维过程,如果思维的过程与代码执行的顺序不一致,那就会干扰代码的理解。

我们的思维是线性的,傻傻地一条道儿跑到黑,而 goto 语句太灵活,随时可以穿越时空,实在是太“混乱”了。

首先发现 goto 语句是“毒药”的人是著名的计算机科学家艾兹格·迪科斯彻(Edsger Dijkstra),同时他还提出了结构化程序设计

在结构化程序设计中,可以使用三种基本控制结构来代替 goto,这三种基本的控制结构就是今天广泛使用的

  • 顺序结构
  • 选择结构
  • 循环结构

顺序结构

选择结构

循环结构(while)

循环结构(do while)

这三种基本的控制结构奠定了今天高级语言的基础,如果仔细观察这三种结构,会发现它们的入口和出口只有一个,这意味它们是可组合的,而且组合起来一定是线性的,整体来看,代码的书写顺序和执行顺序也是一致的。

以前写的并发程序,是否违背了结构化程序设计呢?

这个问题以前并没有被关注,但是最近两年,随着并发编程的快速发展,已经开始有人关注了,而且剑指 Golang 中的 go 语句,指其为“毒药”,类比的是 goto 语句。详情可以参考相关的文章。

Golang 中的 go 语句不过是快速创建协程的方法而已,这篇文章本质上并不仅仅在批判 Golang 中的 go 语句,而是在批判开启新的线程(或者协程)异步执行这种粗糙的做法,违背了结构化程序设计,Java 语言其实也在其列。

当开启一个新的线程时,程序会并行地出现两个分支,主线程一个分支,子线程一个分支,这两个分支很多情况下都是天各一方、永不相见。

而结构化的程序,可以有分支,但是最终一定要汇聚,不能有多个出口,因为只有这样它们组合起来才是线性的。


四、总结

最近几年支持协程的开发语言越来越多了,Java OpenSDK 中 Loom 项目的目标就是支持协程,相信不久的将来,Java 程序员也可以使用协程来解决并发问题了。

计算机里很多面向开发人员的技术,大多数都是在解决一个问题:易用性

协程 作为一项并发编程技术,本质上也不过是解决并发工具的易用性问题而已。

对于易用性,最重要的就是要适应我们的思维模式,在工作的前几年,并没有怎么关注它,但是最近几年思维模式已成为重点关注的对象。因为思维模式对工作的很多方面都会产生影响,例如质量

一个软件产品是否能够活下去,从质量的角度看,最核心的就是代码写得好。

那什么样的代码是好代码呢?最根本的是可读性好

可读性好的代码,意味着大家都可以上手,而且上手后不会大动干戈。

那如何让代码的可读性好呢?很简单,换位思考,用大众、普通的思维模式去写代码,而不是炫耀自己的各种设计能力。

好的代码,就像人民的艺术一样,应该是为人民群众服务的,只有根植于广大群众之中,才有生命力。


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

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

相关文章

C++设计模式(4)——策略模式

策略模式 亦称: Strategy 意图 策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 问题 一天, 你打算为游客们创建一款导游程序。 该程序的核心…

什么是CNCF云原生

一、CNCF简介 CNCF:全称Cloud Native Computing Foundation(云原生计算基金会),成立于2015年12月11日,是一个开源软件基金会,它致力于云原生(Cloud Native)技术的普及和可持续发展。…

golang 错误处理channel+error真的香

官方推荐golang中错误处理当做值处理, 既然是值那就可以在channel中传输,本文带你看看golang中channelerror来做异步错误处理有多香,看完本文还会觉得golang的错误处理相比java try catch一点优势都没有吗? 场景 如下&#xff0…

LeetCode刷题笔记 - JavaScript(二)

文章目录1.剑指 Offer 60. n个骰子的点数2.面试题67. 把字符串转换成整数3.面试题59 - II. 队列的最大值剑指 Offer 60. n个骰子的点数 面试题67. 把字符串转换成整数 面试题59 - II. 队列的最大值 1.剑指 Offer 60. n个骰子的点数 把n个骰子扔在地上,所有骰子朝上一…

Java运行机制

java的运行机制 Java程序的运行机制分为编写、编译和运行三个步骤。 1.编写 编写是指在Java开发环境中进行程序代码的编辑,最终生成后缀名为“.java”的Java源文件。 2.编译 编译是指使用Java编译器对源文件进行错误排查的过程,编译后将生成后缀名为…

一篇文章带你熟悉Ajax

文章目录一、AJAX 简介二、创建 AJAX 的基本步骤1. 创建 XMLHttpRequest 对象2.向服务器发送请求3.服务器响应状态一、AJAX 简介 ☀️AJAX 的英文全称为 Asynchronous JavaScript And XML,Asynchronous 是异步的意思。何为异步呢?在这里异步是指通过 AJA…

IT运维服务体系的总体架构是什么?

大家好,我是技福的小咖老师。 今天我们来简单介绍一下IT运维服务体系的总体架构。 运维服务体系由运维服务制度、运维服务流程、运维服务组织、运维服务队伍、运维技术服务平台以及运行维护对象六部分组成,涉及制度、人、技术、对象四类因素。制度是规…

每日一题-力扣(leetcode)2368. 受限条件下可到达节点的数目

题目描述 现有一棵由 n 个节点组成的无向树,节点编号从 0 到 n - 1 ,共有 n - 1 条边。 给你一个二维整数数组 edges ,长度为 n - 1 ,其中 edges[i] [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。另给你一个整数数组 restr…

激光雷达对植被冠层结构和SIF同时探测展望

前言陆表植被在全球碳循环中起着不可替代的作用。但现阶段,人们对气候变化与植被生态理化功能的关系的研究还不够完善。为了提高气候预测以及缓解气候恶化的速率,对植被参数比如:叶面积指数(leaf)、植被冠层结构&#…

JavaScript JSON序列化和反序列化

文章目录JavaScript JSON序列化和反序列化概述JSON序列化JSON.stringify()仅一个参数使用使用2个参数使用3个参数其他自定义toJson序列化顺序反序列化JSON.parse()仅一个参数使用使用2个参数eval()JavaScript JSON序列化和反序列化 概述 JSON数据在网络传输时存在两种类型&am…

【虹科云展厅】虹科赋能汽车智能化云展厅专题回顾

虹科赋能汽车智能化云展厅 聚焦前沿技术,【虹科赋能汽车智能化云展厅】正式上线,本次云展厅围绕“汽车以太网/TSN、汽车总线、智能网联、电子测试与验证、自动驾驶”等核心话题,为您带来如临展会现场般的讲演与介绍,更有技术工程…

PromQL之选择器和运算符

平台统一监控的介绍和调研直观感受PromQL及其数据类型PromQL之选择器和运算符 PromQL 匹配器 相等匹配器() 选择与提供的字符串完全相同的数据 例:筛选出id“G1 Eden Space” 的数据 jvm_memory_used_bytes{id"G1 Eden Space"}…

Elasticsearch高级查询—— 匹配查询文档

目录一、初始化文档数据二、匹配查询文档示例2.1、概述2.2、示例一、初始化文档数据 在 Postman 中,向 ES 服务器发 POST 请求 :http://localhost:9200/user/_doc/1,请求体内容为: {"name":"张三","age&…

知识图谱与神经网络,神经调节知识网络图

1、图立方和知识图谱的区别和联系与区别 图网络,即Natural Graph,是基于世界各实体之间的自然关系表示而得到的图,他们的节点一般是某个特定网络中的实体(人、物理机、分子)。例如:社交网络、通信网络、蛋…

阿里云-ODPS SQL-日常开发日期、字符、数学运算、聚合函数函数使用技巧

文章目录1、背景2、 数据处理2.1、OLTP与OLAP概念2.2、OLTP与OLAP区别3、日常开发常用函数3.1、日期函数3.2、数学运算函数3.3、字符串处理函数3.4、聚合函数1、背景 数据仓库,是一个面向主题的、集成的、随时间变化的、信息本身相对稳定的数据集合。 数据仓库从Or…

2-Node.js 内置模块

Node.js 内置模块 简介 之前说过,Node.js 中重要的两句话是 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 上面两句话,可以使用下面的图片来具体认识。…

【机器学习 - 5】:多元线性回归

文章目录多元线性回归多元线性回归公式推导举例:波士顿房价取特征值RM为例取所有特证为例多元线性回归 多元线性回归方程:特征值为两个或两个以上。 以下是多元线性回归的模型,我们需要求出theta,使得真实值和预测值的差值最小。 …

2023寒假算法集训营1

A. World Final? World Cup! (I) (模拟、枚举) 题意: 给定一个长度为 10 的01串,表示 A、B 双方的点球情况,1 表示罚进,0 表示罚不进。 A 先手,交替罚点球,各罚五次。 得分多者…

C语言字符串操作函数(库函数)及其实现

库函数 函数介绍及模拟实现 1.1strlen 1.2strcpy 1.3strcat 1.4strcmp 1.5strncpy 1.6strncat 1.7strncmp 1.8strstr 1.9strtok 1.10strerror 1.11memcpy 1.12memmove 1.13memcmp 小结 本章重点: 重点介绍处理字符串和字符串的库函数的使用和注意事项…

剑指offer

剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字…