Was asked to wrap a blocking call to non-blocking during an interview today. So we (the interviewer and me) decided to achieve that by adding a background thread inside the nonblocking API. Here is the code I wrote:
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 }
Let me explain a little bit here:
read_next_sample()
is the blocking call provided;- line 44
get_most_recent_lux()
is the wrapped non-blocking API I need to provide. - Internally, it starts a thread which executes the
worker_thread()
function defined in line 36. worker_thread()
keeps calling the blocking call and writes data to a ringbuf.- So the reader can read most recent record data from the ringbuf.
Also please be noted:
- This programming language used here is C, not C++.
- This is a single reader single writer case.
- This is different than producer-consumer problem, since the wrapped API
get_most_recent_lux()
should always return most recent data.
Since this is a single reader single writer case, I believe:
- No lock required here.
- No atomic values required here.
(so the head in line 33 is not declared as atomic values,
and I use a normal evaluation operation (
head = idx
) in line 40).
Question: Is my above statement correct?
The interviewer keeps telling me that my statement is not correct for all CPU arch, so he believes mutex or atomic variable is required here.
But I don't think so.
I believe, indeed, the single line evaluation C code (head = idx
) can be translated to multiple assembly instructions, but only the last assembly instruction is for storing the updated value to the memory. So,
- before the last assembly instruction executed, the updated value has not been updated to the memory yet, so the reader will always read the old head value.
- after the last assembly instruction executed, the reader will always read the updated head value.
- in both case, it is safe, no corruption will happen.
- There is no other possibilities. Within a specified time period where only 1 write can happen (let's say change from 1 to 2), the reader can only read either 1 or 2, the reader will never read any value other than 1 or 2, like 0, 3, or 1.5.
Agree? I really can not believe there is any CPU arch that the code won't work. If there is, please educate me. Thanks very much.