This question is focused exclusively on the writer side.
So are you saying that this will also work (based
on https://go.dev/play/p/ZXMg_Qq3ygF )
mmapBufRaw[fSize-1] = 255 // W1
(*mmapBufAtomic.Load())[0] = 42 // W2
How about this, would that work as a "everything before the atomic. has to
appear as a CPU instruction 1st ?
mmapBufRaw[fSize-1] = 255 // W1
atomic.LoadInt64(randomVal) // any atomic access acts as barrier
mmapBufRaw[0] = 42 // W2
This is the exact mechanism I am trying to understand - what is the minimum
that golang needs to guarantee "as written" order synchronization, within a
specific single goroutine.
On Sunday, January 22, 2023 at 2:31:37 AM UTC+1 [email protected] wrote:
> On the write side, you write your mult-GB data using normal writes, then
> atomic.Store for the final flag uint. On the read side, you use an
> atomic.Load for the flag uint followed by regular loads for the remaining
> multi-GB of data.
> Reading a particular flag value ensures that the following loads see all
> the writes from before the writer wrote that particular flag value. This is
> guaranteed by the memory model, as the atomic read seeing the atomic write
> introduces the synchronized-before edge you need.
>
> I agree that the Go memory model doesn't directly address multi-process
> communication like this, but assuming both ends are Go this is guaranteed
> to work by the Go memory model. YMMV on what operations/barriers/etc. you
> need in other languages.
>
> On Saturday, January 21, 2023 at 1:46:09 PM UTC-8 [email protected]
> wrote:
>
>> On Sat, Jan 21, 2023 at 12:11 PM Peter Rabbitson (ribasushi) <
>> [email protected]> wrote:
>>
>>> On Saturday, January 21, 2023 at 7:48:12 PM UTC+1 [email protected]
>>> wrote:
>>> On Sat, Jan 21, 2023 at 10:36 AM Peter Rabbitson <[email protected]>
>>> wrote:
>>> Greetings,
>>>
>>> I am trying to understand the exact mechanics of memory write ordering
>>> from within the same goroutine. I wrote a self-contained runnable example
>>> with the question inlined here: https://go.dev/play/p/ZXMg_Qq3ygF and
>>> am copying its header here:
>>>
>>> // Below is a complete example, with the question starting on line 38:
>>> // how do I ensure that a *separate Linux OS process* observing `IPCfile`
>>> // (either via pread() or mmap()) can *NEVER* observe W2 before W1.
>>> // The only permissible states are:
>>> // 1. no changes visible
>>> // 2. only W1 is visible
>>> // 3. both W1 and W2 are visible
>>>
>>> This is based on my interpretation of the go memory model:
>>>
>>> Atomic memory operations are sequentially consistent, so here:
>>>
>>> (*mmapBufAtomic.Load())[fSize-1] = 255 // W1
>>> (*mmapBufAtomic.Load())[0] = 42 // W2
>>>
>>> The first atomic load happens before the second load. That also implies
>>> the first write (W1) happens before the second (W2). However, there is no
>>> guarantee that W2 will be observed by another goroutine.
>>>
>>> This is perfectly acceptable ( see point 2. above ). Also note that
>>> there is no other goroutine that is looking at this: the observers are
>>> separate ( possibly not even go-based ) OS processes. I am strictly trying
>>> to get to a point where the writer process exemplified in the playground
>>> will issue the CPU write instructions in the order I expect.
>>>
>>>
>>> I think what is really needed here is an atomic store byte operation. If
>>> this is the only goroutine writing to this buffer, you can emulate that by
>>> atomic.LoadUint32, set the highest/lowest byte, then atomic.StoreUint32
>>>
>>> This would not be viable: the W1 write is a single byte for the sake of
>>> brevity. In practice it will be a multi-GiB write, with a multi-KiB write
>>> following it, followed by a single-UInt write. All part of a lock-free
>>> "ratcheted" transactional implementation, allowing for incomplete writes,
>>> but no dirty reads - the "root pointer" is the last thing being updated, so
>>> an observer process sees "old state" or "new state" and nothing inbetween.
>>> This is why my quest to understand the precise behavior and guarantees of
>>> the resulting compiled program.
>>>
>>
>>
>> You realize, if W1 is a multi-GB write, another process will
>> observe partial writes for W1. But, I believe, if another process observes
>> W2, then it is guaranteed that all of W1 is written.
>>
>> I think the Go memory model does not really apply here, because you are
>> talking about other processes reading shared memory. What you are really
>> relying on is that on x86, there will be a memory barrier associated with
>> atomic loads. I don't know how this works on arm. I am not sure how
>> portable this solution would be. The memory model is explicit about
>> observing the effects of an atomic write operation, and sequential
>> consistency of atomic memory operations. So it sounds like an unprotected
>> W1 followed by an atomic store of W2 would still work the same way.
>>
>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "golang-nuts" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>>
>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/golang-nuts/c23d512e-a307-4f4d-bf23-74398c5cf42bn%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/golang-nuts/c23d512e-a307-4f4d-bf23-74398c5cf42bn%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/4c8feb7a-f2d9-4392-ad1d-f72253ccafd7n%40googlegroups.com.