2

I am doing this multithreading application just to see how the @synchronized directive works.I've read that if all the threads have the same object as argument of @synchronized, then they all wait on the same lock.So I thought to use self as argument, since it's the same for all threads.
In this application there is a text field being edited multiple times by all the thread.I don't care about performance, it's just a test so I don't put the @synchronized directive before the for, but inside it.

The properties that I use:

@property (weak) IBOutlet NSTextField *textField;
@property (nonatomic, copy) NSNumber* value;
@property (nonatomic,copy) NSMutableArray* threads;

The code:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    value= @0;
    textField.objectValue= value;
    for(int i=0; i<10; i++)
    {
        NSThread* thread=[[NSThread alloc]initWithTarget: self selector: @selector(routine:) object: nil];
        [threads addObject: thread];
        [thread start];
    }
}

- (void) routine : (id) argument
{
    for(NSUInteger i=0; i<100; i++)
    {
        @synchronized(self)
        {
            value= @(value.intValue+1);
            textField.objectValue= value;
        }
    }
}

Sometimes the application has success and I see 1000 as text field value.But sometimes not, I fear that this is starvation and I don't see anything on the text field, it's empty.I tried the debug but it's hard to see what is wrong, since the criteria of failure seems casual to me, sometimes it just works fine.

SOLUTION

@synthesize threads,value, textField;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    value= @0;
    threads=[[NSMutableArray alloc]initWithCapacity: 100];
    for(NSUInteger i=0; i<100; i++)
    {
        NSThread* thread=[[NSThread alloc]initWithTarget: self selector: @selector(routine:) object: nil ];
        [threads addObject: thread];
        [thread start];
    }
}

- (void) routine : (id) arg
{
    for(NSUInteger i=0; i<1000; i++)
    {
        @synchronized(self)
        {
            value= @(value.intValue+1);
            [textField performSelectorOnMainThread: @selector(setObjectValue:) withObject: value waitUntilDone: NO];
        }
    }
}
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • 1
    Is this possible? Did I juts find a good question? Or am I just dreaming? (+1 for an interesting problem.) –  Nov 23 '12 at 22:28
  • 1
    dont know.. code looks ok to me .. but i am not sure which parts of appkit are thread-safe... – Daij-Djan Nov 23 '12 at 22:31
  • 1
    Your added solution is not correct. [Notifications are posted and received on the same thread.](http://stackoverflow.com/questions/1004589/nsnotificationcenter-do-objects-receive-notifications-on-the-same-thread-they-a) Set a breakpoint on `-changeValue:` and you'll see that the current thread is not the main thread. – Kurt Revis Nov 24 '12 at 21:00

2 Answers2

3

You are accessing an NSTextField, which is a subclass of NSView, from a non-main thread. That is not a safe thing to do. The results are undefined; sometimes it appears to work, sometimes it doesn't.

Kurt Revis
  • 27,695
  • 5
  • 68
  • 74
  • What's the alternative solution? Do I have to call the main thread by sending somehow a message and make it execute the selector? – Ramy Al Zuhouri Nov 23 '12 at 22:37
  • 1
    Yes, run your code on the main thread by using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or GCD. [Here is an example.](http://stackoverflow.com/questions/7290931/gcd-threads-program-flow-and-ui-updating/7291056#7291056) Searching for things like "NSView thread update" should lead you to many more examples. – Kurt Revis Nov 23 '12 at 22:42
  • 1
    Also note: posting a notification to `[NSNotificationCenter defaultCenter]` is *not* a way to get to the main thread. The observer of the notification is called on the same thread as the posting of the notification, synchronously. – Kurt Revis Nov 25 '12 at 07:00
  • So now I'm using the performSelectorOnMainThread:withObject:waitUntilDone: method.Should I pass YES for the waitUntilDone argument? – Ramy Al Zuhouri Nov 25 '12 at 10:35
  • 1
    Probably not, because your code running in the thread is not dependent on the update of the text field on the main thread. Since you're obviously experimenting--this is not realistic code--why don't you try it both ways and see what happens? – Kurt Revis Nov 25 '12 at 20:07
1

You are always updating your UI in background thread. This is not good. You should do it like;

 (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    value= @0;
    textField.objectValue= value;
    for(int i=0; i<10; i++)
    {
        NSThread* thread=[[NSThread alloc] initWithTarget: self selector: @selector(routine:) object: nil];
        [threads addObject: thread];
        [thread start];
    }
}

- (void) routine : (id) argument
{
    for(NSUInteger i=0; i<100; i++)
    {
        @synchronized(self)
        {
            value= @(value.intValue+1);
            [textField performSelectorOnMainThread:@selector(setObjectValue:) withObject:value waitUntilDone: NO];    
        }
    }
}

Try locking the instances and use sleep method to make it more smoother.

To lock a variable inside the background thread you first lock it and then set its value and unlock it as;

[NSLock lock];
value=@(value.intValue+1)
[NSLock unlock];

But, you already have @synchronized which makes it safeguard for the variable being accessed from the multiple threads at the same time. I think @synchonized block is easier to understand.

Sleep is more appropriate in this case. If you place a sleep of say 2 seconds inside the thread then, you could see the textField changing every 2 seconds and it is more visible and makes more sense.

[NSThread sleepForTimeInterval:2000] // place this inside you loop after updating the textField and see the effect.

Also it is better to create a runloop and execute the thread in some runloop which is a better practise.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
Sandeep
  • 20,908
  • 7
  • 66
  • 106