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

并发编程的几种形式

在并发编程中我们经常听到以下一些概念,今天我将尝试进行阐述。

一、并发

同时干多件事情,这就是并发的作用。

web服务器可以利用并发同时处理大量用户的请求。

只要我们需要程序同时干多件事情,我们就需要并发。

二、多线程

并发编程的一种形式,其采用多个线程执行程序。

线程是一个独立的运行单元,每个进程内部有多个线程,每个线程可以各自同时执行指令。

每个线程有自己独立的栈,但是与进程内的其他线程共享内存。

线程池是线程更广泛的一种应用形式,其维护着一定数量的工作线程,这些线程等待着执行分配下来的任务。线程池可以随时监测线程的数量

线程池催生了另外一种重要的并发形式:并行处理。

多线程并不是并发编程的唯一形式,虽然.NET和Java等语言框架都对底层线程类型提供了支持,但是对开发人员并不友好,最新的.NET和Java

都提供了更高级别的抽象,让我们开发并发程序更加方便高效。

三、并行处理

将大块的任务分割成相互独立的小块,并分配给多个同时运行的线程处理。

并行处理采用多线程,提高了处理器的利用效率。

并行编程通常不适合服务器系统,服务器本身都具有并发处理能力。

数据并行可以处理大量的彼此独立的数据,比如Hadoop等大数据处理框架。

任务并行可以将彼此独立的拆分任务同时执行。

下边看下.NET中提供的并行编程

使用Parallel.ForEach进行数据并行

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
    Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

 

使用Parallel.ForEach进行数据并行

IEnumerable<bool> PrimalityTest(IEnumerable<int> values)
{
    return values.AsParallel().Select(val => IsPrime(val));
}

 

数据的独立性是并行性最大化的前提,否为了确保安全性就需要引入同步,从而影响程序的并行程度。

只能最大程度的并行,但是总是消灭不了同步,数据并行的结果总是需要进行聚合,Parallel实现了响应的重载及map/reduce函数。

Parallel类的Invoke方式可以实现任务并行

复制代码
void ProcessArray(double[] array)
{
    Parallel.Invoke(
        () => ProcessPartialArray(array, 0, array.Length / 2),
        () => ProcessPartialArray(array, array.Length / 2, array.Length)
    );
}
void ProcessPartialArray(double[] array, int begin, int end)
{
    // CPU 密集型的操作......
}        
复制代码

 

 

任务并行也依赖任务的独立性,同时要注意闭包对变量的引用,即使是值类型也是引用。

任务不要特别短,也不要特别长。如果任务太短,把数据分割进任务和在线程池中调度任务的开销会很大。如果任务太长,线程池就不能进行

有效的动态调整以达到工作量的平衡。

 

四、异步编程

并发编程的一种形式,它采用future模式或者回调(callback)机制,以避免产生不必要的线程。

回调和事件作为老式的异步编程,在服务器端和GUI中都有广泛的应用。

一个future或者promise代表一些即将完成的操作,在.NET中的TPL中有Task和Task<TResult>,在Java中有FutureTask,在JS中有fetch(新版Firefox

Chorm支持)。

异步编程可以在启动一个操作之后,可以继续执行而不会被阻塞,待操作执行完之后,通知future或者执行回调函数,以便告知操作结束。

异步编程是一种功能强大的并发形式,但传统的异步编程特别复杂而且不易于代码维护。.NET和Node.JS支持的async和await,让异步编程变得

跟串行编程一样简单。

 

下面看下.NET 的两个关键字: async 和 await 。 async 关键字加在方法声明上,它的主要目的是使方法内的 await 关键字生效。如果 async 方法有

返回值,应返回 Task<T> ;如果没有返回值,应返回 Task 。这些task 类型相当于 future,用来在异步方法结束时通知主程序。下边的例子同时请求两

个服务地址,只要有一个返回结果即可完成。

 

复制代码
// 返回第一个响应的 URL 的数据长度。
private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
{
    var httpClient = new HttpClient();
    // 并发地开始两个下载任务。
    Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
    Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
    // 等待任意一个任务完成。
    Task<byte[]> completedTask =
    await Task.WhenAny(downloadTaskA, downloadTaskB);
    // 返回从 URL 得到的数据的长度。
    byte[] data = await completedTask;
    return data.Length;
}
复制代码

 

 

五、响应式编程

一种声明式的编程模式,程序在该模式中对事件进行响应。

程序针对不同的事件进行响应并更新自己的状态。

异步编程针对启动的操作,响应编程针对可以任何事件重复发生的异步事件。

响应式编程基于“可观察的流”(observable stream)。一旦申请了可观察流,就可以收到任意数量的数据项( OnNext ),并且流在结束时会发出一个错误(

OnError )或一个结束的通知( OnCompleted )。实际的接口如下

复制代码
interface IObserver<in T>
{
    void OnNext(T item);
    void OnCompleted();
    void OnError(Exception error);
}

interface IObservable<out T>
{
    IDisposable Subscribe(IObserver<T> observer);
}
复制代码

 

微软的 Reactive Extensions(Rx)库已经实现了所有接口。下面的代码中,前面是我们不熟悉的操作符( Interval 和 Timestamp ),最后是一个 Subscribe ,

但是中间部分是我们在 LINQ 中熟悉的操作符: Where 和 Select 。LINQ 具有的特性,Rx也都有。Rx 在此基础上增加了很多它自己的操作符,特别

是与时间有关的操作符:

Observable.Interval(TimeSpan.FromSeconds(1))
.Timestamp()
.Where(x => x.Value % 2 == 0)
.Select(x => x.Timestamp)
.Subscribe(x => Trace.WriteLine(x));

 

上面的代码中,首先是一个延时一段时间的计数器( Interval ),随后、后为每个事件加了一个时间戳( Timestamp )。接着对事件进行过滤,只包含偶数

( Where ),选择了时间戳的值( Timestamp ),然后当每个时间戳值到达时,把它输入调试器( Subscribe )。可观察流的定义和其订阅是互相独立的。

上面最后一个例子与下面的代码等效:

复制代码
IObservable<DateTimeOffset> timestamps =
Observable.Interval(TimeSpan.FromSeconds(1))
.Timestamp()
.Where(x => x.Value % 2 == 0)
.Select(x => x.Timestamp);
timestamps.Subscribe(x => Trace.WriteLine(x));
复制代码

 

一种常规的做法是把可观察流定义为一种类型,然后将其作为 IObservable<T> 资源使用。其他类型可以订阅这些流,或者把这些流与其他操作符

组合,创建另一个可观察流Rx 的订阅也是一个资源。 Subscribe 操作符返回一个 IDisposable ,即表示订阅完成。当你响应了那个可观察流,就得处

理这个订阅。对于hot observable(热可观察流)和 cold observable(冷可观察流)这两种对象,订阅的做法各有不同。一个 hot observable 对象是指一直

在发生的事件流,如果在事件到达时没有订阅者,事件就丢失了。例如,鼠标的移动就是一个 hot observable 对象。cold observable 对象是始终没有

输入事件(不会主动产生事件)的观察流,它只会通过启动一个事件队列来响应订阅。例如,HTTP 下载是一个 cold observable 对象,只有在订阅后

才会发出 HTTP 请求。

六、并发集合和不可变集合

大多数并发集合通过快照,既可以确保一个线程修改数据,同时也可以允许多个线程同时枚举数据。

不可变集合的无法修改性确保了所有操作的简洁性,特别适合在并发编程中使用。

七、并发编程与函数编程

大多数并发编程技术本质上都是函数式(functional) 的。

函数式编程理念简化并发编程的设计。每一个并行的片段都有输入和输出。他们不依赖于全局(或共享)变量,也不会修改全局(或共享)数据结构。

函数式编程的数据不变性在确保并发安全性的前提下,同时也防止了并发的活跃性问题。

 

转载于:https://www.cnblogs.com/Ph-one/p/7724989.html

相关文章:

  • 【传感器】BMA253 数字,三轴加速度传感器
  • 数据结构(六)——二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现...
  • c语言的按位运算符
  • 汇编语言如何取段地址的
  • 二路归并排序算法
  • 什么是MSB/LSB码?
  • 平衡二叉树(AVL树)
  • 二叉排序树(查询、插入、删除)
  • 数据结构中的堆和操作系统里的堆不一样为什么都叫堆呢?
  • 关于Simplicity Studio使用math.h编译出错
  • 正态分布(Normal distribution)又名高斯分布(Gaussian distribution)
  • MLP(多层神经网络)介绍
  • ring0
  • 什么是“欧几里德范数”(Euclidean norm)?
  • 协方差矩阵
  • JS 中的深拷贝与浅拷贝
  • Apache Pulsar 2.1 重磅发布
  • CentOS6 编译安装 redis-3.2.3
  • Git 使用集
  • java中具有继承关系的类及其对象初始化顺序
  • Netty 4.1 源代码学习:线程模型
  • Objective-C 中关联引用的概念
  • PaddlePaddle-GitHub的正确打开姿势
  • PAT A1092
  • Redis字符串类型内部编码剖析
  • Vue2 SSR 的优化之旅
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 第2章 网络文档
  • 关于for循环的简单归纳
  • 京东美团研发面经
  • 前端代码风格自动化系列(二)之Commitlint
  • 深度学习中的信息论知识详解
  • 使用 QuickBI 搭建酷炫可视化分析
  • 终端用户监控:真实用户监控还是模拟监控?
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • Android开发者必备:推荐一款助力开发的开源APP
  • 大数据全解:定义、价值及挑战
  • ​Java并发新构件之Exchanger
  • #Z0458. 树的中心2
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (4) PIVOT 和 UPIVOT 的使用
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (四)库存超卖案例实战——优化redis分布式锁
  • ****Linux下Mysql的安装和配置
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET CORE Aws S3 使用
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • @javax.ws.rs Webservice注解
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(白虎组)
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)