Module xwrust::xwos::thd

source ·
Expand description

XWOS RUST:线程

XWOS的线程分为 分离态连接态

  • 分离态(detached) :分离态的线程退出后由操作系统自动回收其内存资源;
  • 连接态(joinable) :连接态的线程退出后需要被其他线程 join() ,之后才会被操作系统回收内存资源。

XWOS RUST的线程分为 动态线程静态线程 。无论哪种线程创建时都是 连接态(joinable) 的, 如果未将线程的 Handle 绑定到变量上,线程将自动转变为 分离态(detached)

动态线程

动态线程 是指通过动态内存分配器创建的线程。

动态线程的对象结构体、栈、线程闭包等资源都是通过内存申请的接口动态创建的。

XWOS RUST的动态线程库是仿照 std::thread 的来编写的。

use xwrust::xwos::thd;

thd::spawn(|_| {
    // 线程代码;
});

在上述代码中,spawn() 方法会返回 DThdHandle ,但由于 DThdHandle 没有被绑定到任何变量名上, 其生命周期结束后的 drop() 方法会自动将动态线程转变为 分离态(detached) 。此线程运行结束后,其资源会被操作系统自动回收。

动态线程也可以是 可连接的(joinable) ,可以将返回的 DThdHandle 绑定在变量上,防止 drop() 方法被调用。 然后通过 DThdHandle::join() 方法等待线程运行结束:

use xwrust::xwos::thd;

let res = thd::spawn(|_| {
    // 线程代码;
    // 返回值
});
match res {
    Ok(handler) => {
        match handler.join() {
            Ok(r) => {
                // `r` 是线程闭包的返回值。
            },
            Err(e) => {
                // `join()` 失败时的错误码可通过 `e.state()` 获取。
                // `e` 是 `DThdHandle<R>` ,重新被返回。
            },
        };
    },
    Err(rc) => {
        // `rc` 是 `spawn()` 失败时的错误码。
    },
};

DThdHandle::join() 方法会返回 [Result<R, Self>] , R 是返回值的类型,并放在 Ok() 中, 方法调用失败时,会将 DThdHandle 放在 Err() 中重新返回。此时,用户可以通过 DThdHandle::state() 获取失败原因, 并且在合适的重新调用 DThdHandle::join() 方法。

动态线程的线程函数

动态线程可以使用 FnOnce() 闭包作为线程函数。其原型是: FnOnce(Arc<DThdElement>) -> R , 动态线程函数运行时,参数是动态线程的元素 DThdElement ,返回值为泛型 R

动态线程的工厂模式

可以通过线程工厂 DThdBuilder 设置线程属性后,再创建动态线程:

use xwrust::xwos::thd;

let builder = thd::DThdBuilder::new()
                               .name("foo".into()) // 设置线程的名称
                               .stack_size(8 * 1024) // 设置线程栈大小
                               .privileged(true); // 设置系统权限

builder.spawn(|_| {
    // 线程代码;
    // 返回值
});
线程的名称

线程工厂可以通过 DThdBuilder::name() 为线程指定一个字符串名字。可以为空,默认为 "anon"

线程的栈大小

线程工厂可以通过 DThdBuilder::stack_size() 为线程指定栈内存大小,默认为XWOS的内核配置 XWMMCFG_STACK_SIZE_MIN

线程的系统权限

某些SOC内部的寄存器,只有拥有系统权限的线程才可以访问。线程工厂可以通过 DThdBuilder::privileged() 为线程指定是否具有系统特权,默认为拥有系统权限。

动态线程的元素

动态线程的元素 DThdElement 是存放与线程相关的信息的内存空间。 动态线程工厂 DThdBuilder 中设置的信息会被转移到 DThdElement 中。 动态线程运行时, Arc<DThdElement> 作为参数被传递到闭包。以 Arc<T> 进行封装是因为要将其放在堆中。

动态线程的句柄

静态线程的句柄 DThdHandle 功能类似于 std::thread::JoinHandle ,可用于控制动态子线程的退出。

中断动态线程的阻塞态和睡眠态

方法 DThdHandle::intr() 可用于中断线程的 阻塞态睡眠态 。 阻塞和睡眠的方法将以返回值 EINTR 退出。错误码 EINTR 会被转换为各个可阻塞线程的操作系统对象的错误码:

但是,当动态线程的 阻塞状态 是不可被中断的,不会发生中断。

方法 DThdHandle::intr() 是基于 ThdD::intr() 实现的,调用后者与前者在功能上没有区别。 动态线程自身的 ThdD 可通过 cthd::i() 获取。

通知动态线程退出

方法 DThdHandle::quit() 可用于父线程通知动态子线程退出。此方法不会等待动态子线程退出。

方法 DThdHandle::quit() 是基于 ThdD::quit() 实现的,调用后者与前者在功能上没有区别。 动态线程自身的 ThdD 可通过 cthd::i() 获取。

通知动态线程退出的方法会为动态子线程设置 退出状态 ,并中断 阻塞状态睡眠状态 。 阻塞和睡眠的方法将以返回值负的 EINTR 退出。错误码 EINTR 会被转换为各个可阻塞线程的操作系统对象的错误码:

但是,当动态子线程的 阻塞状态 是不可被中断的,方法 DThdHandle::quit() 只会为动态子线程设置 退出状态 ,不会发生中断。

动态子线程可以通过 cthd::shld_stop() 判断是否被设置了 退出状态 ,可以作为结束线程循环的条件。

等待动态线程退出

当父线程需要等待动态子线程退出,并捕获其返回值,需要使用方法 DThdHandle::join()

  • 如果动态子线程还在运行,此方法会阻塞父线程直到动态子线程退出。父线程的阻塞状态可被中断;
  • 如果动态子线程已经提前运行至退出,此方法可立即返回动态子线程的返回值。

此方法会消费 DThdHandle

  • 如果此方法执行成功,会消费掉 DThdHandle ,并将动态子线程的返回值放在 Ok() 中返回,因为线程已经结束,其 DThdHandle 的生命周期也应该结束;
  • 如果此方法执行失败,会重新在 Err() 中返回 DThdHandle ,用户可通过 DThdHandle::state() 方法获取失败的原因,并且在合适的重新调用 DThdHandle::join() 方法。
通知并等待动态线程退出

方法 DThdHandle::stop() 等价于 DThdHandle::quit() + DThdHandle::join()

动态线程的示例

XWOS/xwam/xwrust-example/xwrust_example_dthd

对比 std::thread

闭包原型不同
  • std::thread 的闭包原型是 FnOnce() -> R

  • xwrust::xwos::thd 的闭包原型是 FnOnce(Arc<DThdElement>) -> RDThdElement 用于存放与线程相关的元素,例如线程的名称。 DThdElement 由系统创建,并用作参数传递给闭包,用户可在闭包内直接使用。

  • 【举例】 std::thread 通过 thread::current() 获取线程自己的handle,然后获取线程的名称:

use std::thread;

let handler = thread::DThdBuilder::new()
    .name("named thread".into())
    .spawn(|| {
        let handle = thread::current();
        assert_eq!(handle.name(), Some("named thread"));
    }).unwrap();

handler.join().unwrap();
  • 【举例】 xwrust::xwos::thd 通过闭包参数 ele.name() 获取线程的名称:
use xwrust::xwos::thd;
use libc_print::std_name::println;

thd::DThdBuilder::new()
    .name("foo".into())
    .spawn(|ele| {
        println!("Thread name: {}", ele.name());
    });

若不需要使用 Arc<DThdElement> ,可使用 _ 占位。

use xwrust::xwos::thd;
use libc_print::std_name::println;

thd::DThdBuilder::new()
    .name("foo".into())
    .spawn(|_| {
        // 线程闭包
    });
spawn() 失败时的处理方式不同
use xwrust::xwos::thd;

match thd::DThdBuilder::new()
    .name("child".into())
    .spawn(|ele| {
        // 子线程闭包
    }) {
        Ok(h) => { // 新建子线程成功
        },
        Err(rc) => { // 新建子线程失败
        },
    };
子线程 panic!() 的处理方式不同
  • std::thread 可捕获子线程的 panic!()
  • xwrust::xwos::thd 的子线程 panic!() 后会导致整个系统 halt 。 目前 #![no_std] 环境的 unwind 支持还不完善,暂时无法实现类似于 std::thread 的捕获机制。

静态线程

静态线程 是指 通过动态内存分配器创建的线程,所需内存全部由编译器在编译连接阶段分配。 设计 静态线程 的目的就是为了避免使用动态内存分配。

因为无法确定线程会运行多长时间,只能将 静态线程 定义为 静态生命周期 的全局变量。

use xwrust::xwos::thd::*;

static STHD: SThd<1024, &str> = SThd::new("SThd", true);
pub fn xwrust_example_sthd() {
    let h = STHD.run(|sthd| { // 子线程
        // 线程功能
        "OK" // 返回值
    });
    let res = h.join();
    match res {
        Ok(r) => {
            // `r` 是线程的返回值。
        },
        Err(e) => {
            h = e;
            // `join()` 失败时的错误码可通过 `e.state()` 获取。
            // `e` 是 `SThdHandle` ,重新被返回。
        },
    };

}

静态线程可以是 可连接的(joinable) 。当另一线程通过 SThd::run() 启动静态线程后, 可将返回的 SThdHandle 绑定在变量上,然后通过 SThdHandle::join() 方法等待静态线程运行结束并获取返回值。

SThdHandle 生命周期结束前, SThdHandle::join() 未被调用过,其 drop() 方法会将静态线程自动变成 分离的(detached)

静态线程的线程函数

定义RUST闭包时,实际上会生成一个隐藏的结构体,然后将捕获的环境放在结构体内。 闭包其实是这个隐藏结构体的变量,并且是在函数栈上建立的,其生命周期只限于函数的局部。 因此,不能将闭包直接转移到静态线程的线程函数内部,因为生命周期不够长。

如何延长闭包生命周期?RUST语言的常用技巧是借助于 Box<T>Arc<T> 将其放入堆中。 但若使用了 Box<T>Arc<T> 就违背了“不使用动态内存分配器”的初衷。

综上,静态线程的线程函数只能是 普通的函数不捕获任何环境的闭包 。 只能访问 静态生命周期的全局变量 。 其原型是 fn(&Self) -> R 。其中 &Self 是指向静态线程自身的引用, R 是泛型返回值。

静态线程的句柄

静态线程的句柄 SThdHandle 由方法 SThd::run() 返回,功能类似于 std::thread::JoinHandle ,可用于控制静态子线程的退出。

中断静态线程的阻塞态和睡眠态

方法 SThdHandle::intr() 可用于中断线程的 阻塞态睡眠态 。 阻塞和睡眠的方法将以返回值 EINTR 退出。错误码 EINTR 会被转换为各个可阻塞线程的操作系统对象的错误码:

但是,当静态线程的 阻塞状态 是不可被中断的,不会发生中断。

方法 SThdHandle::intr() 是基于 ThdD::intr() 实现的,调用后者与前者在功能上没有区别。 动态线程自身的 ThdD 可通过 cthd::i() 获取。

通知动态线程退出

方法 SThdHandle::quit() 可用于父线程通知静态子线程退出。此方法不会等待静态子线程退出。

方法 SThdHandle::quit() 是基于 SThd::quit() 实现的,调用后者与前者在功能上没有区别。后者可以由静态现在自己调用,自己通知自己退出。

方法 ThdD::quit() 也可达到同样的效果。静态态线程自身的 ThdD 可通过 cthd::i() 获取。

通知静态线程退出的方法会为静态子线程设置 退出状态 ,并中断 阻塞状态睡眠状态 。 阻塞和睡眠的方法将以返回值负的 EINTR 退出。错误码 EINTR 会被转换为各个可阻塞线程的操作系统对象的错误码:

但是,当静态子线程的 阻塞状态 是不可被中断的,方法 SThdHandle::quit() 只会为静态子线程设置 退出状态 ,不会发生中断。

静态子线程可以通过 cthd::shld_stop() 判断是否被设置了 退出状态 ,可以作为结束线程循环的条件。

等待动态线程退出

当父线程需要等待静态子线程退出,并捕获其返回值,需要使用方法 SThdHandle::join()

  • 如果静态子线程还在运行,此方法会阻塞父线程直到静态子线程退出。父线程的阻塞状态可被中断;
  • 如果静态子线程已经提前运行至退出,此方法可立即返回静态子线程的返回值。

此方法会消费 SThdHandle

  • 如果此方法执行成功,会消费掉 SThdHandle ,并将静态子线程的返回值放在 Ok() 中返回,因为线程已经结束,其 SThdHandle 的生命周期也应该结束;
  • 如果此方法执行失败,会重新在 Err() 中返回 SThdHandle ,用户可通过 SThdHandle::state() 方法获取失败的原因,并且在合适的时机重新调用 SThdHandle::join() 方法。
通知并等待动态线程退出

方法 SThdHandle::stop() 等价于 SThdHandle::quit() + SThdHandle::join()

静态线程的示例

XWOS/xwam/xwrust-example/xwrust_example_sthd

只可在线程自身函数内部调用的方法

XWOS RUST有一组方法,只会对调用线程自身起作用,被实现在 xwrust::xwos::cthd 。命名中的 ccurrent 的意思。

Structs

Enums

Constants

Functions

  • 新建一个动态线程