在今天的一次采访中,被要求将阻塞调用封装到非阻塞。所以我们(面试官和我)决定通过在非阻塞 API 中添加一个后台线程来实现这一点。这是我写的代码:
30 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
31
32 struct SensorReading records[60*10] = {{0}};
33 size_t head = 0;
34
35
36 void * worker_thread(void *arg) {
37 while (1) {
38 size_t idx = (head + 1) % ARRAY_SIZE(records);
39 records[idx] = read_next_sample();
40 head = idx;
41 }
42 }
43
44 float get_most_recent_lux() {
45 static pthread_t worker = -1;
46 if (-1 == worker) {
47 struct SensorReading r = read_next_sample(); // This is the blocking call
48 records[0] = r;
49 if (-1 == pthread_create(&worker, NULL, worker_thread, NULL)) {
50 // error handling
51 }
52 return r.lux;
53 }
54 return records[head].lux;
55 }
让我在这里稍微解释一下:
read_next_sample()
是否提供了阻塞调用;- 第 44 行
get_most_recent_lux()
是我需要提供的封装非阻塞 API。 - 在内部,它启动一个线程来执行第
worker_thread()
36 行中定义的函数。 worker_thread()
不断调用阻塞调用并将数据写入 ringbuf。- 因此读者可以从 ringbuf 中读取最新的记录数据。
另请注意:
- 这里使用的这种编程语言是 C,而不是 C++。
- 这是一个单读单写的案例。
- 这与生产者-消费者问题不同,因为包装后的 API
get_most_recent_lux()
应始终返回最新数据。
由于这是一个单一的读者单一作家案例,我相信:
- 这里不需要锁。
- 这里不需要原子值。(所以第 33 行中的 head 没有被声明为原子值,我
head = idx
在第 40 行使用了正常的评估操作 ( ))。
问:我上面的说法对吗?
面试官一直告诉我,我的陈述并不适用于所有 CPU 架构,所以他认为这里需要互斥锁或原子变量。但我不这么认为。我相信,确实,单行评估 C 代码 ( head = idx
) 可以翻译成多条汇编指令,但只有最后一条汇编指令用于将更新后的值存储到内存中。所以,
- 在最后一条汇编指令执行之前,更新的值还没有更新到内存中,所以阅读器总是会读取旧的头值。
- 在执行最后一条汇编指令后,阅读器将始终读取更新后的头部值。
- 在这两种情况下,它都是安全的,不会发生腐败。
- 没有其他可能性。在只能发生 1 次写入的指定时间段内(假设从 1 变为 2),读取器只能读取 1 或 2,读取器永远不会读取除 1 或 2 之外的任何值,例如 0、3 或1.5。
同意?我真的不敢相信有任何 CPU 拱门代码不起作用。有的话请不吝赐教。非常感谢。