XMOS软件开发入门(5) - xc语言(3)之任务间使用通道通讯

本篇目的

继续XMOS的程序开发-xc语言的任务间的通讯机制:使用通道的方式。

开发环境

  • 硬件平台使用官方评估板"xCORE VOCAL FUSION XP-VF3100-BASE"
  • IDE开发环境win10 下的 xTIMEcomposer

简介

xC不使用信号量或者同步锁这些来同步两个任务,而是使用channels,streaming channels,interfaces这3种方式实现的。本篇说明前两种,即通道的方式。

现摘录官方文档对通道说明的一段和译文(谷歌翻译)

Channels provide a the simplest method of communication between tasks; they allow synchronous passing of untyped data between tasks. Streaming channels allow asynchronous communication between tasks; they exploit any buffering in the communication frabric of the hardware to provide a simple short-length FIFO between tasks. The amount of buffering is hardware dependent but is typically one or two words of data. Channels (both streaming and normal) are the lowest level of abstraction to the communication hardware available in xC and can be used to implement quite efficient inter-core communication but have no type-checking and cannot be used between tasks on the same core.

译文是:

通道提供了任务之间最简单的通信方法。 它们允许在任务之间同步传递非类型化数据。 流通道允许任务之间的异步通信。 他们利用硬件通信架构中的任何缓冲来在任务之间提供简单的短长度FIFO。 缓冲量取决于硬件,但通常是一个或两个数据字。 通道(流和常规通道)是xC中可用的通信硬件的最低抽象级别,可用于实现相当有效的内核间通信,但没有类型检查,因此不能在同一内核上的任务之间使用。

后面详述。

Channels用于任务间通讯

Channels是xC的一种用于任务间通讯的手段,有以下特性:

  • 任务之间通过Channels传递的数据是无类型的数据

    传递的数据不需要指定类型,而是用chan声明通道。

    任务通过chanend来使用声明的通道

  • 在执行于同一个core的任务之间不能用Channels传递数据

    本片先不关注这个问题,在后续的文章论述过指定任务在哪个core上执行这类内容之后,才能说明白。现在任务由系统自动分配运行的core。

  • Channels是阻塞的

    通过通道传输数据的任务,数据输出给通道的时候阻塞,直到通道的数据被另一个任务读取才往下执行;

    从通道读取数据的任务,在通道空了无数据的时候也会阻塞,直到有数据被读取到了才往下执行;

  • 通道通过:>读取数据,通过<:发送数据

下面通过2个具体的例子说明

  • 例子1:任务发送数据到通道,另一个任务不读取通道数据,发送的任务阻塞
#include <stdio.h>
#include <platform.h>

void task1(chanend c) {
    int d;
    while(1){
//        c :> d;    // 任务1读取通道的代码注释掉,不读取通道
//        printf("get data from channel: %d\n", d);
//        printf("task1 running...\n");
    }
}

void task2(chanend c) {
    int count = 0;
    while(count <= 50) {
        count++;
        if((count % 10) == 0) {
            printf("set data %d to channel\n", count);
            c <: count;  // 发送数据到通道,如果通道数据没有被读取,任务阻塞
        }
        printf("task2 running count: %d\n", count);
    }
}

int main () {
    chan c;    // 声明通道
    par {
        task1(c);
        task2(c);
    }
    return 0;
}

执行结果如下:

task2 running count: 1
task2 running count: 2
task2 running count: 3
task2 running count: 4
task2 running count: 5
task2 running count: 6
task2 running count: 7
task2 running count: 8
task2 running count: 9
set data 10 to channel

因为没有读取通道,导致发送数据到通道的任务阻塞。task2执行到c <: count这句就阻塞了。

  • 例子2:任务读取数据,无数据时阻塞,有数据时才往下执行
void task1(chanend c) {
    int d;
    while(1){
        c :> d;    // 读取通道数据,没有数据则阻塞,读取到数组则往下执行
        printf("get data: %d\n", d);
        printf("task1 running...\n");
    }
}

void task2(chanend c) {
    int count = 0;
    while(count <= 50) {
        count++;
        if((count % 10) == 0) {
            c <: count;  // 往通道发送数据
        }
    }
}

int main () {
    chan c;    // 声明通道
    par {
        task1(c);
        task2(c);
    }
    return 0;
}

task2循环从0到50循环,只有到10的整数倍的时候才把数据送往通道。

task1从通道获取数据,没获取到数据则时候是阻塞的,即不会打印任何东西,包括"task1 running…“,只有获取到了数据(数据是10的整数倍)的时候,才会打印信息并往下执行。

main函数声明了通道c,并传递给并发的任务task1和task2。这里通道c起到了task1和task2之间通讯(同步)的作用。

执行结果如下:

get data: 10
task1 running…
get data: 20
task1 running…
get data: 30
task1 running…
get data: 40
task1 running…
get data: 50
task1 running…

通过以上两个例子,可以理解Channels的任务间同步的作用,通道发送之后,必须等待通道接收了才能往下执行;通道接收的,需要从通道里接收到了数据才能往下执行。

Streaming Channels用于任务间通讯

streaming channels和channels类似,区别在于

  • 通过streaming chan声明,任务使用streaming chanend使用

  • 任务不会因像同步机制那样而阻塞,缓冲区满才会引起阻塞

    这个不阻塞,可以认为是通道使用了缓冲区,发送到通道就是发送到缓冲区,只要缓冲区还有空间,就不会阻塞,那怕没有任务读取通道,发送的任务也会不阻塞继续执行;但是如果缓冲区满了,发送的任务还是会阻塞的。

    接收的任务,只要有数据就不阻塞,如果读不到数据,仍会阻塞。

把上面使用Channels的程序改为使用Streaming Channels的,并稍作修改来说明,如下:

  • 例子1:task2发送,task1不接收,看task2是否阻塞(即上面的例子1改为streaming chan的)
#include <stdio.h>
#include <platform.h>

void task1(streaming chanend c) {
    int d;
    while(1){
//        c :> d;    // 任务1读取通道的代码注释掉,不读取通道
//        printf("get data from channel: %d\n", d);
//        printf("task1 running...\n");
    }
}

void task2(streaming chanend c) {
    int count = 0;
    while(count <= 50) {
        count++;
        if((count % 10) == 0) {
            printf("set data %d to channel\n", count);
            c <: count;  // 发送数据到通道,缓冲区未满前不阻塞,满了后才阻塞
        }
        printf("task2 running count: %d\n", count);
    }
}

int main () {
    streaming chan c;    // 声明通道
    par {
        task1(c);
        task2(c);
    }
    return 0;
}

执行结果:

task2 running count: 1
task2 running count: 2
task2 running count: 3
task2 running count: 4
task2 running count: 5
task2 running count: 6
task2 running count: 7
task2 running count: 8
task2 running count: 9
set data 10 to channel
task2 running count: 10
task2 running count: 11
task2 running count: 12
task2 running count: 13
task2 running count: 14
task2 running count: 15
task2 running count: 16
task2 running count: 17
task2 running count: 18
task2 running count: 19
set data 20 to channel
task2 running count: 20
task2 running count: 21
task2 running count: 22
task2 running count: 23
task2 running count: 24
task2 running count: 25
task2 running count: 26
task2 running count: 27
task2 running count: 28
task2 running count: 29
set data 30 to channel

结果和Channels不同,并没有一发送就阻塞,程序也没有执行完50次循环,而是发送了3次数据到通道然后阻塞,应该和之前官方文档说的信息对应上了:“利用硬件通信架构中的任何缓冲来在任务之间提供简单的短长度FIFO。 缓冲量取决于硬件,但通常是一个或两个数据字”。可见缓冲区应该有2个,所以前2个不阻塞,到第3个因为没有空的缓冲区了,所以阻塞,这可以通过把task2循环改为20做验证,不会阻塞,发送完后直接结束任务。

  • 例子2:task2先往通道发送3个数据,task1再读取通道数据,task1一直读取
#include <stdio.h>
#include <platform.h>

void task1(streaming chanend c) {
    int d;
    int count = 0;
    while(1){
        count++;
        if(count > 10) {  // 循环大于10之后才开始读取通道数据
            c :> d;
            printf("get data from channel: %d\n", d);
        }
        printf("task1 running count: %d\n", count);
    }
    printf("task1 finished\n");
}

void task2(streaming chanend c) {
    int count = 0;
    while(count < 3) {  // 快速的往通道发送3个数据
        count++;
        printf("set data %d to channel\n", count);
        c <: count;
    }
    printf("task2 finished\n"); 
}

int main () {
    streaming chan c;    // 声明通道
    par {
        task1(c);
        task2(c);
    }
    return 0;
}

执行结果为

set data 1 to channel
task1 running count: 1
set data 2 to channel
task1 running count: 2
set data 3 to channel
task1 running count: 3
task1 running count: 4
task1 running count: 5
task1 running count: 6
task1 running count: 7
task1 running count: 8
task1 running count: 9
task1 running count: 10
get data from channel: 1
task2 finished
task1 running count: 11
get data from channel: 2
task1 running count: 12
get data from channel: 3
task1 running count: 13

结果显示,task2发送3个数据,但是task2结束在task1读取了1个数据之后,可见在第3个的时候阻塞了,task1读取1个数据之后,空出来一个位置,使得task2继续执行,再次证明了上面所述的结论;这个例子也说明了streaming channels不是同步机制,而是异步,task2先发送数据到通道,不受读取影响而阻塞,之后task1读取,能把缓冲区的数据读回来,不受发送影响而阻塞,之后通道缓冲区空了,读取才阻塞。

  • 例子3:把例子2的task2循环次数3改为2。

代码不重复贴了,直接结果为

set data 1 to channel
task1 running count: 1
set data 2 to channel
task1 running count: 2
task2 finished
task1 running count: 3
task1 running count: 4
task1 running count: 5
task1 running count: 6
task1 running count: 7
task1 running count: 8
task1 running count: 9
task1 running count: 10
get data from channel: 1
task1 running count: 11
get data from channel: 2
task1 running count: 12

可见task2发送完两个数据就结束了,不等待通道的读取,不受task1的影响,再次证实了streaming channels在任务交互之间使用的异步方式。

通道数组

通道数组可以用于一个任务和多个任务通讯。select case 语法中可以使用通道数组名[int index]这样的来处理通道数组。

比如以下的例子,定义通道数组c[2],task1通过通道数组,同时和task2和task3通讯,task2使用c[0]和task1通讯,task3使用c[1]和task1通讯。

#include <stdio.h>
#include <platform.h>

void task1(chanend c[n], unsigned int n) {
    while(1){
        select {
        case c[int i] :> int x:
            printf("收到通道%d的数据: %d\n", i, x);
            break;
        }
    }
}

void task2(chanend c) {
    c <: 12;
}

void task3(chanend c) {
    c <: 34;
}

int main () {
    chan c[2];    // 声明通道数组
    par {
        task1(c, 2);
        task2(c[0]);
        task3(c[1]);
    }
    return 0;
}

执行结果

收到通道1的数据: 34
收到通道0的数据: 12

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值