Threads for steve9000

  1. 2

    Nice. Similar pattern for heavily threaded code accessing shared state - split the variable into an array & ensure there’s a cacheline of space for each to avoid false sharing..

    1. 15

      I’m keeping this one:

      Microservices

      grug wonder why big brain take hardest problem, factoring system correctly, and introduce network call too

      seem very confusing to grug

      1. 3

        This is a nice feature! For a toy language project, I did something similar. From the AST, built a DAG of types where each edge is e.g. “is exactly/is-a”. Then start flood-filling the DAG from known types. The trick is that you only push a type “down” when you cannot make any more “up” progress. Push a single one down & retry the “up”s. Repeat.

        1. 6

          There are multiple points here I disagree with:

          1. Go’s and Zig’s defer are rather different beasts Go runs defered statements at the end of the function, Zig at the end of scope. ant to lock a mutex insife a loop? Can’t use Go defer for that..
          2. destructors can’t take arguments or return values While most destructions only release acquired resources, passing an argument to a defered call can be very useful in many cases
          3. hidden code all defer code is visible in the scope. Look for all lines starting with defer in the current scope and you have all the calls. Looking for destructors means looking how drop is implemented for all the types in the scopes.
          1. 11

            Go’s and Zig’s defer are rather different beasts Go runs defered statements at the end of the function, Zig at the end of scope. ant to lock a mutex insife a loop? Can’t use Go defer for that..

            This distinction doesn’t really matter in a language with first-class lambdas. If you want to unlock a mutex at the end of a loop iteration with Go, create and call a lambda in the loop that uses defer internally.

            destructors can’t take arguments or return values

            But constructors can. If you implement a Defer class to use RAII, it takes a lambda in the constructor and calls it in the destructor.

            hidden code all defer code is visible in the scope

            I’m not sure I buy that argument, given that the code in defer is almost always calling another function. The code inside the constructor for the object whose cleanup you are defering is also not visible in the calling function.

            1. 4

              hidden code all defer code is visible in the scope

              I’m not sure I buy that argument, given that the code in defer is almost always calling another function. The code inside the constructor for the object whose cleanup you are defering is also not visible in the calling function.

              The point is that as a reader of zig, you can look at the function and see all the code which can be executed. You can see the call and breakpoint that line. As a reader of c++, it’s a bit more convoluted to breakpoint on destructors.

              1. 2

                you can look at the function and see all the code which can be executed.

                As someone that works daily with several hundred lines functions, that sounds like a con way more than a pro.

              2. 1

                But constructors can.

                This can work sometimes, but other times packing pointers in a struct just so you can drop it later is wasteful. This happens a lot with for example the Vulkan API where a lot of the vkDestroy* functions take multiple arguments. I’m a big fan of RAII but it’s not strictly better.

                1. 1

                  At least in C++, most of this all goes away after inlining. First the constructor and destructor are both inlined in the enclosing scope. This turns the capture of the arguments in the constructor into local assignments in a structure in the current stack frame. Then scalar replacement of aggregates runs and splits the structure into individual allocas in the first phase and then into SSA values in the second. At this point, the ‘captured’ values are just propagated directly into the code from the destructor.

                2. 1

                  If you want to unlock a mutex at the end of a loop iteration with Go, create and call a lambda in the loop that uses defer internally.

                  Note that Go uses function scope for defer. So this will actually acquire locks slowly then release them all at the end of function. This is very likely not what you want and can even risk deadlocks.

                  1. 1

                    Is a lambda not a function in Go? I wouldn’t expect defer in a lambda to release the lock at the end of the enclosing scope, because what happens if the lambda outlives the function?

                    1. 1

                      Sorry, I misread what you said. I was thinking defer func() { ... }() not func() { defer ... }().

                      1. 2

                        Sorry, I should have put some code - it’s much clearer what I meant from your post.

                3. 5

                  The first point is minor, and not really changing the overall picture of leaking by default.

                  Destruction with arguments is sometimes useful indeed, but there are workarounds. Sometimes you can take arguments when constructing the object. In the worst case you can require an explicit function call to drop with arguments (just like defer does), but still use the default drop to either catch bugs (log or panic when the right drop has been forgotten) or provide a sensible default, e.g. delete a temporary file if temp_file.keep() hasn’t been called.

                  Automatic drop code is indeed implicit and can’t be grepped for, but you have to consider the trade-off: a forgotten defer is also invisible and can’t be grepped for either. This is the change in default: by default there may be drop code you may not be aware of, instead of by default there may be a leak you may not be aware of.

                  1. 3

                    destructors can’t take arguments or return values. While most destructions only release acquired resources, passing an argument to a deferred call can be very useful in many cases.

                    Yes, more than useful:

                    • Zero-cost abstraction in terms of state: A deferred call doesn’t artificially require objects to contain all state needed by their destructors. State is generally bad, especially references, and especially long lived objects that secretly know about each other.
                    • Dependencies are better when they are explicit: If one function needs to run before another, letting it show (in terms of what arguments they require) is a good thing: It makes wrong code look wrong (yes, destruction order is a common problem in C++) and prevents it from compiling if you have lifetimes like Rust.
                    • Expressiveness: In the harsh reality we live in, destructors can fail.

                    I think the right solution is explicit destructors: Instead of the compiler inserting invisible destructor calls, the compiler fails if you don’t. This would be a natural extension to an explicit language like C – it would only add safety. Not only that: It fits well with defer too – syntactic sugar doesn’t matter, because it just solves the «wrong default» problem. But more than anything, I think it would shine in a language with lifetimes, like Rust, where long lived references are precisely what you don’t want to mess with.

                    1. 2

                      You could run an anonymous function within a loop in Go, just to get the per-loop defer. Returning a value in a defer is also possible.

                      func main() (retval int) {
                          for {
                              func() {
                                  // do stuff per loop
                                  defer func() {
                                      // per loop cleanup
                                  }()
                              }()
                          }
                          defer func() {
                              retval = 42
                          }()
                          return
                      }