Vala编程语言高级特性-多线程
多线程
Vala 中的线程
使用 Vala 编写的程序可以拥有多个执行线程,使其能够同时执行多个任务。具体如何管理这些线程超出了 Vala 的范围——线程可能共享也可能不共享单个处理器核心,这取决于运行环境。
一个非常简化的示例:
void thread_func() {stdout.printf("child_thread is running.\n");
}void main() {if (!Thread.supported()) {error("Cannot run without thread support.\n");}var thread = new Thread<void> ("child_thread", thread_func);stdout.printf("main_thread is running");
}
注意
请注意 main 方法开头的测试。
最初,UNIX 没有线程,这意味着一些传统的 UNIX API 在线程程序中可能存在兼容性问题。
使用此测试可以检查当前环境是否支持线程。
在大多数情况下,可以省略此测试。
现在看下面的语句:
var thread = new Thread<void> ("new_thread", thread_func);
我们创建了一个新线程,并且它会立即启动。第一个参数是线程的名称,第二个参数是新线程要执行的内容(函数)。泛型参数是线程返回值的类型。
这个程序仍然不会按我们预期的那样运行,因为如果没有任何形式的事件循环,当 Vala 程序的主/根/父线程(为运行 "main" 而创建的线程)结束时,程序就会终止。为了控制这种行为,你可以让线程进行协作。使用事件循环和异步队列可以非常强大地实现这一点,但在这篇线程介绍中,我们只展示线程的基本功能。
如果其主/父线程执行完毕,子线程将被杀死。根据这个事实,我们应该告诉主线程等待子线程完成,这是通过调用 Thread 模块中的 join
方法来实现的。
// ......
var thread = new Thread<void> ("child_thread", thread_func);
stdout.printf("main_thread is running");
thread.join(); // 注意
因为 join
方法,主线程必须等待子线程完成。
此外,线程可以告诉系统它当前不需要执行,从而建议应该改为运行另一个线程,这是通过静态方法 Thread.yield()
来完成的。如果将此语句放在上述 main 方法的末尾,运行时系统将暂停主线程片刻,并检查是否有其他可以运行的线程——当发现新创建的线程处于可运行状态时,它将转而运行该线程直到其完成——这样程序的行为看起来就符合预期了。然而,这并不能保证一定会发生。系统能够决定线程何时运行,因此可能在新线程完成之前就重新启动主线程并结束了程序。
所有这些示例都有一个潜在的问题,即新创建的线程不知道它应该在什么上下文中运行。在 C 语言中,你需要向线程创建方法提供一些数据,而在 Vala 中,你通常会传递一个实例方法,而不是静态方法。
更多示例请参见线程示例。
资源控制
每当多个执行线程同时运行时,就有可能发生数据被同时访问的情况。这可能导致竞态条件,即结果取决于系统决定在线程之间切换的时机。
为了控制这种情况,你可以使用 lock
关键字来确保某些代码块不会被其他需要访问相同数据的线程中断。最好的展示方式可能是通过一个示例:
public class Test : GLib.Object {private int a { get; set; }public void action_1() {lock (a) {int tmp = a;tmp++;a = tmp;}}public void action_2() {lock (a) {int tmp = a;tmp--;a = tmp;}}
}
这个类定义了两个方法,这两个方法都需要更改 "a" 的值。如果这里没有 lock
语句,这些方法中的指令可能会交织执行,导致对 "a" 的更改结果实际上是随机的。由于这里有 lock
语句,Vala 将保证如果一个线程已经锁定了 "a",另一个需要相同锁的线程将必须等待。
在 Vala 中,只能锁定正在执行代码的对象所属的成员。这看起来像是一个主要的限制,但实际上,这种技术的标准用法应该涉及那些各自负责控制一个资源的类,因此所有的锁定确实都是在类内部进行的。同样,在上面的示例中,对 "a" 的所有访问都被封装在类中。