C#代替go采用的CSP并发模型实现

目录
  • CSP(Communicating sequential processes)
  • 在Go中的CSP
  • 协程(提升并发的利器)
    • 线程
    • 线程的开销
    • 回归协程
    • 协程的目的
  • C#中的协程
  • C#中的CSP
  • Go协程与.NET协程的区别?
  • 写在最后

说起Golang(后面统称为Go),就想到他的高并发特性,在深入一些就是 Goroutine。在大家被它优雅的语法和简洁的代码实现的高并发程序所折服时,其实C#/.NET也可以很容易的做到。今天我们来参照Go,来用C#实现它所采用的的CSP并发模型。

CSP(Communicating sequential processes)

这东西我一开始以为很简单,后面差了资料发现它独树一帜,自己是一门语言,也是一套理论。这边我不深入的对它做过多的见解,我怕耽误大家=_=,大家可以看看wiki。

wiki:https://en.wikipedia.org/wiki/Communicating_sequential_processes

我们从Go的角度对它进行一些分析,摘抄一段概要:

“用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。”

好了,单独写出 CSP 是为了让大家了解这是一套独立于语言的东西,大家有兴趣可以查看wiki和搜索一些其它资料。

在Go中的CSP

Channel(通道)

Goroutine(不知道怎么翻译,大家可以理解成一个“工作者”,不是工作者线程。本质是实现了协程。)

协程(提升并发的利器)

大家都很明白线程能做什么,但协程是个什么东西?比起线程又如何呢?

线程

我们重新思考一些东西。

CPU:核心、超线程

OS:线程

编程语言:线程池

这边不做细讲,只是大概点到一下。

我们所做的任何计算都要经由CPU计算,而CPU的核数直接决定了我们能给CPU执行几件事情。

我们现在所常用的OS内部都有一个轮询,用时间片的形式来分配任何轮流使用CPU执行计算,线程就是这些任务的载体。

这块的概念非常庞大(还有牵扯到,什么是并发,什么事并行),本文的重点不是这些,大家有兴趣后面可以单独开一篇文章来解释这块的内容。

回归本文,现在我们知道线程是操作系统级别用来共享CPU的一种技术实现,多线程编程早在各大语言遍地开花,被用的惟妙惟肖,百花齐放。

那么为什么需要协程呢?

线程的开销

这块又是一个大知识点,这边也不多做介绍。

大家只要明白,线程并不是廉价的,一个线程的创立有至少两点的开销

  • 内存
  • 调度器压力(线程上下文切换等)

线程是可以持有逻辑数据的(比如,HttpContext.Current,等对象)所以必定是占用内存的(至于占用了多少内存不同的语言和OS不一样)

如果一个CPU是4核的,同时就只能处理4件任务,一个OS的线程越多他们轮训一整圈所耗的时间就更长。而每次调度线程时都需要复制当前线程上下文的状态,再去读取准备调度线程上下文的状态。

这边可以看到最后一点,有时候多线程反而会比单线程更加的慢,所以多线程提升性能本质上其实是假的。多线程并不会提升程序性能。

我知道这边肯定有人会心存疑问,绝大数的人都说用多线程来提升性能,为什么这边说多线程会比单线程慢?

我们这边想一下:PHP 和 NodeJS,PHP默认不支持多线程,NodeJS采用单线程事件轮询,他们的效率比拥有多线程的语言低吗?并不会。

多线程之所以快是因为作弊,别人一个人干的事情你叫两个人去干当然会比单线程快。这也有非常大的限制,多线程所执行的东西尽可能避免共享,不然你的效率还是可能不如单线程。

这边说的有点跑题,这块的内容实在太大,大家只要知道,线程即使不昂贵也绝不廉价。

针对这个问题,各大语言都推出了一个叫做线程池的技术,我申请一批线程,持有他,等到有任务的时候直接使用,这样我就不会频繁的创建和销毁线程了。这样大大提升了效率。

在.NET中,很早就提倡任何需要线程的时刻都使用 ThreadPool。

ps:现在觉大多数(我还没见过)的语言(runtime)中,线程与操作系统的线程是一一对应的。

回归协程

协程与线程是多对一的关系,有多个协程会对应到一根线程上。跟线程和CPU是一样的关系。

线程是为了共享CPU,而协程是为了共享线程。

协程是应用层面的自有“线程”实现。也就是说在不改变OS的线程逻辑下,自己构建了一套 “线程”系统。

为什么不直接改动OS的线程,让其更轻?我个人觉得 1是历史兼容性问题,2是必要性问题,线程是一个很好的抽象逻辑。实现协程完全可以通过线程来完成。

协程的目的

我们来思考一个场景

抓取百度、google、bing的html。

多线程的做法是

启动三个线程,分别对百度、google、bing发起HTTP GET请求。这时候使用了三个线程。

协程的做法是(极端)

启动一个线程对百度发起HTTP GET请求,将任务放入队列,在对google发起HTTP GET请求,将任务放入队列,在对bingHTTP GET请求将任务放入队列。

这时候只需要使用一个线程(极端情况下,其实大多数实现来说至少需要两个线程,因为需要有一个后台线程去监听任务队列,当任务完成后再分配一个可用线程去处理下面的逻辑)

为什么说极端情况下?因为协程有时候也可能会与线程一一对应,比如你的CPU有8个核心,同时跑4个协程也有可能会分配4根线程单独去处理这4个任务,这主要取决于调度算法。

总结:协程是为了提升线程利用率,减少线程的无用功(大多数是IO堵塞),协程也更适合IO密集型的场景。

C#中的协程

可以看到,3个任务是异步执行的,但都由线程4来处理,也就是说三个异步任务只用了一根线程。

C#中的CSP

讲了这么大篇幅的协程,终于回归了今天的主题。

其实单单实现CSP来说根本不用理清线程和协程。但今天主要对比的是Go中的CSP,所以如果没有协程基本是没有意义的。

C#如何对应,CSP中最重要的Channel呢?

答案就是:BlockingCollection<T>

我们来看一个例子

抓取一批网站并输出网站的title

发起 HTTP GET 请求 和分析Title的代码逻辑如下:

主程序的代码如下:

执行逻辑

  • 启用一个生产者协程来根据url生产对应的html、同时使用主线程消费队列内的内容(异步)
  • 每个url单独起一个协程来发起HTTP GET请求
  • 生产者协程等待所有url的html全部加载完成
  • 标志队列完成
  • 主线程退出

执行结果如下:

Go协程与.NET协程的区别?

去除实现上的一些逻辑,本质上没太多区别。

但Go有一个天生优势就是它是新时代的语言,抛弃了线程。也就是说Go层面没有线程的东西,它只有协程。

但.NET中线程已经拥有了好多年,大量的类库、驱动使用线程来完成。

所以你在上一层就算使用了协程,执行到底部不一定只有一根线程来完成,底部可以自己创建线程来运行逻辑,今天篇幅关系不做过多说明。后面我们在介绍这块的内容。

写在最后

最后总结一个要点,多线程、协程并不能提升性能,它们所达到的目的只是提高CPU利用率。

今天本来想详细写BlockingCollection<T>的使用说明,但协程等概念占了大量的篇幅,后面我们再来详细介绍.NET中的异步编程。

以上就是C#代替go采用的CSP并发模型实现的详细内容,更多关于C#实现go采用CSP并发模型的资料请关注我们其它相关文章!

(0)

相关推荐

  • 分析Go语言中CSP并发模型与Goroutine的基本使用

    目录 一.并发实现模型 1.1.多进程 1.2.多线程 1.3.协程 二.共享内存与CSP 三.Goroutine 一.并发实现模型 1.1.多进程 在之前的文章当中我们曾经介绍过,进程是操作系统资源分配的最小单元.所以多进程是在操作系统层面的并发模型,因为所有的进程都是有操作系统的内核管理的.所以每个进程之间是独立的,每一个进程都会有自己单独的内存空间以及上下文信息,一个进程挂了不会影响其他进程的运行.这个也是多进程最大的优点,但是它的缺点也很明显. 最大的缺点就是开销很大,创建.销毁进程的开

  • C#使用队列(Queue)解决简单的并发问题

    本文通过实例,更具体的讲解了队列,队列(Queue)代表了一个先进先出的对象集合.当您需要对各项进行先进先出的访问时,则使用队列.当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队. 有一个场景:一个抢购的项目,假设有5件商品,谁先抢到谁可以买,但是如果此时此刻(这里的此时此刻假设是相同的时间),有100人去抢这个商品,如果使用平时的方法会出现什么情况呢?你懂的,这里所说是就是有关并发的问题. 平时我们去超市购物去结账的时候就是排队,这里我们先让抢购人排好队,按时间,谁先点击的抢购

  • C#并发容器之ConcurrentDictionary与普通Dictionary带锁性能详解

    结果已经写在注释中 static void Main(string[] args) { var concurrentDictionary = new ConcurrentDictionary<int, string>(); var dictionary = new Dictionary<int, string>(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 1000000; i++) { lock (

  • C#实现控制线程池最大数并发线程

    1. 实验目的: 使用线程池的时候,有时候需要考虑服务器的最大线程数目和程序最快执行所有业务逻辑的取舍. 并非逻辑线程越多也好,而且新的逻辑线程必须会在线程池的等待队列中等待 ,直到线程池中工作的线程执行完毕, 才会有系统线程取出等待队列中的逻辑线程,进行CPU运算. 2.  解决问题: <a>如果不考虑服务器实际可支持的最大并行线程个数,程序不停往线程池申请新的逻辑线程,这个时候我们可以发现CPU的使用率会不断飙升,并且内存.网络带宽占用也会随着逻辑线程在CPU队列中堆积,而不断增大. &l

  • C#编程高并发的几种处理方法详解

    并发(英文Concurrency),其实是一个很泛的概念,字面意思就是"同时做多件事",不过方式有所不同.在.NET的世界里面,处理高并发大致有以下几种方法: 1.异步编程 异步编程就是使用future模式(又称promise)或者回调机制来实现(Non-blocking on waiting).如果使用回调或事件来实现(容易callback hell),不仅编写这样的代码不直观,很快就容易把代码搞得一团糟. 不过在.NET 4.5 及以上框架中引入的async/await关键字(在.

  • C#代替go采用的CSP并发模型实现

    目录 CSP(Communicating sequential processes) 在Go中的CSP 协程(提升并发的利器) 线程 线程的开销 回归协程 协程的目的 C#中的协程 C#中的CSP Go协程与.NET协程的区别? 写在最后 说起Golang(后面统称为Go),就想到他的高并发特性,在深入一些就是 Goroutine.在大家被它优雅的语法和简洁的代码实现的高并发程序所折服时,其实C#/.NET也可以很容易的做到.今天我们来参照Go,来用C#实现它所采用的的CSP并发模型. CSP(

  • Go语言CSP并发模型goroutine channel底层实现原理

    目录 Go的CSP并发模型(goroutine + channel) 1.goroutine goroutine的优点: 2.channel 无缓存channel 有缓存channel 3.Go并发模型的底层实现原理 4.一个CSP例子 参考Go的CSP并发模型实现:M, P, G Go语言是为并发而生的语言,Go语言是为数不多的在语言层面实现并发的语言. 并发(concurrency):多个任务在同一段时间内运行. 并行(parallellism):多个任务在同一时刻运行. Go的CSP并发模

  • Go语言CSP并发模型实现MPG

    目录 Golang调度机制 并发(concurrency)和并行(parallellism) Go的CSP并发模型 Go并发模型的实现原理 用户级线程模型 内核级线程模型 两级线程模型 Go线程实现模型MPG 抛弃P(Processor) 均衡的分配工作 Golang调度机制 最近抽空研究.整理了一下Golang调度机制,学习了其他大牛的文章.把自己的理解写下来.如有错误,请指正!!! golang的goroutine机制有点像线程池: 一.go 内部有三个对象: P对象(processor)

  • 示例剖析golang中的CSP并发模型

    目录 1. 相关概念: 2. CSP (通信顺序进程) 3. channel:同步&传递消息 4. goroutine:实际并发执行的实体 5. golang调度器 1. 相关概念: 用户态:当一个进程在执行用户自己的代码时处于用户运行态(用户态) 内核态:当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),引入内核态防止用户态的程序随意的操作内核地址空间,具有一定的安全保护作用.这种保护模式是通过内存页表操作等机制,保证进程间的地址空间不会相互冲突,一个进程的操作不会修改另一个

  • CSP communicating sequential processes并发模型

    目录 前言 GO并发模型的实现原理 内核级线程模型 两级线程模型 Go线程实现模型MPG Goroutine 小结 优点: 缺点: 前言 https://www.jb51.net/article/228730.htm 请记住下面这句话: DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING. “不要以共享内存的方式来通信,相反,要通过通信来共享内存.” 普通的线程并发模型,就是像Java.C++.

  • Golang CSP并发机制及使用模型

    目录 CSP并发模型 Golang CSP Channel Goroutine Goroutine 调度器 总结 今天介绍一下 go语言的并发机制以及它所使用的CSP并发模型 CSP并发模型 CSP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型. CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel. Golang CSP Golang 就是借用CSP模型的一些概念为之实现并发进行理论

  • Node.js 与并发模型的详细介绍

    目录 进程 线程 内核态线程 用户态线程 轻量级进程(LWP) 小结 协程 I/O 模型 阻塞 I/O 非阻塞 I/O 同(异)步 I/O Node.js 的并发模型 总结 前言: Node.js 现在已成为构建高并发网络应用服务工具箱中的一员,何以 Node.js 会成为大众的宠儿?本文将从进程.线程.协程.I/O 模型这些基本概念说起,为大家全面介绍关于 Node.js 与并发模型的这些事. 进程 我们一般将某个程序正在运行的实例称之为进程,它是操作系统进行资源分配和调度的一个基本单元,一般

  • Go语言并发模型的2种编程方案

    概述 我一直在找一种好的方法来解释 go 语言的并发模型: 不要通过共享内存来通信,相反,应该通过通信来共享内存 但是没有发现一个好的解释来满足我下面的需求: 1.通过一个例子来说明最初的问题 2.提供一个共享内存的解决方案 3.提供一个通过通信的解决方案 这篇文章我就从这三个方面来做出解释. 读过这篇文章后你应该会了解通过通信来共享内存的模型,以及它和通过共享内存来通信的区别,你还将看到如何分别通过这两种模型来解决访问和修改共享资源的问题. 前提 设想一下我们要访问一个银行账号: 复制代码 代

  • PHP下用Swoole实现Actor并发模型的方法

    什么是Actor? Actor对于PHPer来说,可能会比较陌生,写过Java的同学会比较熟悉,Java一直都有线程的概念(虽然PHP有Pthread,但不普及),它是一种非共享内存的并发模型,每个Actor内的数据独立存在,Actor之间通过消息传递的形式进行交互调度,且Actor是一种高度抽象化的编程模型,非常适合于游戏.硬件行业. Swoole协程与信箱 得益于Swoole4.x,我们可以基于Swoole的协程与Channel快速实现一个信箱模式调度.模拟代码如下: use Swoole\

随机推荐