【发布时间】:2020-11-26 19:52:23
【问题描述】:
我的日常工作是使用对安全至关重要的嵌入式系统。我还与客户就编写安全嵌入式代码的主题进行了一些教学/咨询。编程语言的问题总是会出现,我们比较 C、D、Ada、Erlang、Rust 等。
我经常使用一个练习来进行演示。这是一个简单的双线程程序,每个线程获取一个全局变量(初始化为 0),将其加 1 并替换它十次。然后我们推测变量在末尾可以具有的最大值 (20) 和它的最小值(我们通常在使用正式证明证明它可以是 2 之前决定 10)。
我演示的一件事是程序的 C 版本可以编译(危险),但 Rust 版本不能(好!)。今天我写了 Ada 版本,并且有两个惊喜,我想请教 cmets。首先,我的程序:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
task AddTenA;
task AddTenB;
-- Global variable
x : Natural := 0;
finished : array (0 .. 1) of Natural := (0, 0);
-- Make sure that the compiler doesn't remove
-- all the addition.
pragma Volatile (x);
task body AddTenA is
y : Integer;
begin
for I in 1 .. 10 loop
y := x + 1;
x := y;
end loop;
finished (0) := 1;
end AddTenA;
task body AddTenB is
y : Integer;
begin
for I in 1 .. 10 loop
y := x + 1;
x := y;
end loop;
finished (1) := 1;
end AddTenB;
begin
while finished (0) + finished (1) < 2 loop
delay 0.001;
end loop;
Put_Line (Integer'Image (x));
end Main;
是的,我熟悉受保护对象和任务集合点,但这不是程序的重点。
我的两个惊喜:
-
即使有完整的编译器标志字母表(-fstack-check、-gnata、-gnato13、-gnatf、-gnatwa、-g、-gnatVa、-gnaty3abcdefhiklmnoOprstux、-gnatwe、-gnat2012、-Wall、-O2)我没有收到编译器警告。 Rust 告诉我全局变量没有唯一的所有者,因此它不会为我编译 Rust 版本的程序。我知道 SPARK 不处理任务,因此 Ada 不会生成任何警告,表明我在代码中存在潜在危险的竞争条件。这让我对 Ada 这样的语言感到惊讶。我错过了一个聪明的编译器或运行时选项吗?
-
当我执行等效的 C 程序时,最常见的输出是 20,但是当我多次运行它时,我得到的值是分散的,通常从大约 8 到 20。我已经运行了 Ada 程序(上图) 500,000 次,只得到 10 和 20 的值(中间没有值,99.9% 的输出是 20)。这表明 C 的 pthreading 和 Ada 的 Tasking 之间存在一些根本区别。那是什么? Ada 任务是否未映射到 pthread? Ada 版本中是否有隐式循环调度?
循环加法循环 10 次大概不会花费很长时间,所以我尝试将循环计数增加到 100 以查看任务是否可以更频繁地中断。然后我只得到 200 和 100。
【问题讨论】:
-
只是一个信息说明(仅供参考):Rust(和 SPARK)检测“数据竞争”,这是对可能导致错误值的非原子数据的多线程访问。 “竞争条件”是逻辑错误,允许您的数据处于您不期望的有效状态。 Rust 和 SPARK 都可能在编译时错过竞态条件(尽管 SPARK 确实有这样的优势,如果写得好,你的合约很可能会捕获它)。
-
您的系统上有多少个处理器?现代系统通常有多个处理器,在这样的系统上,如果 >= 3 个处理器可用,您的每个任务通常将在自己的处理器上运行,并且不会有任务调度。在这种情况下,种族应该是明显的,并且值
-
我记得有一次“有多少处理器?”是一个简单的问题。现在我们将处理器和内核区分开来,英特尔引入了超线程技术让我们保持警觉。回来 8080(我打字时我的办公桌抽屉里有一个),一切都被原谅了。无论如何,我有 4 个处理器和 2 个内核。我使用 Zoom 和其他多线程处理器来加载系统。我剩下的惊喜是,使用该程序的 C 和 Python 等效项,我得到的值通常在 10 到 20 之间,但有时低至 8。使用 Ada 版本,我似乎只能得到 10 或 20。跨度>
-
我的第一个想法是编译器可能会展开您的循环,甚至可能将最终分配保存到 x 直到结束。如果你还没有,你可以尝试在调试模式下编译。
标签: ada