Most active commenters
  • cogman10(4)
  • colonwqbang(3)

←back to thread

271 points mithcs | 14 comments | | HN request time: 0.001s | source | bottom
Show context
woodruffw ◴[] No.45953391[source]
Intentionally or not, this post demonstrates one of the things that makes safer abstractions in C less desirable: the shared pointer implementation uses a POSIX mutex, which means it’s (1) not cross platform, and (2) pays the mutex overhead even in provably single-threaded contexts. In other words, it’s not a zero-cost abstraction.

C++’s shared pointer has the same problem; Rust avoids it by having two types (Rc and Arc) that the developer can select from (and which the compiler will prevent you from using unsafely).

replies(13): >>45953466 #>>45953495 #>>45953667 #>>45954940 #>>45955297 #>>45955366 #>>45955631 #>>45955835 #>>45959088 #>>45959352 #>>45960616 #>>45962213 #>>45975677 #
kouteiheika ◴[] No.45953466[source]
> the shared pointer implementation uses a POSIX mutex [...] C++’s shared pointer has the same problem

It doesn't. C++'s shared pointers use atomics, just like Rust's Arc does. There's no good reason (unless you have some very exotic requirements, into which I won't get into here) to implement shared pointers with mutexes. The implementation in the blog post here is just suboptimal.

(But it's true that C++ doesn't have Rust's equivalent of Rc, which means that if you just need a reference counted pointer then using std::shared_ptr is not a zero cost abstraction.)

replies(2): >>45953492 #>>45953505 #
1. cogman10 ◴[] No.45953505[source]
> very exotic requirements

I'd be interested to know what you are thinking.

The primary exotic thing I can imagine is an architecture lacking the ability to do atomic operations. But even in that case, C11 has atomic operations [1] built in. So worst case, the C library for the target architecture would likely boil down to mutex operations.

[1] https://en.cppreference.com/w/c/atomic.html

replies(2): >>45953966 #>>45954236 #
2. kouteiheika ◴[] No.45953966[source]
Well, basically, yeah, if your platform lacks support for atomics, or if you'd need some extra functionality around the shared pointer like e.g. logging the shared pointer refcounts while enforcing consistent ordering of logs (which can be useful if you're unfortunate enough to have to debug a race condition where you need to pay attention to refcounts, assuming the extra mutex won't make your heisenbug disappear), or synchronizing something else along with the refcount (basically a "fat", custom shared pointer that does more than just shared-pointering).
replies(1): >>45954774 #
3. goalieca ◴[] No.45954236[source]
Which platforms might that be? Even MIPS has atomics (at least pointer sized last i checked).
replies(1): >>45954811 #
4. colonwqbang ◴[] No.45954774[source]
Does there exist any platform which has multithreading but not atomics? Such a platform would be quite impractical as you can't really implement locks or any other threading primitive without atomics.
replies(3): >>45955009 #>>45955880 #>>45959619 #
5. cogman10 ◴[] No.45954811[source]
AFIAK, and I'm not MIPS expert, but I believe it doesn't have the ability to add a value directly to a memory address. You have to do something like

    // Not real MIPS, just what I've gleaned from a brief look at some docs
    LOAD addr, register
    ADD 1, register
    STORE register, addr
The LOAD and STORE are atomic, but the `ADD` happens out of band.

That's a problem if any sort of interrupt happens (if you are multi-threading then a possibility). If it happens at the load, then a separate thread can update "addr" which mean the later STORE will stomp on what's there.

x86 and ARM can do

    ADD 1, addr
as well as other instructions like "compare and swap"

    LOAD addr, register
    MOV register, register2
    ADD 1, register2
    COMPARE_AND_SWAP addr, register, register2
    if (cas_failed) { try again }
replies(1): >>45955512 #
6. cogman10 ◴[] No.45955009{3}[source]
Certainly such systems can pretty readily exist. You merely need atomic reads/writes in order to implement locks.

You can't create userspace locks which is a bummer, but the OS has the capability of enforcing locks. That's basically how early locking worked.

The main thing needed to make a correct lock is interrupt protection. Something every OS has.

To go fast, you need atomic operations. It especially becomes important if you are dealing with multiple cores. However, for a single core system atomics aren't needed for the OS to create locks.

replies(2): >>45955630 #>>45957478 #
7. unnah ◴[] No.45955512{3}[source]
On MIPS you can simulate atomics with a load-linked/store-conditional (LL/SC) loop. If another processor has changed the same address between the LL and SC instructions, the SC fails to store the result and you have to retry. The underlying idea is that the processors would have to communicate memory accesses to each other via the cache coherence protocol anyway, so they can easily detect conflicting writes between the LL and SC instructions. It gets more complicated with out-of-order execution...

    loop: LL r2, (r1)
          ADD r3, r2, 1
          SC r3, (r1)
          BEQ r3, 0, loop
          NOP
8. colonwqbang ◴[] No.45955630{4}[source]
I wrote "multithreaded" but I really meant "multicore". If two cores are contending for a lock I don't see how irq protection help. As long as there is only one core, I agree.
replies(1): >>45956149 #
9. addaon ◴[] No.45955880{3}[source]
> Does there exist any platform which has multithreading but not atomics?

Yes. Also, almost every platform I know that supports multi threading and atomics doesn’t support atomics between /all/ possible masters. Consider a microcontroller with, say, two Arm cores (multithreaded, atomic-supporting) and a DMA engine.

replies(1): >>45959154 #
10. cogman10 ◴[] No.45956149{5}[source]
On most multicore systems you can pin the IRQ handling to a single core. Pinning locking interrupts to a single core would be how you handle this.
replies(1): >>45978788 #
11. SkiFire13 ◴[] No.45957478{4}[source]
> You merely need atomic reads/writes in order to implement locks.

Nit: while it's possible to implement one with just atomic reads and writes, it's generally not trivial/efficient/ergonomic to do so without an atomic composite read-write operation, like a compare-and-swap.

12. lpribis ◴[] No.45959154{4}[source]
Yes but "atomic" operations with the DMA engine are accomplished through interrupts (atomic) or memory mapped IO configuration (atomic).
13. oconnor663 ◴[] No.45959619{3}[source]
The boring answer is that standard atomics didn't exist until C++11, so any compiler older than that didn't support them. I think most platforms (certainly the popular desktop/server platforms) had ways to accomplish the same thing, but that was up to the vendor, and it might not've been well documented or stable. Infamously, `volatile` used to be (ab)used for this a lot before we had proper standards. (I think it still has some atomic-ish properties in MSVC?)
14. colonwqbang ◴[] No.45978788{6}[source]
True, but locks are not only needed inside IRQ handler routines.