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

核越多,越快乐

多核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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenCv图像基本变换

目录 一、图像翻转 二、图像旋转 三、仿射变换之平移 四、仿射变换之获取变换矩阵 五、仿射变换之透视变换 一、图像翻转 图像翻转不等同于旋转&#xff0c;类似于一些视频的拍摄&#xff0c;拍摄后实际是左右颠倒的&#xff0c;通过图像翻转可进行还原 案例代码如下: …

Android之Intent

意图介绍 一个意图(Intent)对象包含了目标组件、动作、数据、类别、附加数据、标志六个部分。 目标组件 目标组件可以帮助应用发送显式意图调用请求。在创建Intent时&#xff0c;可以通过setComponent方法来设置一个组件&#xff0c;如&#xff1a; //设置组件 intent.setC…

Linux - CentOS 二进制安装 MySQL 8.0.31(非常实用)

一、下载 mysql-8.0.31-linux-glibc2.12-x86_64.tar.xz 下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 具体如下图所示&#xff1a; 二、将 mysql-8.0.31-linux-glibc2.12-x86_64.tar.xz 放入到服务器的 /usr/local &#xff08;路径可…

C++万字自学笔记

[TOC] 一、 C基础 C的IDE有CLion、Visual Studio、DEV C、eclipse等等&#xff0c;这里使用CLion进行学习。 0. C初识 0.1 第一个C程序 编写一个C程序总共分为4个步骤 创建项目创建文件编写代码运行程序 #include <iostream>int main() {using namespace std;cout…

SpringCloud Alibaba——Ribbon底层怎样实现不同服务的不同配置

目录 一、Ribbon底层怎样实现不同服务的不同配置二、源码角度分析 一、Ribbon底层怎样实现不同服务的不同配置 为不同服务创建不同的spring上下文&#xff0c;不同的spring上下文中存放对应这个服务所有的配置。 二、源码角度分析 SpringClientFactory中可以获取到所有ribbon…

基于MSP432P401R跟随小车(一)【2022年电赛】

文章目录 一、赛前准备1. 硬件清单2. 工程环境 二、赛题思考三、软件设计1. 路程、时间、速度计算2. 距离测量3. 双机通信4. 红外循迹 四、技术交流 一、赛前准备 1. 硬件清单 主控板&#xff1a; MSP432P401R测距模块&#xff1a; GY56数据显示&#xff1a; OLED电机&#x…

7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

文章目录 前言一、定义注解annotation二、拦截角色注解1. 在拦截器哪里拦截&#xff1f;2. 如何拦截角色注解&#xff1f;3. 角色如何读取?4. 最后做角色校验 三、应用&#xff1a;给管理员操作接口加注解四、PostMan测试最后 前言 在【7.1】管理员图书录入和修改API&#xf…

原生js实现将图片内容复制到剪贴板

核心代码 /*复制图片*/ copyImg(dom) {/* 警告&#xff1a;dom不能是img标签&#xff0c;建议用DIV标签包裹img标签&#xff0c;否者会报错&#xff01;不支持复制背景图&#xff01; */dom.style.userSelect auto;let selection getSelection(), range document.createRan…

支持向量机(SVM)的超参数调整 C 和 Gamma 参数

作者:CSDN @ _养乐多_ 支持向量机(Support Vector Machine,SVM)是一种广泛应用的监督式机器学习算法。它主要用于分类任务,但也适用于回归任务。在本文中,我们将深入探讨支持向量机的两个重要参数:C和gamma。在阅读本文前,我假设您对该算法有基本的了解,并专注于这些…

从网络安全行业人才需求讲讲【个人规划】

如果你是一名正在找工作的网络安全方向大学生&#xff0c;或者是刚刚踏入网络安全领域的新手&#xff0c;这篇文章很适合你&#xff0c;如果你是一名老网安人&#xff0c;看看有哪些你中招了。 当你打开BOSS直聘、拉钩等招聘类网站后&#xff0c;在首页的快速导航页面很难找到关…

微信小程序安装和使用 Vant Weapp 组件库

微信小程序安装和使用 Vant Weapp 组件库 1. Vant Weapp 介绍2. Vant Weapp 的 安装2.1. 通过npm安装2.2. 构建npm2.3. 修改 app.json2.4. 修改 project.congfig.json2.5. 测试一下&#xff0c;使用Vant Weapp提供的组件 1. Vant Weapp 介绍 Vant 是一个轻量、可靠的移动端组件…

《遗留系统现代化》读书笔记(基础篇)

你现在所写的每一行代码&#xff0c;都是未来的遗留系统 为什么要对遗留系统进行现代化&#xff1f; 什么是遗留系统&#xff1f; 判断遗留系统的几个维度&#xff1a;代码、架构、测试、DevOps 以及技术和工具。时间长短并不是衡量遗留系统的标准。代码质量差、架构混乱、没…

【深度学习笔记】正则化与 Dropout

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

Django实现简单的音乐播放器 4

在原有音乐播放器功能基础上&#xff0c;增加上传音乐功能。 效果&#xff1a; 目录 配置上传路径 配置路由 视图处理歌曲 引入类库 保存歌曲文件 模板上传 设置菜单列表 设置菜单列表样式 脚本设置 上传效果 1.显示菜单列表 2.点击上传歌曲 3.上传完成 4.查看保…

GEE:计算每个对象的面积、标准差、周长、宽度、高度

作者:CSDN @ _养乐多_ 本文记录了面对对对象分割,以及计算每个对象的面积、标准差、周长、宽度、高度的代码。 文章目录 一、代码一、代码 // 设置种子 var seeds = ee.Algorithms.Image.Segmentation.seedGrid(20)

如何通过 CrossOver 在 Mac 苹果电脑上安装使用 windows 应用程序?

首先我们可以先了解一下什么是 crossOver &#xff0c;CrossOver 是 Mac 和 Windows 系统之间的兼容工具。使 Mac 操作系统的用户可以运行 Windows 系统的应用&#xff0c;从办公软件、实用工具、游戏到设计软件&#xff0c; 您都可以在 Mac 程序和 Windows 程序之间随意切换。…

18.Lucas-Kanade光流及OpenCV中的calcOpticalFlowPyrLK

文章目录 光流法介绍OpenCV中calcOpticalFlowPyrLK函数补充reference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 光流法介绍 光流描述了像素在图像中的运动&#xff0c;就像彗星☄划过天空中流动图像。同一个像素&#xff0c;随着时…

MacOS触控板缩放暂时失灵问题解决

我的系统版本为Monterey 12.5.1&#xff0c;亲测有效 直接创建脚本xxx.sh&#xff0c;并在终端执行脚本bash xxx.sh即可解决此问题&#xff0c;脚本内容如下&#xff1a; #!/bin/bashkillall Finder #kill Finder如不需要可以删除 killall Dock #kill Dock 如不需要可以删…

【Maven】Maven下载,配置以及基本概念

文章目录 1. Maven简介2. Maven下载3. Maven环境配置4.Maven的基本概念4.1 仓库4.2 坐标4.3 仓库配置(修改IDEA默认Maven库) 1. Maven简介 Maven是一个Java项目管理工具和构建工具&#xff0c;用于管理项目的依赖关系、构建过程以及项目的部署。它是Apache软件基金会的开源项目…

『表面』在平面模型上提取凸(凹)多边形

原始点云 直通滤波,z轴0~1.1 分割模型为平面&#xff0c;分割结果进行投影 提取多边形 代码: #include <pcl/ModelCoefficients.h> // 模型系数的数据结构&#xff0c;如平面、圆的系数 #include <pcl/io/pcd_io.h>#include <pcl/point_types.h> // 点云数据…