【问题标题】:Simple multithreading application sometimes fails简单的多线程应用程序有时会失败
【发布时间】:2012-11-12 05:57:39
【问题描述】:

我正在做这个多线程应用程序只是为了看看@synchronized 指令是如何工作的。我读过如果所有线程都具有与@synchronized 的参数相同的对象,那么它们都在同一个锁上等待。所以我想使用 self 作为参数,因为它对所有线程都是相同的。
在这个应用程序中,有一个文本字段被所有线程多次编辑。我不关心性能,这只是一个测试,所以我没有将@synchronized 指令放在 for 之前,但在里面。

我使用的属性:

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

代码:

- (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;
        }
    }
}

有时应用程序成功,我看到 1000 作为文本字段值。但有时不是,我担心这是饥饿,我在文本字段上看不到任何内容,它是空的。我尝试了调试,但很难看看有什么问题,因为失败的标准对我来说似乎很随意,有时它就可以正常工作。

解决方案

@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];
        }
    }
}

【问题讨论】:

  • 这可能吗?我是不是发现了一个好问题?还是我只是在做梦? (+1 表示一个有趣的问题。)
  • 不知道.. 代码对我来说看起来不错.. 但我不确定 appkit 的哪些部分是线程安全的......
  • 您添加的解决方案不正确。 Notifications are posted and received on the same thread.-changeValue: 上设置断点,你会看到当前线程不是主线程。

标签: objective-c multithreading cocoa


【解决方案1】:

您总是在后台线程中更新您的 UI。这是不好的。你应该这样做;

 (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];    
        }
    }
}

尝试锁定实例并使用 sleep 方法使其更流畅。

要在后台线程中锁定一个变量,首先要锁定它,然后设置它的值并解锁它;

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

但是,您已经拥有@synchronized,这使得它可以保护同时从多个线程访问的变量。我认为@synchonized 块更容易理解。

在这种情况下,睡眠更合适。如果您在线程中放置 2 秒的睡眠时间,您可以看到 textField 每 2 秒更改一次,并且更明显且更有意义。

[NSThread sleepForTimeInterval:2000] // 更新 textField 后将其放入循环中并查看效果。

另外,最好创建一个runloop并在一些runloop中执行线程,这是一个更好的做法。

【讨论】:

    【解决方案2】:

    您正在从非主线程访问NSTextField,它是NSView 的子类。 That is not a safe thing to do。结果未定义;有时它似乎有效,有时却无效。

    【讨论】:

    • 什么是替代解决方案?我是否必须通过发送消息来调用主线程并使其执行选择器?
    • 是的,使用 -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] 或 GCD 在主线程上运行您的代码。 Here is an example. 搜索“NSView 线程更新”之类的内容应该会引导您找到更多示例。
    • 另请注意:向[NSNotificationCenter defaultCenter] 发布通知不是进入主线程的方式。通知的观察者在与通知的发布相同的线程上被同步调用。
    • 所以现在我正在使用 performSelectorOnMainThread:withObject:waitUntilDone: 方法。我应该为 waitUntilDone 参数传递 YES 吗?
    • 可能不会,因为你在线程中运行的代码不依赖于主线程上文本字段的更新。既然你显然是在试验——这不是现实的代码——为什么不尝试两种方式,看看会发生什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-06
    相关资源
    最近更新 更多