至简,Nim lang 并行编程

作者: Tulayang | 来源:发表于2015-08-01 21:35 被阅读1435次

Nim 语言中,提供低阶的多线程接口,以及一个高阶的线程池 threadpool 。使用低阶接口,你可以完全用 C 的函数编写多线程,或者使用 Nim 实现的相同性质的 threads 模块(内置系统模块)。高阶接口,实现的非常优雅,封装了操作系统中互斥锁等待信号变量的线程池模式。

这样组合的结果是,在 Nim 中编写多线程非常优雅和可读。

你看了之后,也可能会说 Erlang 语言的并发也不错。我要说的是,Erlang 仅仅是针对通信网络的语言。而 Nim 是针对操作系统和上层网络通信的语言。Nim 拥有 C 语言的计算级别(其编译器优化甚至可以比 C 源码更快),并且可以随时轻易的切换到 C 操作。Nim 对数据计算拥有更有力的速度。

现在,我们使用 Nim 来实现一个典型的多线程事务例子:

多个客户端请求,将账户 x 的金额减少 1,并把账户 y 的金额增加 1 。

首先,这涉及到并发。多个账户,使用多个线程读取 read() 他们的信息,然后需要把多线程锁住,只开一个线程“窗口”,进入事务模式: 每次只有一个线程可以操作减少增加金额。然后把完成的线程重新开放,作出响应 response() 告诉客户端完成任务。

线程模块

Nim 有一个 System 模块,是自动加载的,里面包含了大量的工具,包括 threads, channels(线程通信模块)。这个 System 非常类似 JavaScript 中的 global,比如你在 JavaScript 中使用 parseInt(1.2) 不需要导入模块,也没有命名空间。Nim 的 System 跟此很相似。

实现这个任务需要 threadpool(线程池模块),locks(锁模块)。locks 就是操作系统的互斥量(pthread_mutex_t)和条件变量(pthread_cond_t)的对应实现。此外,Nim 为锁增加了数据安全的检查(发生在编译期,不会影响运行期的效能),每个被锁标记的数据,在访问时都要有一个编译注释:

var 
    xlock: TLock
    x {.guard.} = 100

{.locks: [xlock].}: x = x - 1

如果没有 {.locks: [xlock].},会触发一个编译期错误。这能有效提醒多线程的程序员,这个变量是锁变量,需要更多的“照顾”。

threadpool 提供了非常棒的线程池,默认是 256 个启动线程,这对大部分业务都是足够得了(实际上,更多的线程可能性能反而更低)。

使用线程池,你不需要手动创建、脱离线程。只需要使用 spawn() 函数,就可以从线程池中领取一个线程来运行任务。所以,你更多的是关注任务如何运作,而不再是线程如何领取并按照你的意愿运行。

proc work() {.thread.} = 
    read()
    update()
    response()

spawn work()  # 领取一个线程,运行 work 函数

这跟事件驱动模式很相似,先把资源加载进一个池中管理,然后通过某些类似钩子的东西,领取一个资源,运行加入的函数。

实现并行

首先,为了模拟一个时间比较长的操作,使用一个 longtime() 来延长计算时间。这个函数什么也不做,只是从0数到200000000。根据估算,大概是3秒种:

proc longtime() =
    for i in 0..200_000_000: discard

然后是读取客户端的数据,我们使用一个 read() 来模拟:

proc read() =
    echo "--- Read begin"
    longtime()
    echo ">>> Read finish"

响应客户端,我们使用一个 response() 来模拟:

proc response() =
    echo "--- Response begin"
    longtime()
    echo ">>> Response finish"

然后是最重要的事务函数,这个函数需要锁,我们通过模板宏来封装一个锁语句:

template lock(x: TLock, y: TLock, body: stmt) =
    acquire(x)
    acquire(y)
    {.locks: [x, y].}: body
    release(y)
    release(x)

然后使用 lock 语句来启动事务:

proc update() = 
    # 启动一个事务
    lock(xlock, ylock): 
        # 把账户 x 减少 1
        echo "--- Decrease begin with x " & $x
        longtime()     
        dec(x, 1)
        echo ">>> Decrease finish with x " & $x
        # 把账户 y 增加 1
        echo "--- Increase begin with y " & $y
        longtime()
        inc(y, 1)
        echo ">>> Increase finish with y " & $y

最后,工作线程的过程非常简单,就是上面的3个任务的调用:

proc work() {.thread.} = 
    read()
    update()
    response()

That's all。源码如下:

import threadpool, locks

var 
    xlock: TLock
    ylock: TLock
    x {.guard: xlock.} = 100  # 账户 x 的金额 100
    y {.guard: ylock.} = 100  # 账户 y 的金额 100

proc longtime() =
    for i in 0..200_000_000: discard

proc read() =
    echo "--- Read begin"
    longtime()
    echo ">>> Read finish"

proc response() =
    echo "--- Response begin"
    longtime()
    echo ">>> Response finish"

template lock(x: TLock, y: TLock, body: stmt) =
    acquire(x)
    acquire(y)
    {.locks: [x, y].}: body
    release(y)
    release(x)

proc update() = 
    # 启动一个事务
    lock(xlock, ylock): 
        # 把账户 x 减少 1
        echo "--- Decrease begin with x " & $x
        longtime()     
        dec(x, 1)
        echo ">>> Decrease finish with x " & $x
        # 把账户 y 增加 1
        echo "--- Increase begin with y " & $y
        longtime()
        inc(y, 1)
        echo ">>> Increase finish with y " & $y

proc work() {.thread.} = 
    read()
    update()
    response()

initLock(xlock)
initLock(ylock)

while true:
    longtime()
    longtime()
    for i in 0..2:
        spawn work()

sync()
deinitLock(xlock)
deinitLock(ylock)

运行代码

$ nim c -r --threads:on test.nim

会输出

--- Read begin
--- Read begin
--- Read begin
>>> Read finish
--- Decrease begin with x 100
>>> Read finish
>>> Read finish
>>> Decrease finish with x 99
--- Increase begin with y 100
--- Read begin
--- Read begin
>>> Read finish
>>> Increase finish with y 101
--- Response begin
--- Decrease begin with x 99
>>> Read finish
>>> Response finish
--- Read begin
>>> Decrease finish with x 98
--- Increase begin with y 101
>>> Increase finish with y 102
--- Response begin
--- Decrease begin with x 98
>>> Read finish
--- Read begin
>>> Decrease finish with x 97
--- Increase begin with y 102
>>> Response finish
--- Read begin
>>> Read finish
...

相关文章

  • 至简,Nim lang 并行编程

    Nim 语言中,提供低阶的多线程接口,以及一个高阶的线程池 threadpool 。使用低阶接口,你可以完全用 C...

  • Nim早茶之均匀分布 [Nim 语言实现]

    Nim 语言是一门高效而优雅的系统级编程语言,官网如下:https://nim-lang.org/中文官网:htt...

  • Nim核心编程

    第一部分Nim核心编程 第1章欢迎来到Nim世界 1.1什么是Nim..........................

  • Nim 编程极简入门(一)

    Nim 语言是一门静态类型的编译语言,语法上与 Python 类似,高效而优雅。 安装 Nim 在官网 https...

  • 《深入理解并行编程》整理笔记

    目录 1.并发编程的目标 2.并行访问控制 - 是什么使并行编程变得复杂? 3.关于硬件 - 对并行编程造成的障碍...

  • nim-lang callback uuid shellcode

    0x00 描述 Nim(最初叫 Nimrod)是⼀⻔命令式静态类型编程语⾔,可以被编译成 C 或 JavaScri...

  • [转]并发和并行有什么区别

    做并发编程之前,必须首先理解什么是并发,什么是并行,什么是并发编程,什么是并行编程。 并发(concurrency...

  • SHELL并发编程

    SHELL的并行编程:通过启用多个并行的后台子进程,实现任务的并行处理。 并发编程的模式: 简单模式 批处理模式 ...

  • 01-并发概述

    并发编程由来: 串行与并行 并发编程目的 并发编程的场景

  • Nim 语言编程实现指数分布的随机数

    这一节,我们使用 Nim 语言来实现符合指数分布的随机数。Nim 语言是一门高效而优雅的系统级编程语言,可以编译成...

网友评论

本文标题:至简,Nim lang 并行编程

本文链接:https://www.haomeiwen.com/subject/twkvqttx.html