Hacker News new | past | comments | ask | show | jobs | submit login

What's a better alternative to synchronizing access to shared resources?



Treat it like GC and don't leave it up to the programmer.


An make a programmer unable to achieve highest performance when needed. We leave in supposedly free world. If you want to be "protected" be my guest and use languages with GC. Plenty of those. For somebody who need the opposite and uses "unprotected" tools - leave them alone. You have no rights to decide how other people do their work unless they're under your direct control.


Does that involve using concurrency primatives that basically don't allow access to any shared mutable state?


I'd say provide concurrency primitives to disallow direct access to shared mutable state. You still do the reads and the writes, but you let the system take and release locks for you.

Let's say you wanted to turn a list into a bounded list of 4 elements.

Race-condition insert:

    if (sz < 4) {
      list.insert(x);
      sz++;
    }
Safe insert:

    atomically {
      if (sz < 4) {
        list.insert(x);
        sz++;
      }
    }
So atomically organises the locking/unlocking/rollback for you such that a fifth element will not be inserted.


Aren't the semantics of this exactly the same as Java's synchronise? What error does it protect you against compared to synchronising on list? What happens if .insert() also uses atomically{} somehow?


Glad you asked!

synchronized locks code, atomically locks data.

It maps to my thinking better, because what I'm interested in is manipulating two or more resources at the same time. I'm not interested in making sure two threads aren't in the same region of code at the same time.

> What error does it protect you against compared to synchronising on list? Synchronized is perfectly fine for my example. I should have picked a better example of two resources (instead of list and size of list), because synchronized gets trickier once you start combining different pieces.

For example, I have two bounded lists, and I want to transfer an element from one to the other. I've already written synchronized versions of remove and insert, so let's try with them:

    transfer(otherlist) {
        x = otherlist.synchronizedRemove();
        this.synchronizedInsert(x);
    }
There will be a moment in time where the outside world can't see x in either list. Maybe I crash and x is gone for good. Or maybe the destination list becomes full and I'm left holding the x with nowhere to put it. So what is to be done? I could synchronize transfer but that still wouldn't fix the vanishing x, or the destination filling up. So I paid the performance cost of taking two/three locks and I've still ended up buggy.

I think the fix here is to lock each list, then no-one else can access them and it should fix the vanishing x:

    transfer(otherlist) {
        synchronized(this) {
            synchronized(otherlist) {
                x = otherlist.synchronizedRemove();
                this.synchronizedInsert(x);
            }
        }
    }
I think that's correct? But now I have taken too many locks - I only needed remove and insert not synchronizedRemove and synchronizedInsert. And now I've introduced deadlock possibility - if two transfers are attempted in opposite directions.

I can fix the too many locks problem, by exposing non-synchronized remove and insert and have transfer call them instead. But then callers and I will accidentally call the wrong one. I break any pretence of encapsulation by exposing unsafe and safe versions of a method. The deadlock is harder to fix. I'd need to synchronize the lists in some agreed order (and have everyone else obey that ordering in their other methods too!).

Instead, I want my implementation to look something like:

    BoundedList {

        transactional Int sz;
        transactional List list;

        transactional insert(x) {
            if (sz < 4) {
            list.insert(x);
            sz++;
            }
        }

        transactional remove() {...}

        transactional transfer(otherlist) {
            x = otherlist.remove();
            this.insert(x);
        }
    }
> What happens if .insert() also uses atomically{} somehow?

A good implementation would throw a compile-time error. A bad implementation could throw a runtime error.

In order to do this, transactional actions would need to be marked as such - to prevent mixing them up. atomically by definition is a non-transactional action (because it's the thing that commits all the steps to the outside world) so if you find an atomically inside a transaction, it's a type error.

You've already used a system like this if you've worked with any decent SQL implementation:

    BEGIN TRANSACTION

    UPDATE accounts
    SET money = money - 50
    WHERE accountId = 'src'

    UPDATE accounts
    SET money = money + 50
    WHERE accountId = 'dest'

    COMMIT TRANSACTION
I didn't take any 'locks'. I just wrapped two perfectly good individual actions and said 'run them both or not at all'. Though to be fair I am getting a lot of grief in another thread by suggesting that even novices could wrap up their SQL like that without getting it wrong.


Thanks for the detailed reply.

So, atomically{} is basically like a SQL transaction and would repeat or signify failure if it cannot commit the changes you make inside the code block, similar to a CAS lock-free algorithm. This seems quite limited though, you are basically constrained to writing code within the atomic block that deals with value types only, and with no side-effects. Otherwise how would the compiler or runtime know how to roll it back?

That sounds useful but doesn't seem to cover all the use cases of thread synchronization by a long shot. Isn't it also the case that even knowing how to implement interesting alrogithms in a lock-free manner is an area of significant ongoing research? For example I think only recently someone worked out how to implement a lock-free ring buffer (https://ferrous-systems.com/blog/lock-free-ring-buffer/)




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: