No TryLock in the sync package?

2,793 views
Skip to first unread message

Luke Scott

unread,
Jul 31, 2012, 8:58:05 PM7/31/12
to golan...@googlegroups.com
I was working on an iPhone app the other day. I had an issue that could only be solved by a tryLock function. Learning GO, I looked at its sync packaged and was surprised not to find one.

The app reads QRCodes from the camera. I had an issue where a barcode "double scan". The solution looked something like this:

if(barcode != nil && [mutex tryLock] == YES)
{
    [delegate performSelectorOnMainThread:@selector(barcodeScanned:) withObject:barcode waitUntilDone:YES];
    [self stopCamera];
    [mutex unlock];
}

Even though I tell "performSelectorOnMainThread" to wait, the camera still calls the method containing this code continually. I wouldn't have been able to solve this issue without tryLock. The key is I needed to be able to ensure only one successful scan got through, and abort the rest.

Why isn't there a TryLock function in Go? I'm sure there are plenty of situations where you need a lock, but would rather abort and do something else if something else is holding the lock.

You can somewhat do it with channels:

select {
    case lock <- true:
    default:
}

But in some situations I've found mutex locks to be better. I use both mutex locks and channels in my code.


Luke

David Symonds

unread,
Jul 31, 2012, 9:00:25 PM7/31/12
to Luke Scott, golan...@googlegroups.com
On Wed, Aug 1, 2012 at 10:58 AM, Luke Scott <lu...@visionlaunchers.com> wrote:

> Why isn't there a TryLock function in Go? I'm sure there are plenty of
> situations where you need a lock, but would rather abort and do something
> else if something else is holding the lock.

It's come up a few times. Check the golang-nuts archive for previous
discussion. In short, TryLock is inherently racy, and there's almost
definitely a better way to achieve the same end goal.


Dave.

Andrew Gerrand

unread,
Jul 31, 2012, 9:01:51 PM7/31/12
to Luke Scott, golan...@googlegroups.com
On 1 August 2012 10:58, Luke Scott <lu...@visionlaunchers.com> wrote:
> if(barcode != nil && [mutex tryLock] == YES)
> {
> [delegate performSelectorOnMainThread:@selector(barcodeScanned:)
> withObject:barcode waitUntilDone:YES];
> [self stopCamera];
> [mutex unlock];
> }

This is inherently racy. What if tryLock returns true, and then the
holder of the lock releases it immediately afterward?

The feature leads to bad design (and bugs), so we don't include it in
the standard library.

Andrew

David Symonds

unread,
Jul 31, 2012, 9:05:45 PM7/31/12
to Andrew Gerrand, Luke Scott, golan...@googlegroups.com
On Wed, Aug 1, 2012 at 11:01 AM, Andrew Gerrand <a...@golang.org> wrote:

> On 1 August 2012 10:58, Luke Scott <lu...@visionlaunchers.com> wrote:
>> if(barcode != nil && [mutex tryLock] == YES)
>> {
>> [delegate performSelectorOnMainThread:@selector(barcodeScanned:)
>> withObject:barcode waitUntilDone:YES];
>> [self stopCamera];
>> [mutex unlock];
>> }
>
> This is inherently racy. What if tryLock returns true, and then the
> holder of the lock releases it immediately afterward?

Actually Luke's code is fine (Objective C's [mutex tryLock] will grab
the lock; think of it as a non-blocking Lock), but it's easy to get it
wrong.

> The feature leads to bad design (and bugs), so we don't include it in
> the standard library.

Agreed.

Andrew Gerrand

unread,
Jul 31, 2012, 10:34:27 PM7/31/12
to David Symonds, Luke Scott, golan...@googlegroups.com
On 1 August 2012 11:05, David Symonds <dsym...@golang.org> wrote:
> On Wed, Aug 1, 2012 at 11:01 AM, Andrew Gerrand <a...@golang.org> wrote:
>
>> On 1 August 2012 10:58, Luke Scott <lu...@visionlaunchers.com> wrote:
>>> if(barcode != nil && [mutex tryLock] == YES)
>>> {
>>> [delegate performSelectorOnMainThread:@selector(barcodeScanned:)
>>> withObject:barcode waitUntilDone:YES];
>>> [self stopCamera];
>>> [mutex unlock];
>>> }
>>
>> This is inherently racy. What if tryLock returns true, and then the
>> holder of the lock releases it immediately afterward?
>
> Actually Luke's code is fine (Objective C's [mutex tryLock] will grab
> the lock; think of it as a non-blocking Lock), but it's easy to get it
> wrong.

Ah, I see.

To achieve a similar thing using Go's synchronization primitives, you
would associate a sync.Once value with the barcode object.

http://golang.org/pkg/sync/#Once

Andrew

Jan Mercl

unread,
Aug 1, 2012, 1:39:53 AM8/1/12
to Luke Scott, golan...@googlegroups.com
On Wed, Aug 1, 2012 at 2:58 AM, Luke Scott <lu...@visionlaunchers.com> wrote:

if(barcode != nil && [mutex tryLock] == YES)
{
    [delegate performSelectorOnMainThread:@selector(barcodeScanned:) withObject:barcode waitUntilDone:YES];
    [self stopCamera];
    [mutex unlock];
}

if barcode != nil && /*sync/*/atomic.CompareAndSwapInt32(&/*barcode.*/foo, 0, 1) {
        // safe we are here
}

-j

roger peppe

unread,
Aug 1, 2012, 9:26:20 AM8/1/12
to Luke Scott, golan...@googlegroups.com
On 1 August 2012 01:58, Luke Scott <lu...@visionlaunchers.com> wrote:
> You can somewhat do it with channels:
>
> select {
> case lock <- true:
> default:
> }
>
> But in some situations I've found mutex locks to be better. I use both mutex
> locks and channels in my code.

I've always thought that sending to and receiving from a channel
with a buffer size of one was equivalent to using sync.Mutex.Lock and
Unlock. I realise that sync.Mutex performs better and the zero value
is automatically usable, but is there any other difference?

Luke Scott

unread,
Aug 1, 2012, 12:45:09 PM8/1/12
to golan...@googlegroups.com, Luke Scott
On Tuesday, July 31, 2012 10:39:53 PM UTC-7, Jan Mercl wrote:
On Wed, Aug 1, 2012 at 2:58 AM, Luke Scott wrote:

if(barcode != nil && [mutex tryLock] == YES)
{
    [delegate performSelectorOnMainThread:@selector(barcodeScanned:) withObject:barcode waitUntilDone:YES];
    [self stopCamera];
    [mutex unlock];
}

if barcode != nil && /*sync/*/atomic.CompareAndSwapInt32(&/*barcode.*/foo, 0, 1) {
        // safe we are here
}

-j

That's awesome. I was looking at the sync package's Lock function, and it does almost the same thing. It looks like a TryLock function would look like this:

func (m *Mutex) TryLock() bool {
return atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
}

Is this correct? If so, what's the problem?

(Kind of odd sync uses sync/atomic. With the other way packages are arranged, you would think atomic uses sync because atomic is a sub-package (that's how other packages do it). Would have thought it would have been atomic/sync, or by itself, or in another package like runtime.)

Luke

Andrew Gerrand

unread,
Aug 1, 2012, 6:50:14 PM8/1/12
to Luke Scott, golan...@googlegroups.com
There's no such thing as a "sub-package". They're just packages in
directories, organized by whatever makes the most sense in context.

Examples can be found of both conventions:

net/http/httputil uses net/http uses net
hash/crc32 uses hash
crypto/md5 uses crypto

text/template uses text/template/parse
database/sql uses database/sql/driver
sync uses sync/atomic

Andrew
Reply all
Reply to author
Forward
0 new messages