并发

V语言并发的思路和语法跟go语言基本一致,不过有两种不同的并发方式:

  • 使用spawn关键字创建操作系统线程(OS thread)

  • 使用go关键字创建轻量级线程(coroutines)

目前spawn和go的使用方式基本一致。

轻量级线程实现

目前(2023-05-28)V语言初步实现了轻量级线程,基于阿里开源的PhotonLibOS协程库。目前仅支持mac和linux操作系统。

协程库参考:https://developer.aliyun.com/article/1208390

演示代码:

#目前轻量级线程还不支持gc,默认不启用,暂时需要加上-gc -use-coroutines
v -gc none -use-coroutines main.v 
./main
import coroutines as co
import time

fn foo(a int) {
    for {
        println('hello from foo() a=$a')
        co.sleep(1 * time.second)
    }
}

fn foo2(a int) {
    for {
        println('hello from foo2() a=$a')
        co.sleep(2 * time.second)
    }
}

fn foo3(a int) {
    for {
        println('hello from foo3() a=$a')
        co.sleep(3 * time.second)
    }
}


fn main() {
    go foo(10)
    go foo2(20)
    go foo3(30)
    for {
        println('hello from MAIN')
        co.sleep(1 * time.second)
    }
    println('done')
}

目前仅实现在轻量级线程中执行网络请求和文件IO:

判断是否启用了轻量级线程:

操作系统线程

轻量级线程

go可以添加在函数调用,方法调用,匿名函数调用前,即可创建并发任务单元。

声明channel变量

channel变量声明时,可以指定cap,cap表示缓冲区大小/容量,指定后不可改变,如果不指定,默认为0。

len表示当前被使用的缓冲大小,len不能在声明时指定,初始值为0,只读,根据写入/读取channel自动改变,写入增加len,读取减少len。

没有指定cap,就是同步模式,同步模式下,发送和接收双方配对,然后读写同时完成,如果接收之前,还没有发送,就会出现阻塞。

有指定cap,就是异步模式,异步模式下,在缓冲大小的范围内,发送方不用等待接收方,数据写入后,继续往下执行,不会出现阻塞,如果超出了缓冲大小范围,发送方还是要阻塞等待接收方接收数据。

channel从底层实现上来说,是一个队列,通过push()把数据写入到队列中,通过pop()把数据读取出来。

读取channel/接收消息

写入channel/发送消息

go表达式

除了使用标准的chanel和waitgroup方式外,还可以使用go表达式来简化并发代码,go表达式更像是一种并发语法糖/简化版。

thread线程数组

go表达式实现了并发执行后,然后返回单个结果给主线程。

而thread线程数组实现了并发执行多个线程,然后返回结果数组给主线程,用起来挺简洁明了的。

错误处理

可以在读写channel中增加or代码块,实现错误处理。

go表达式和go线程数组也支持同样的错误处理方式。

if条件语句读取chan

关闭channel

关闭channel或者写入channel都会解除阻塞。

关闭channel以后,使用try_push()和try_pop函数都会返回.closed枚举。

select语句

select语句可以同时监听多个channel的读写事件,并且可以进行监听的超时处理。

一般都会结合for循环使用,实现持续监听。

if select语句

for select语句

for select语句主要在并发中使用,用来循环监听多个chanel。

主进程阻塞等待

一般来说,主进程执行完毕后,不会等待其他子线程的结果,就直接退出返回,其他子线程也随着终止。

可以在主进程末尾增加阻塞等待子线程的运行结果。

泛型函数/方法

除了使用标准函数/方法作为go的并发单元,泛型函数/方法也可以。

线程之间的变量共享/锁定

可以使用shared/lock/rlock来实现,多个线程之间,可以通过定义shared类型的变量,来实现线程间共享,所有线程都可以读写该变量。

当某个线程要进行读写共享变量时,为了防止线程之间的数据竞争:

  • 在读写之前,要使用lock代码块(读写锁)来锁定共享变量。

  • 在只读之前,要使用rlock代码块(只读锁)来锁定共享变量。

共享变量可以是基本类型,array,map,struct类型。

函数返回shared类型

读写锁表达式

易变变量

跟C语言一样,V语言也有volatile关键字,用来标识易变变量。

V语言中的变量根据可变性有以下三种:不可变(默认),可变(mut),易变(volatile)。

易变变量一定也是可变的,所以一定是跟mut同时使用的。

用volatile关键字修饰的变量表示:该变量可能被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等

volatile关键字告诉编译器:该变量的值是随时可能发生变化的,编译器对访问该变量的代码就不再进行优化,每次使用它的时候必须从内存中取出该变量的值

volatile除了可以使用在变量上,还可以使用在结构体字段上:

sync标准模块

Channel

WaitGroup

如果要等待多个并发任务结束,可以使用WaitGroup。

通过设定计数器,让每一个线程开始时递增计数,退出时递减计数,直到计数归零时,解除阻塞。

输出:

更多参考代码可以查看: vlib/sync。

最后更新于

这有帮助吗?