跳到主要内容
版本:master

协程(coroutine)

1. 概述

本章节将系统性地介绍 GS 语言中的协程(Coroutine) 机制。协程是 GS 中用于实现并发、并行和协作式多任务的基本单元。本章内容面向已经学习过函数指针概念的 GS 语言初学者或希望了解或回顾 GS 协程基础的同学。通过本章学习,您将掌握 GS 协程的核心概念、创建方法、调度规则以及实际应用技巧。

2. 协程基础

2.1 协程的概念

在GS语言中,协程(Coroutine) 是并发编程的基本执行单元,它代表了一个可以暂停和恢复执行的轻量级任务流。与传统编程语言中的线程或简单的协程不同,GS协程融合了"域(Domain)"的概念,形成了一个独特而强大的并发编程范式。

GS中每个协程都是一个独立的执行流,拥有自己的执行上下文,但比传统操作系统线程更加轻量,可以创建成千上万个协程而不会导致系统资源耗尽。协程之间的切换通常由GS运行时系统管理,而不是操作系统内核,这使得上下文切换的成本更低。GS中的协程一般都需要在调度器的安排下,在有限的线程中排队执行。为了尽可能减少协程切换时的上下文切换开销,在正常执行流程中,GS的调度器不会主动打断协程执行,而是要等待协程主动让出(例如遇到锁、执行 coroutine.sleep 等)。

GS 不同域的协程是可以并行执行,GS运行时会尽可能地将它们调度到不同的操作系统线程上,充分利用多核处理器的计算能力。这种"域内串行、域间并行"的模型既保证了数据访问的安全性,又提供了真正的并行计算能力。

2.2 协程的创建

协程可以通过coroutin.createcoroutine.create_with_domaincoroutine.create_in_thread三种函数创建,除了指定协程名称与协程的入口函数以外,这几个协程创建函数的区别在在于是否能指定协程的执行域以及是否能指定协程在固定的线程执行。

协程可以通过coroutine.create系列的3个函数创建;除了必须指定的协程名称(可以是匿名)和入口函数相同以外,这几个函数的区别在于可以指定协程的执行域和指定调度这个协程的OS线程。接下来分别用样例介绍:

coroutine create(mixed name, string|function name_or_func, ...)此方法创建的协程执行域会被自动指定,相应规则如下:

  • 创建协程时,使用入口函数的参数域做为协程执行域。

  • 若入口函数的参数域为nil时,使用创建时的当前域做为协程的执行域

  • 使用默认的OS线程调度

// test.gs
#pragma parallel // 带有此选项的object下所有函数均为 parallel 的
public void test()
{
// 故意指定函数的参数域
function fn1 = (: co_entry, 1:);
fn1.bind_arg_domain(domain.create());

// 无参数域
function fn2 = (: co_entry, 2:);

// 创建协程
coroutine co1 = coroutine.create(nil, fn1);
coroutine co2 = coroutine.create(nil, fn2);

// 协程执行域就是函数的参数域
printf("----------------------\n");
printf("init domain :%M\n", this_domain());
printf("co1 entry domain: %M\n", co1.get_entry_domain());
printf("fn1 arg domain: %M\n", fn1.get_arg_domain());

printf("co2 entry domain: %M\n", co2.get_entry_domain());
printf("fn2 arg domain: %M\n", fn2.get_arg_domain());

printf("----------------------\n");
}

void co_entry(int n)
{
printf("+++++++++++++++++++++++++++\n");
printf("coroutine %d, entry domain: %M\n", n, this_domain());
printf("+++++++++++++++++++++++++++\n");
}

test();

示例2-1:coroutine.create 创建协程示例

输出结果如下:

----------------------
init domain :domain[2048:v0]zero
co1 entry domain: domain[2883348:v0]~d2883348:v0@(test@/test/test.gs)
fn1 arg domain: domain[2883348:v0]~d2883348:v0@(test@/test/test.gs)
co2 entry domain: domain[2048:v0]zero
fn2 arg domain: nil
----------------------
+++++++++++++++++++++++++++
coroutine 1, entry domain: domain[2883348:v0]~d2883348:v0@(test@/test/test.gs)
+++++++++++++++++++++++++++

Welcome driver shell.
GS 1.35.250803 Copyright (C) G-bits
Shell> +++++++++++++++++++++++++++
coroutine 2, entry domain: domain[2048:v0]zero
+++++++++++++++++++++++++++

从输出结果可以看到,由绑定了参数域的函数指针f1创建的co1的执行域与f1的参数域相同。未绑定参数域,参数域为nil的函数指针f2创建的协程co2使用了当前参数域zero作为协程执行域。之后入口函数在相应的协程与域中执行,可以看到函数的执行域被输出。

coroutine.create_with_domain(mixed name, domain domain, mixed name_or_func, ...): 创建协程时,使用参数指定的域做为协程执行域(entry domain)。

  • 协程执行域必须通过参数手动指定。
  • 使用默认的OS线程调度。
  • 入口函数的参数域必须与协程执行域一致(相同或者为nil)
// test.gs
#pragma parallel

public void test()
{
domain d = domain.create("xxx");

// 故意指定函数的参数域
function fn1 = (: co_entry :);
fn1.bind_arg_domain(d);

// 无参数域
function fn2 = (: co_entry :);

// 有参数域但是和协程执行域不一致
function fn3 = (: co_entry :);
fn3.bind_arg_domain(this_domain());

// 创建协程
coroutine co1 = coroutine.create_with_domain(nil, d, fn1);
coroutine co2 = coroutine.create_with_domain(nil, d, fn2);

// 这个创建要发生错误
coroutine co3;
catch(co3 = coroutine.create_with_domain(nil, d, fn3));

// 协程执行域就是函数的参数域
printf("----------------------\n");
printf("this domain:%M\n", this_domain());
printf("co1 entry domain: %M\n", co1.get_entry_domain());
printf("fn1 arg domain: %M\n", fn1.get_arg_domain());

printf("co2 entry domain: %M\n", co2.get_entry_domain());
printf("fn2 arg domain: %M\n", fn2.get_arg_domain());

printf("----------------------\n");
}

void co_entry()
{
coroutine.sleep(1);
printf("+++++++++++++++++++++++++++\n");
coroutine co = this_coroutine();
printf("entry domain: %M\n", co.get_entry_domain());
printf("+++++++++++++++++++++++++++\n");
}
test();

示例2-2:coroutine.create_with_domain 创建协程示例

输出结果如下:

Error(-1): The function 'co_entry' can not be called in domain[2887444:v0]xxx, expected to be called in domain[2048:v0]zero
Coroutine: co[440664:v0]boot
Domain: domain[2048:v0]zero [local call]
At unknown:0 (create_with_domain) in object[2872913:v0]/test/test.gs
Argument name = nil
Argument domain = domain[2887444:v0]xxx
Argument name_or_func = (: .co_entry :)object[2872913:v0]/test/test.gs<Parallel>
Domain: domain[2048:v0]zero [local call]
At /test/test.gs:25 (test) in object[2872913:v0]/test/test.gs
Variable d = domain[2887444:v0]xxx
Variable fn1 = (: .co_entry :)object[2872913:v0]/test/test.gs<Parallel>
Variable fn2 = (: .co_entry :)object[2872913:v0]/test/test.gs<Parallel>
Variable fn3 = (: .co_entry :)object[2872913:v0]/test/test.gs<Parallel>
Variable co1 = co[2937168:v0]anonymous
Variable co2 = co[2937208:v0]anonymous
Variable co3 = (optimized)
Variable $catch_ret_0 = nil
Domain: domain[2048:v0]zero [local call]
At /test/test.gs:47 (::entry) in object[2872913:v0]/test/test.gs
At <Internal routine>
----------------------
this domain:domain[2048:v0]zero
co1 entry domain: domain[2887444:v0]xxx
fn1 arg domain: domain[2887444:v0]xxx
co2 entry domain: domain[2887444:v0]xxx
fn2 arg domain: nil
----------------------

Welcome driver shell.
GS 1.35.250803 Copyright (C) G-bits
Shell> +++++++++++++++++++++++++++
entry domain: domain[2887444:v0]xxx
+++++++++++++++++++++++++++
+++++++++++++++++++++++++++
entry domain: domain[2887444:v0]xxx
+++++++++++++++++++++++++++

从示例的输出结果可以看到,以coroutine.create_with_domain方式创建的协程必须手动指定协程执行域。协程执行域与函数指针参数域冲突的情况下,比如函数指针fn3参数域为zero协程co3却指定执行域为xxx此时协程的创建就会报错函数不能在xxx域执行。其他的情况中,fn1函数指针参数域与协程co1的执行域相同。fn2函数指针无参数域,协程co2不会出现域冲突问题。

coroutine.create_in_thread(mixed name, domain domain, string|function name_or_func, ...):对于一些实时性要求较高,不希望堵在调度器里的任务,一般会使用 coroutine.create_in_thread 创建独立于调度器的协程,这种协程会在一个独立的线程上运行,服从操作系统的调度。

  • 创建协程时,使用参数指定的域做为协程执行域
  • 特别的,为这个协程准备一个单独的OS线程进行调度
  • create_in_thread创建的独立调度的协(线)程数量是有数量限制的
    • 'get_system_info().max_os_threads指令可以获取最大允许的协(线)程数量
// test.gs
#pragma parallel

public void test()
{
domain d = domain.create("xxx");

// 故意指定函数的参数域
function fn1 = (: co_entry :);
fn1.bind_arg_domain(d);

// 创建协程
coroutine co1 = coroutine.create_in_thread(nil, d, fn1);

// 协程执行域就是函数的参数域
printf("----------------------\n");
printf("this domain:%M, d: %M\n", this_domain(), d);
printf("co1 entry domain: %M\n", co1.get_entry_domain());
printf("fn1 arg domain: %M\n", fn1.get_arg_domain());
printf("----------------------\n");
}

void co_entry()
{
coroutine.sleep(1);
printf("+++++++++++++++++++++++++++\n");
coroutine co = this_coroutine();
printf("entry domain: %M\n", co.get_entry_domain());
printf("+++++++++++++++++++++++++++\n");
}
test();

示例2-3:coroutine.create_in_thread 创建协程示例

输出结果如下:

----------------------
this domain:domain[2048:v0]zero, d: domain[2879252:v0]xxx
co1 entry domain: domain[2879252:v0]xxx
fn1 arg domain: domain[2879252:v0]xxx
----------------------

2.3 协程的返回值

在协程结束后,通过get_ret函数获取协程的返回值,只要协程入口函数原型有声明返回值且在协程预处CoState.ENDING状态之后,就可以通过co.get_ret()获取这个返回值,这样大大方便了多线程相关业务的开发,避免需要额外利用queue、share_value或额外的回调方法去同步协程的结果,示例如下:

map co_get_data()
{
coroutine.sleep(0.5);
return make_parallel(get_system_info());
}

coroutine co = coroutine.create(nil, (: co_get_data :));
co.wait();
write(co.get_ret());

示例2-4:协程的返回值获取

可以看到通过协程返回值获取了system_info,结果为:

{ /* sizeof() == 56 */
"version" : "1.0.220829.01",
"run_in_main_thread" : false,
"first_execution" : true,
"git_commit_version" : "784cd0ccb2dc980199160a99e6d9661a5de609d2 @ origin/master @ 2022-09-16 07:27:53 +0000",
...后续略...
}

要注意的是,返回值如果是引用类型,必须为parallel或constant,以避免产生并发访问问题

3. 协程与域

在之前的章节中我们已经了解过域(Domain)的基础概念。GS 的协程必须在域(Domain)内执行,且同一时间一个域中仅可有一个协程执行。相关规则如下:

  • 一个域下可以有多个协程,但同一时刻只能有一个协程在该域内执行。
  • 不同域的协程可以并行执行。
  • 同一域的两个协程不能同时执行。
  • 协程被挂起(如调用coroutine.sleep)时,会释放占有的域,此时其他协程即可尝试进入该域并执行相关逻辑。

3.1 同域串行

让我们通过一些样例来了解协程与域的关系,这是一个两个不会让域的协程在同一域执行的样例:

test.gs
// test.gs
#pragma parallel

void task(string name, int duration, string format) {
for(int i = 0 upto 7)
{
printlnf(format + "[%s] output %d"NOR, name, i);
// coroutine.sleep(0.1);
}
}

void test()
{
domain shared_domain = domain.create(); // 创建一个共享域

// 创建两个协程,都指定在同一个 shared_domain 中执行
coroutine co1 = coroutine.create_with_domain("coroutine_A", shared_domain, (: task, "coroutine_A", 1, HIG:));
coroutine co2 = coroutine.create_with_domain("coroutine_B", shared_domain, (: task, "coroutine_B", 2, HIM:));

co1.wait(); // 等待协程A完成
co2.wait(); // 等待协程B完成
}

test();

示例3-1:两个不会让域的协程在同一域中执行

输出如下:

[coroutine_A] output 0
[coroutine_A] output 1
[coroutine_A] output 2
[coroutine_A] output 3
[coroutine_A] output 4
[coroutine_A] output 5
[coroutine_A] output 6
[coroutine_A] output 7
[coroutine_B] output 0
[coroutine_B] output 1
[coroutine_B] output 2
[coroutine_B] output 3
[coroutine_B] output 4
[coroutine_B] output 5
[coroutine_B] output 6
[coroutine_B] output 7

运行示例的输出中可以看到协程A或协程B在同一个域中轮流执行。同一协程中的循环输出总是连续的,总是8个[coroutine_A]输出或者8个[coroutine_B]输出连续出现。

3.2 同域让域

现在让我们更改代码,在示例代码3-1的基础上,函数中task放开coroutine.sleep处的注释,让协程挂起让出所占用域,看看会发生什么?示例如下:

test.gs
// test.gs
#pragma parallel

void task(string name, int duration, string format) {
for(int i = 0 upto 7)
{
printlnf(format + "[%s] output %d"NOR, name, i);
coroutine.sleep(0.1);
}
}

void test()
{
domain shared_domain = domain.create(); // 创建一个共享域
// 创建两个协程,都指定在同一个 shared_domain 中执行
coroutine co1 = coroutine.create_with_domain("coroutine_A", shared_domain, (: task, "coroutine_A", 1, HIG:));
coroutine co2 = coroutine.create_with_domain("coroutine_B", shared_domain, (: task, "coroutine_B", 2, HIM:));

co1.wait(); // 等待协程A完成
co2.wait(); // 等待协程B完成
}

test();

示例3-2:两个让域的协程在同一域中执行

[coroutine_A] output 0
[coroutine_B] output 0
[coroutine_B] output 1
[coroutine_A] output 1
[coroutine_A] output 2
[coroutine_B] output 2
[coroutine_A] output 3
[coroutine_B] output 3
[coroutine_A] output 4
[coroutine_B] output 4
[coroutine_A] output 5
[coroutine_B] output 5
[coroutine_A] output 6
[coroutine_B] output 6
[coroutine_B] output 7
[coroutine_A] output 7

从输出结果可以看到开始有的[coroutine_A][coroutine_B]输出交替出现的情况。协程A协程B在同一个域中,示例中协程A先占有域并执行循环,循环中调用了coroutine.sleep使得协程A让出了所在域,此时调度器会让另一个在该域中等待的协程B尝试占有域并执,两者不断地让域交替,所以可以看到不连续输出的交替。

3.3 不同域并行

继续修改样例,这次我们将两个协程创建在不同的域中。同时,我们移除让域的coroutine.sleep代码,代码如下:

test.gs
// test.gs
#pragma parallel

void task(string name, int duration, string format) {
for(int i = 0 upto 7)
{
printlnf(format + "[%s] output %d"NOR, name, i);
}
}

void test()
{
// 创建两个协程,在两个不同域中执行
coroutine co1 = coroutine.create_with_domain("coroutine_A", domain.create(), (: task, "coroutine_A", 1, HIG:));
coroutine co2 = coroutine.create_with_domain("coroutine_B", domain.create(), (: task, "coroutine_B", 2, HIM:));

co1.wait(); // 等待协程A完成
co2.wait(); // 等待协程B完成
}

test();

示例3-3:不同域并行示例

示例的输出如下:

[coroutine_A] output 0
[coroutine_A] output 1
[coroutine_B] output 0
[coroutine_B] output 1
[coroutine_B] output 2
[coroutine_B] output 3
[coroutine_A] output 2
[coroutine_A] output 3
[coroutine_A] output 4
[coroutine_B] output 4
[coroutine_A] output 5
[coroutine_B] output 5
[coroutine_B] output 6
[coroutine_A] output 6
[coroutine_A] output 7
[coroutine_B] output 7

这次,由于两个协程并未创建在同一域中,两者实际上被调度器并行执行,两个协程函数的输出同样是交替出现。

4. 协程的调度

在之前的协程与域的章节中我们已经了解到协程与域的关系。需要注意的是对于不同域下协程的执行描述一直是可以并行,而不是一定并行。尽管GS的调度器会尽可能均衡地将协程分配到多个OS线程上执行,但不同域的协程实际被调度到同一OS线程执行也是有可能的。具体的协程并行及调度规则如下:

  • GS的调度器会尽可能均衡地将协程分配到多个OS线程上执行。

  • 相同执行域的协程在任一时刻最多只会有一个协程正在运行:这些协程由相同的OS线程调度,必然不可能同时运行;

  • 不同执行域的协程是可以并行的:不同执行域的协程也可能由相同的OS线程调度,当不同执行域的协程由相同的OS线程调度时,这些协程是不会并行执行的。反之,则可以。

// test.gs
#pragma parallel
readonly int _count := 0;

// 演示多协程串行执行(计数不会错)
public void test()
{
// co1和co2的执行域是相同的
// 根据协程调度规则:相同执行域的协程在任一时刻只有一个在运行
// 因此能保证_count的计数不会错
int N = 10000;
_count := 0;;
coroutine co1 = coroutine.create(nil, (: co_entry, N :));
coroutine co2 = coroutine.create(nil, (: co_entry, N :));
co1.wait();
co2.wait();

// 绝对不可能报错
assert(_count == N * 2);
}

// 演示多协程并行执行(计数会错)
public void test2()
{
// co1和co2的执行域是不相同的
// 根据协程调度规则:不同执行域的协程是可以并发执行的
// 因此不能保证_count的计数不会错
int N = 10000;
_count := 0;
coroutine co1 = coroutine.create_in_thread(nil, domain.create(), (: co_entry, N :));
coroutine co2 = coroutine.create_in_thread(nil, domain.create(), (: co_entry, N :));
co1.wait();
co2.wait();

// 这里9999.9999%会报错
assert(_count == N * 2);
}

void co_entry(int n)
{
for (int i = 1 upto n)
{
if (i % 100 == 0)
coroutine.sleep(0);

_count := _count + 1;
}
}
test();
test2();

示例4-1:串行与并行执行示例

可以看到由于共享域的保护,test函数中的两个协程实际是并发的交替执行的,导致实际的加法操作是不会出错的。而test2函数的两个协程创建在不同的域中,极大概率被调度器实际并行执行,由于并行竞争最终的加法结果是错误的并触发了assert报错。

现在让我们尝试修改代码,尝试触发极少部分的执行test2但不会报错的情况。之前我们提到的create_in_thread会将协程放在单独的线程中调度,我们可尝试通过create_in_thread创建大量的由单独线程调度的死循环协程,此时test2创建的两个协程实际由同一个OS线程负责的概率,或由于负载较大被先后执行的概率会增大。示例如下:

// test.gs
#pragma parallel
readonly int _count := 0;

// 演示多协程串行执行(计数不会错)
public void test()
{
// co1和co2的执行域是相同的
// 根据协程调度规则:相同执行域的协程在任一时刻只有一个在运行
// 因此能保证_count的计数不会错
int N = 10000;
_count := 0;;
coroutine co1 = coroutine.create(nil, (: co_entry, N :));
coroutine co2 = coroutine.create(nil, (: co_entry, N :));
co1.wait();
co2.wait();

// 绝对不可能报错
assert(_count == N * 2);
}

// 演示多协程并行执行(计数会错)
public void test2()
{
// co1和co2的执行域是不相同的
// 根据协程调度规则:不同执行域的协程是可以并发执行的
// 因此不能保证_count的计数不会错
int N = 10000;
_count := 0;
coroutine co1 = coroutine.create_in_thread(nil, domain.create(), (: co_entry, N :));
coroutine co2 = coroutine.create_in_thread(nil, domain.create(), (: co_entry, N :));
co1.wait();
co2.wait();

// 这里9999.9999%会报错
assert(_count == N * 2);
}

void co_entry(int n)
{
for (int i = 1 upto n)
{
_count := _count + 1;
}
}

void co_dead_loop() // 死循环,用于通过create_in_thread占用线程资源
{
while(true)
{

}
}

for(int i = 0 upto 128)
{
// 创建
coroutine.create_in_thread(nil, domain.create(), (:co_dead_loop:));
}

test();
test2();
printf(HIG "We success" NOR);

示例4-2:不同域协程未并行示例

输出如下:

We success

多跑几次测试用例,可以看到输出We success的概率明显增大,符合我们修改代码前所做的假设。尽管test2中两个协程创建在两个不同的域中,由于实际负载较大,导致协程有更高的概率实际被并发交替执行而不是真正的并行。

5. 性能陷阱

5.1 creat_in_thread 陷阱

create_in_thread创建协程除了需要注意不要超过最大线程数量限制以外,由于在单独的线程中创建,服从操作系统的调度。操作系统必须保证CPU资源公平分配,避免线程'饿死'。这意味着如果线程数过多,大量的CPU会被这些线程占据导致其他任务难以为继。同时,GS中通过 coroutine.create_in_thread 创建的协程,在执行上下文切换工作时的开销可能较高,执行以下代码:

#pragma parallel

void busy_job_1()
{
for (int i = 0 ; i < 1; i++)
{
coroutine.sleep(0.000001);
}
}

void test_co()
{
array a = array.allocate(100);

int start_time = time.time_ms();
for (int i = 0 ; i < 100; i++)
a[i] = coroutine.create_with_domain(nil, domain.create(), (: busy_job_1 :));
for (int i = 0 ; i < 100; i++)
a[i].wait();
int end_time = time.time_ms();

write("Test co cost(ms):", end_time - start_time, "\n");
}
void test_co_in_thread()
{
array a = array.allocate(100);

int start_time = time.time_ms();
for (int i = 0 ; i < 100; i++)
a[i] = coroutine.create_in_thread(nil, domain.create(), (: busy_job_1 :));
for (int i = 0 ; i < 100; i++)
a[i].wait();
int end_time = time.time_ms();

write("Test co in thread cost(ms):", end_time - start_time, "\n");
}

test_co();
test_co_in_thread();

示例5-1:create_in_thread 性能陷阱

示例输出结果如下:

Test co cost(ms):14
Test co in thread cost(ms):28

以上代码分别创建100个普通协程和独立协程,执行函数 busy_job_1 并统计所有协程完成的总耗时。通过结果可以发现,虽然 busy_job_1 只是做了一次普通的 coroutine.sleep,两边的耗时却有显著差异。

因为创建独立协程有数量上的限制、另一方面是此类协程存在一些意想不到的开销。因此在使用 coroutine.create_in_thread 时需要额外注意。

5.2 调度器阻塞

当一个协程尝试从一个域进入另一个域时,会先调用当前域的leave方法,然后调用enter方法进入目标域,这一过程类似先将占有的锁unlock,然后lock一个新的锁。当一个协程尝试进入一个尚未被释放的域时,其会被阻塞,等待指定的域被释放。

但如果同一时间有大量的协程需要进入一个被长期阻塞的域,那么调度器的线程将被大量阻塞——甚至完全堵住。导致其他域的协程无法被正确的调度执行,示例如下:

// test.gs
parallel void busy_job_0()
{
while(true)
{

}
}

domain d = domain.create("xxx");
coroutine co1 = coroutine.create_with_domain(nil, d, (: busy_job_0 :));

// Make sure busy job start.
coroutine.sleep(1.0);

for (int i = 0; i < 100; i++)
coroutine.create_with_domain(nil, d, (: write, "Will be block" :));

write("Ready to create coroutine in other domain\n");

coroutine.create_with_domain(nil, domain.create(), (: write, "Even in other domain, this will not display." :));

执行以上代码,可以看到大量(100个)需要在xxx域输出"Will be block"的协程由于busy_job_0函数不会让域,持续的被堵在调度器中。此时尽管我们在后续创建在另一个域输出Even in other domain, this will not display.的协程,此协程不在xxx域,与之前的的所有协程都无域竞争关系,但却依然无法被正确的调度执行。

这是因为调度器的线程将被大量等待的协程阻塞了,导致后续创建的无域冲突的协程也无法正确的被调度执行。为了避免此类情况出现,在开发过程在要避免连续长时间地在同一个域上执行代码,即便确有需要,也应该在适当的时机执行 coroutine.sleep(0) 主动让出线程。

6. 拓展阅读

​ 更多有关协程的的相关内容请参照文档:

7. 总结

通过本章的学习,我们对GS语言中的协程机制有了全面而深入的理解。协程作为GS并发编程的核心,其独特的设计理念和强大的功能特性为我们提供了高效的并发及并行解决方案。

核心概念与特性

协程与域的结合是GS并发模型的精髓所在。协程提供了轻量级的执行单元,而域则确保了线程安全的数据访问,这种"域内串行、域间并行"的设计既保证了数据一致性,又实现了真正的并行计算能力。

GS协程具有以下关键特性:轻量高效(比传统线程更轻量,可创建大量协程而不耗尽资源)、自动调度(运行时系统自动管理挂起、恢复和调度)、域安全(通过域机制天然避免数据竞争和死锁)、灵活控制(可通过挂起操作实现协作式调度)以及返回值支持(简化异步编程模型)。

三种创建方式满足不同场景需求:coroutine.create适用于简单并发任务,自动管理执行域;coroutine.create_with_domain用于需要精确控制执行域的复杂场景;coroutine.create_in_thread适合实时性要求高的特殊任务。

实践要点与学习建议

在实际使用中,需要注意性能优化和陷阱规避:合理选择创建方式避免线程开销,适时使用挂起操作平衡效率与公平性,谨慎使用create_in_thread防止资源耗尽,注意长时间任务可能阻塞调度器,确保跨域访问的数据类型安全。