F#奇妙游(12):并行编程与π

news2024/11/27 8:48:11

核越多,越快乐

多核CPU对于计算机程序的开发带来了很大的挑战,但是也带来了很大的机遇。在多核CPU上,程序的性能可以通过并行化来提升,但是并行化的难度也随之提升。本文将介绍多核CPU的基本概念,以及如何在多核CPU上进行并行化编程。

此外,Web应用、GUI应用中广泛存在的响应式编程也代表了系统规模自适应和充分利用多核多CPU来改善体验的一种方式。

在F#的语言介绍中,或者在任何函数式语言的广告词中,都要提到函数式语言的并行化编程能力。这个能力是函数式语言的一个重要特性,也是函数式语言的一个重要优势。但是,这个能力是如何实现的呢?在本文中,我们将介绍函数式语言的并行化编程能力的基本原理,以及如何在F#中使用这个能力。

多核编程概念

什么是异步?什么是并发?这些概念之间有什么区别?

异步涉及到系统无法控制的外部调用,外部和内部同时进行,调用外部的功能并需要外部返回的结果,在等待结果的过程中,还需要继续执行内部的功能。异步的例子包括:网络请求、文件读写、数据库查询、用户输入等等。

并发涉及到系统可以控制的内部调用,内部和内部同时进行,调用内部的功能并需要内部返回的结果,在等待结果的过程中,还需要继续执行内部的功能。并发的例子包括:多线程、多进程等等。所有的内部调用都是系统可以控制的。并且,整个系统运行的结果应该是确定性的,即使是多线程、多进程,从设计的角度来看也应该是确定性的。

异步并发
目的提高响应加快处理速度
应用IOCPU
调用外部
是否确定

奇妙游:WPF+F#

多线程模型

这个函数有若干线程。

  1. 程序的主线程,也就是通常说的UI线程;
  2. 处理数据的线程,采用BackgroundWorker实现;
  3. 产生数据的线程,采用async计算表达式来实现。

线程之间的数据传递采用ConcurrentQueue来完成,处理数据的线程中更新UI界面,采用UIElement.Dispatcher.Invoke(fun () -> ...)来实现。

这基本上就涵盖了多线程编程的所有部分。

BackgroundWorker还挺可爱的,可以通过DoWork来注册工作,还能注册处理进度的函数,还能注册完成的函数。

let worker = new BackgroundWorker()
worker.WorkerReportsProgress <- true
worker.DoWork.Add (fun (e: DoWorkEventArgs) -> 
    worker.ReportProgress(......)
 )
worker.ProgressChanged.Add (fun (e: ProgressChangedEventArgs) -> ... 
    match e.UserState with
    | :> (......) ->  ...
    | _ -> ()
)
worker.RunWorkerCompleted.Add (fun (e: RunWorkerCompletedEventArgs) -> ... )
worker.RunWorkerAsync()

方法传递的几个函数中还能携带用户自定义的信息,查看帮助即可知道。在F#中可以很方便的通过math e.UserState with解析得到用户的信息.

ConcurrentQueue的用法也可以很容易查到。

π的低效方法

介绍来介绍去,其实都没什么意思。我们秃子哦不软件工程师就喜欢看到CPU忙起来。

那么我们就整一个效率非常低的π的计算方法,这个计算方法利用的就是圆的面积和正方形的面积的比例关系。产生两个随机数,都属于[-1,1],那么落在单位圆范围内的概率就是π/4。那么我们就可以通过这个概率来计算π的值。

let calPi (q: ConcurrentQueue<float * float * bool>) =
    async {
        let rand = new Random()
        let inline rd () = 2.0 * (rand.NextDouble() - 0.5)

        while true do
            if q.Count >= 500 then
                Thread.Sleep(10)
            else
                let x = rd ()
                let y = rd ()
                let inside = x * x + y * y <= 1.0
                q.Enqueue(x, y, inside)
    }

这个函数产生两个随机数,并把判断的结果和随机数放到一个队列里头(FIFO),这里选择的是并行队列,也就是线程安全的队列。

WPF但是F#

接下来就是搞一个WPF的用户界面。其实WPF的用户界面用起来比WinForm还简单啊。主函数就是这个样子,跟PyQt5简直是一个样子。

[<STAThread>]
[<EntryPoint>]
let main args =
    let app = Application()
    let window = MyWindow()
    app.Run(window) |> ignore
    0

这个主窗口,在F#中又两种实现方法,一种实现方法是搞一个Window的子类,一种方法就是定义一个函数,返回一个Window的实例,代码基本没区别,但是可以看到F#的对象模型和C#的对象模型还是有一些区别的。这里还设了一个ico,WPF只能用ico文件,人家JavaFX和PyQt5都能直接png,jpg啊。如果没有ico,把这句话删了就行。

type MyWindow() as self =
    inherit Window()
    let mutable inside = float 0
    let mutable total = float 0
    let canvas = Canvas()
    let processQueue = new ConcurrentQueue<(float * float * bool)>()

    let worker = new BackgroundWorker()

    do
        self.Content <- canvas
        self.Width <- 800.0
        self.Height <- 800.0
        self.ResizeMode <- ResizeMode.NoResize

    do
        worker.DoWork.Add (fun (e: DoWorkEventArgs) ->
            let mutable tup = (0.0, 0.0, false)

            while true do
                if processQueue.IsEmpty then
                    Thread.Sleep(10)
                else if processQueue.TryDequeue(&tup) then
                    let x, y, isInside = tup

                    let color =
                        if isInside then
                            Brushes.Black
                        else
                            Brushes.Red

                    canvas.Dispatcher.Invoke(fun () -> drawPoint canvas x y 6.0 color)
                    total <- total + 1.0
                    inside <- inside + if isInside then 1.0 else 0.0
                    let pi = 4.0 * float inside / float total

                    self.Dispatcher.Invoke (fun () ->
                        self.Title <- $"Sample(%16.0f{total}/%3d{processQueue.Count}), Pi = %.12f{pi}"))

    do
        [ 1..1 ]
        |> List.map (fun _ -> calPi processQueue)
        |> Async.Parallel
        |> Async.StartAsTask
        |> ignore

    do
        canvas.Background <- Brushes.Black

        canvas.Loaded.Add (fun _ ->
            drawBackground canvas
            // Start update UI worker
            worker.RunWorkerAsync())

函数式的实现更加清晰,反正就是Window中间放一个Canvas。

let makeWindow () =
    let window = Window()
    let canvas = Canvas()

    let mutable inside = float 0
    let mutable total = float 0

    canvas.Background <- Brushes.Black

    let processQueue = ConcurrentQueue<(float * float * bool)>()

    [ 1..3 ]
    |> List.map (fun _ -> calPi processQueue)
    |> Async.Parallel
    |> Async.Ignore
    |> Async.Start



    canvas.Loaded.Add (fun _ ->
        drawBackground canvas

        let worker = new BackgroundWorker()

        worker.DoWork.Add (fun (e: DoWorkEventArgs) ->
            let mutable tup = (0.0, 0.0, false)

            while true do
                if processQueue.IsEmpty then
                    Thread.Sleep(10)
                else if processQueue.TryDequeue(&tup) then
                    let x, y, isInside = tup

                    let color =
                        if isInside then
                            Brushes.Black
                        else
                            Brushes.Red

                    total <- total + 1.0
                    inside <- inside + if isInside then 1.0 else 0.0

                    canvas.Dispatcher.Invoke(fun () -> drawPoint canvas x y 6.0 color)
                    // the following will not work!
                    // drawPoint canvas x y 6.0 color
                    let pi = 4.0 * float inside / float total

                    window.Dispatcher.Invoke (fun () ->
                        window.Title <- $"Sample(%16.0f{total}/%3d{processQueue.Count}), Pi = %.12f{pi}"))
                    // the following will not work!
                    // window.Title <- $"Sample(%10.0f{total}/%6d{processQueue.Count}), Pi = %.12f{pi}")

        worker.RunWorkerAsync())

    window.Content <- canvas
    window.Width <- 800.0
    window.Height <- 800.0
    window.ResizeMode <- ResizeMode.NoResize
    // window.WindowStartupLocation <- WindowStartupLocation.CenterScreen

    window

最后就是工程文件,首先用dotnet new console -lang F# -o pi创建一个工程,然后把文件改吧改吧。

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net7.0-windows</TargetFramework>
        <UseWpf>true</UseWpf>
        <Configuration>Release</Configuration>
        <ApplicationIcon>fsharp.ico</ApplicationIcon>
    </PropertyGroup>

    <ItemGroup>
        <Compile Include="Program.fs"/>
    </ItemGroup>

</Project>

另外有两个在Canvas上画图的函数。

完整代码

open System
open System.Collections.Concurrent
open System.ComponentModel
open System.Threading
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.Windows.Shapes
open Microsoft.FSharp.Control
open Microsoft.FSharp.Core


let drawPoint (c: Canvas) (x: float) (y: float) (d: float) color =
    let w, h = c.ActualWidth, c.ActualHeight
    let r = (min w h) * 0.5

    let myEllipse = new Ellipse()
    myEllipse.Fill <- color
    myEllipse.Width <- d
    myEllipse.Height <- d
    let myPoint = new Point(x * r, y * r)
    Canvas.SetLeft(myEllipse, myPoint.X + w / 2.0 - d / 2.0)
    Canvas.SetTop(myEllipse, myPoint.Y + h / 2.0 - d / 2.0)
    c.Children.Add(myEllipse) |> ignore


let drawBackground (c: Canvas) =
    let w, h = c.ActualWidth, c.ActualHeight
    let myEllipse = new Ellipse()
    myEllipse.Fill <- Brushes.Beige
    let d = min w h
    myEllipse.Width <- d
    myEllipse.Height <- d
    // Canvas.SetZIndex(myEllipse, -1)
    let myPoint = new Point(w / 2.0, h / 2.0)
    Canvas.SetLeft(myEllipse, myPoint.X - d / 2.0)
    Canvas.SetTop(myEllipse, myPoint.Y - d / 2.0)
    c.Children.Add(myEllipse) |> ignore

let calPi (q: ConcurrentQueue<float * float * bool>) =
    async {
        let rand = new Random()
        let inline rd () = 2.0 * (rand.NextDouble() - 0.5)

        while true do
            if q.Count >= 500 then
                Thread.Sleep(10)
            else
                let x = rd ()
                let y = rd ()
                let inside = x * x + y * y <= 1.0
                q.Enqueue(x, y, inside)
    }

let makeWindow () =
    let window = Window()
    let canvas = Canvas()

    let mutable inside = float 0
    let mutable total = float 0

    canvas.Background <- Brushes.Black

    let processQueue = ConcurrentQueue<(float * float * bool)>()

    [ 1..3 ]
    |> List.map (fun _ -> calPi processQueue)
    |> Async.Parallel
    |> Async.Ignore
    |> Async.Start



    canvas.Loaded.Add (fun _ ->
        drawBackground canvas

        let worker = new BackgroundWorker()

        worker.DoWork.Add (fun (e: DoWorkEventArgs) ->
            let mutable tup = (0.0, 0.0, false)

            while true do
                if processQueue.IsEmpty then
                    Thread.Sleep(10)
                else if processQueue.TryDequeue(&tup) then
                    let x, y, isInside = tup

                    let color =
                        if isInside then
                            Brushes.Black
                        else
                            Brushes.Red

                    total <- total + 1.0
                    inside <- inside + if isInside then 1.0 else 0.0

                    canvas.Dispatcher.Invoke(fun () -> drawPoint canvas x y 6.0 color)
                    // the following will not work!
                    // drawPoint canvas x y 6.0 color
                    let pi = 4.0 * float inside / float total

                    window.Dispatcher.Invoke (fun () ->
                        window.Title <- $"Sample(%16.0f{total}/%3d{processQueue.Count}), Pi = %.12f{pi}"))
        // the following will not work!
        // window.Title <- $"Sample(%10.0f{total}/%6d{processQueue.Count}), Pi = %.12f{pi}")

        worker.RunWorkerAsync())

    window.Content <- canvas
    window.Width <- 800.0
    window.Height <- 800.0
    window.ResizeMode <- ResizeMode.NoResize
    // window.WindowStartupLocation <- WindowStartupLocation.CenterScreen

    window

type MyWindow() as self =
    inherit Window()
    let mutable inside = float 0
    let mutable total = float 0
    let canvas = Canvas()
    let processQueue = new ConcurrentQueue<(float * float * bool)>()

    let worker = new BackgroundWorker()

    do
        self.Content <- canvas
        self.Width <- 800.0
        self.Height <- 800.0
        self.ResizeMode <- ResizeMode.NoResize

    do
        worker.DoWork.Add (fun (e: DoWorkEventArgs) ->
            let mutable tup = (0.0, 0.0, false)

            while true do
                if processQueue.IsEmpty then
                    Thread.Sleep(10)
                else if processQueue.TryDequeue(&tup) then
                    let x, y, isInside = tup

                    let color =
                        if isInside then
                            Brushes.Black
                        else
                            Brushes.Red

                    canvas.Dispatcher.Invoke(fun () -> drawPoint canvas x y 6.0 color)
                    total <- total + 1.0
                    inside <- inside + if isInside then 1.0 else 0.0
                    let pi = 4.0 * float inside / float total

                    self.Dispatcher.Invoke (fun () ->
                        self.Title <- $"Sample(%16.0f{total}/%3d{processQueue.Count}), Pi = %.12f{pi}"))

    do
        [ 1..1 ]
        |> List.map (fun _ -> calPi processQueue)
        |> Async.Parallel
        |> Async.StartAsTask
        |> ignore

    do
        canvas.Background <- Brushes.Black

        canvas.Loaded.Add (fun _ ->
            drawBackground canvas
            // Start update UI worker
            worker.RunWorkerAsync())

[<STAThread>]
[<EntryPoint>]
let main args =
    let app = Application()
    let window = MyWindow()
    app.Run(window) |> ignore
    0

运行界面

在这里插入图片描述

总结

  1. F#中调用WPF同样容易,可能比WinForm还简单,不用XAML其实就是字多一点,也没啥;
  2. F#中的多线程功能非常丰富,自己async是很好的工程工具,.NET平台的各个工具也能使用;
  3. 要界面有响应,就不要在UI线程外面掉UI代码,应该用UIElement.Dispatcher.Invoke

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

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

相关文章

【分布式任务调度】XXL-JOB调度中心对执行器的上下线感知实现原理(三)

文章目录 1. 前言2. 调度关系3. 执行器注册3.1. 调度中心处理注册请求3.2. 执行器发起注册请求 4. 执行器注销4.1.主动注销4.2. 被动注销 5.流程图6. 总结 1. 前言 XXL-JOB专题历史文章列表&#xff1a; XXL-JOB调度中心集群部署配置&#xff08;一&#xff09;XXL-JOB执行器…

官宣!Databend Cloud 和青云科技达成合作

近日&#xff0c;北京数变科技有限公司与北京青云科技股份有限公司(以下简称&#xff1a;青云科技 &#xff09;顺利完成了产品兼容性适配互认证。本次测试是对 Databend 云原生数据仓库系统与青云科技公司自主研发的 QingStor U10000 进行严格的联合测试验证。测试结果显示&am…

数字 IC 设计职位经典笔/面试题(二)

共100道经典笔试、面试题目&#xff08;文末可全领&#xff09; FPGA 中可以综合实现为 RAM/ROM/CAM 的三种资源及其注意事项&#xff1f; 三种资源&#xff1a;BLOCK RAM&#xff0c;触发器&#xff08;FF&#xff09;&#xff0c;查找表&#xff08;LUT&#xff09;&#xf…

CAS机制详解

一、是什么 CAS&#xff0c;是Compare and Swap的简称&#xff0c;实现并发算法是常用到的一种技术&#xff0c;在这个机制中有三个核心的参数。 主内存中存放的共享变量的值&#xff1a;V&#xff08;一般情况下这个V是内存的地址值&#xff0c;通过这个地址可以获得内存中的…

mysql数值函数

1. ceil() 向上取整 2. floor() 向下取整 3. mod() 取余 4. rand() 生成0-1之间的随机数 5. round(x&#xff0c;y) x 四舍五入&#xff0c;保留 y 位小数

【Spring core学习三】对象装配:获取Bean对象的四种方式

目录 对象装配的四种方式 &#x1f337;1、Autowired属性注入&#xff08;使用最多&#xff09; &#x1f337;2、Setter注入 &#x1f337;3、构造函数注入 &#x1f337;4、Resource&#xff1a;另⼀种注⼊关键字 对象装配的四种方式 对象装配&#xff1a;获取bean对象也…

2、用phpMyAdmin修改mysql的密码

用phpMyAdmin登录mysql服务器&#xff0c;默认的用户名密码为 Username: root Password: [null] 在账户中修改密码

8.分布式存储

文章目录 分布式存储存储基础单机存储设备单机存储的问题分布式存储&#xff08;软件定义的存储 SDS&#xff09;分布式存储的数据类型总结 CephCeph概念Ceph优势Ceph架构Ceph核心组件OSD 存储后端Ceph 数据的存储过程Ceph 版本发行生命周期Ceph 集群部署Ceph 生产环境推荐&…

虚拟机磁盘扩容

工作中已经不止一次碰到虚机扩容了&#xff0c;所以一定要切记&#xff1a;给虚机多分配些容量&#xff01;&#xff01; 方法其实就是现在物理机上给磁盘扩容&#xff0c;然后再跑到虚拟机内部进行lvm卷扩容&#xff0c;具体的原理到去看LVM底层&#xff0c;总体就是LV->V…

我做了10年的测试,由衷的建议年轻人别入这行了...

两天前&#xff0c;有个做功能测试7年的同事被裁员了。这位老哥已经做到了团队中的骨干了&#xff0c;人又踏实&#xff0c;结果没想到刚刚踏入互联网“老龄化”大关&#xff0c;就被公司给无情优化了。 现在他想找同类型的工作&#xff0c;薪资也一直被压&#xff0c;考虑转行…

leetcode-541. 反转字符串 II

leetcode-541. 反转字符串 II 文章目录 leetcode-541. 反转字符串 II一.题目描述二.第1次提交(for循环&#xff0c;std::reverse)三.第2次提交四.第3次提交五.第4次提交六.代码随想录解答一七.代码随想录解答二八.代码随想录解答三 一.题目描述 二.第1次提交(for循环&#xff0…

安达发|APS排程系统能帮医药行业实现哪些目标

高级发展规划和调度aps &#xff08;advanced planning and scheduling&#xff09;是一个国家基于中国整个社会企业进行生产经营活动的计算机信息系统。通过aps的管理手段和信息&#xff0c;企业可以优化从产品产量的决定到最终产品管理的所有生产过程。利用实时&#xff0c;准…

10大功能特性,助力开发者玩转华为云API Explorer

伴随着我国API生态逐渐成熟、市场发展不断完善&#xff0c;API已广泛应用在以网页、移动应用、后端系统集成为主的众多开发场景中。同时&#xff0c;开发者对API的主要诉求已由获取数据能力转变为获取技术能力、甚至业务能力&#xff0c;开发者渴望更加高效便捷的调用方式&…

CRMEB商城系统授权与不授权有什么区别

现在&#xff0c;很多时候我们都会听到“授权”这个词&#xff0c;在CRMEB的商城系统产品中&#xff0c;商业授权也是一个重要环节&#xff0c;今天&#xff0c;我们就来了解一下关于CRMEB商城系统授权的那些事儿。 一、为什么要进行商业授权&#xff1f; 正版商业授权是对用户…

MVVM 实现记录文本

1. MVVM 框架说明: Model - 数据层 View - 视图层 ViewModel - 管理模型的视图 2. 资源文件 2.1 启动图标: AppIconhttps://img-blog.csdnimg.cn/8fa1031489f544ef9757b6b3ab0eddbe.png 2.2 Display Name: Do Stuff 2.2 颜色图: 2.3 项目结构图: 3. Model 层实现&a…

2.8 Bootstrap 图片

文章目录 Bootstrap 图片\<img> 类响应式图片 Bootstrap 图片 在本章中&#xff0c;我们将学习 Bootstrap 对图片的支持。Bootstrap 提供了三个可对图片应用简单样式的 class&#xff1a; .img-rounded&#xff1a;添加 border-radius:6px 来获得图片圆角。.img-circle&…

探索uni-app:构建跨平台应用的神奇工具

文章目录 &#x1f4da;1. 视图容器类组件⚡ <view>&#xff1a;视图容器&#xff0c;类似于div元素⚡<scroll-view>&#xff1a;可滚动的视图容器 &#x1f4da;2. 基础内容类组件⚡<text>&#xff1a;文本内容&#xff0c;类似于span元素⚡<icon>&am…

阿里大佬都在偷偷肝的 Java 程序优化笔记,程序性能提高了 5 倍!

前言 此笔记从软件设计、编码和 JVM 等维度阐述性能优化的方法和技巧&#xff0c;分享资深架构师 Java 程序性能优化的宝贵经验&#xff0c;专注于 Java 应用程序的优化方法、技巧和思想&#xff0c;并深度剖析 JDK 部分的实现。具有较强的层次性和连贯性&#xff0c;深入剖析…

wps插入图片显示不全、混乱

问题如下&#xff1a; 原因&#xff1a; 格式混乱 解决办法&#xff1a; 1、统一格式&#xff0c;使用格式刷统一文档的格式 2、Ctrl A 全选&#xff0c;重新选择行距 3、重新粘贴图片&#xff08;选择嵌入型&#xff09;