【问题标题】:Thread safe static variables objective c线程安全的静态变量目标 c
【发布时间】:2010-10-28 02:17:41
【问题描述】:

在 Objective C 中有没有一种方法可以定义一个线程安全的静态 int?

例如,如果我有一个名为 Session 的类:

static unsigned int session_id = 1000;

- (int) generateSessionID{
        return session_id++;
}

我正在从不同的线程构造会话对象,每个会话对象 应该有一个唯一的ID。

【问题讨论】:

  • 您需要使用某种锁定语义。问题是您希望操作是原子的。但请记住,“++”根本不是原子的,它是一次几个操作。
  • 然后可以用 synchronized(self){return session_id++} 包围我的返回 session_id++。但是我不确定这是否会锁定类或它自己的对象。
  • 如所写,它会锁定实例,而不是类(不是你想要的)。也许是“@synchronized([self class])”?
  • @David Gelhar:不,不要在[self class] 上同步,因为子类的实例会在不同的类对象上同步。在显式类上同步,例如[MyClass class]

标签: objective-c thread-safety


【解决方案1】:

我认为你最好使用atomic operations 来修改session_idprevious question 谈论 OS X 的原子递增/递减操作,this page 谈论 OSAtomic 头文件。整数上的原子操作,很容易得到硬件支持,可能会比使用锁定结构快得多。

【讨论】:

【解决方案2】:

如果您说的是 Cocoa,则其中的互斥锁功能由 NSLockNSRecursiveLock 提供。

为了正确保护非原子资源,你需要这些互斥体,以免多个线程可能试图同时更改数据(导致损坏)或使用半更改状态的数据(导致无效数据)。

您的代码将如下所示:

static NSLock session_id_lock;
static unsigned int session_id = 1000;

- (int) generateSessionID{
    int new_id;
    [myLock lock];
    new_id = session_id++;
    [myLock unlock];
    return new_id;
}

如果您没有使用 Cocoa(或者我在与 iMac 的短暂插曲中记得的一些 Cocoa 小程序,记忆模糊,几乎没有用处),只需使用这个概念,将其翻译成您拥有的任何语言或框架:

  • 在使用或更改受保护资源之前锁定互斥锁。
  • 使用或更改资源。
  • 解锁互斥锁。
  • 额外建议 1:尽可能晚地锁定互斥锁并尽快解锁。
  • 额外建议 2:只锁定您需要的内容,以避免不必要的延误。

进一步解释最后一点:如果您在self 上同步两个完全不相关的事物(例如会话 ID 和用户 ID),尽管没有必要这样做,但它们会相互阻止。我更喜欢两个单独的互斥锁来保持低粒度。

当然,如果您只有会话 ID 上的互斥锁(但请参阅下面的警告),请随意使用 synchronized(self),但我更愿意按照自己的方式进行操作,这样我就不会被抓到稍后添加另一个受保护的资源。

在任何情况下(这是提到的警告),您可能会发现在self 上进行同步并不能充分保护静态变量,该变量将在多个对象之间共享。互斥体应该属于 data 而不是任何使用它的对象。

【讨论】:

  • 我来自 Java 世界,你可以简单地做:静态同步 int generateID(){return sessionId++}。如果我这样做,这是否兼容 -(int) generateSessionID{synchronized(self){return session_id++;}}?
  • 我认为这可以正常工作,尽管对 self(对象)的锁定可能比我做的更广泛。相反,我更喜欢特定的互斥锁,以避免不必要的互斥锁延迟(请参阅我的附加奖励建议)。
  • @user489579 关闭,但您不想在 self 上同步访问静态变量的实例方法;可能在 [self class] 上同步,或者在与您正在修改的值直接关联的对象上同步(例如,将 int 包装在 NSNumber 中并在 NSNumber 上同步)。
  • 啊,好点,我忘了它是静态的,所以可以被 多个 对象使用 - 我会调整答案。
  • 大卫,我明白你的意思,我想包装或声明一个 NSNumber 而不是 int,但是你将在何处/何时确切地释放 NSNumber,因为它将(可以)通过构造其他对象来生成它们的 id。
【解决方案3】:

4 年后的响应,目前在 iOS8 中。 :o) 对我来说最好的方法是使用如下的单例类:

(你的文件.h)

#import <Foundation/Foundation.h>

@interface singletonSessionId : NSObject

+ (singletonMsgNbr*)sharedSingleton;

- (void)generateSessionId;

@property NSInteger value;

@end

================================================ ====== (yourFile.m)

#import "singletonSessionId.h"

@implementation singletonSessionId
@synthesize value = _value;

+ (singletonMsgNbr*)sharedSingleton {

    static singletonSessionId *instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[singletonSessionId alloc]init];
        instance.value = 1000;
    });

    return instance;
}

- (void)generateSessionId {
    _value += 1;
}

@end

您只需为每个新的 Id 值调用方法“generateSessionId”。 我认为使用此类生成会话 ID 应该足够了。

希望对这篇文章的读者有所帮助。 :o)

编辑

正如一些读者所说,按照这种方式,NSInteger“值”不是线程安全的,只有单例是。

要获得一个完整的线程安全的“值”实例变量,只需将其 NSInteger 类型更改为 NSNumber(使用适当的方法转换为“int”)即 完全线程安全。

这可能是获得所需内容的最简单、最快的方法。

【讨论】:

  • @ondermerol :确实,这正是它的本意 => '您只需要为每个新的 Id 值调用方法 'generateSessionId''。
  • @XLE_22 :这个线程安全吗?假设 thread1 调用 generateSessionID,然后 thread2 在 thread1 有机会检索值之前调用 generateSessionID?他们不会都使用相同的ID吗?如果它们同时调用该方法,由于没有使用同步工具,是否存在内存损坏的可能性?
  • @jk7 dispatch_once 将阻止所有调用,直到其中一个调用成功。文档说“如果从多个线程同时调用,这个函数会同步等待,直到块完成。”事实上,该方法是专门为这个用例设计的:“这个函数对于在应用程序中初始化全局数据(单例)很有用。在使用或测试任何由块初始化的变量之前,总是调用这个函数。”跨度>
  • 这不是线程安全的,这正是@jk7 声明的原因。 dispatch_once与它无关,因为调用generateSessionId时没有调用它
  • @jk7 : 'fishinear' 是对的。那时,我认为拥有一个完美的线程安全单例就足以以线程安全的方式生成和递增变量。绝对不是这样的。在我看来,在没有竞争条件的情况下读写实例变量的最佳方法是使用 Grand Central Dispatch 提供的“dispatch_barriers”功能。需要实施的事情很少,但结果是 100% 确定的。
【解决方案4】:

有很多选项,包括(从高级到低级)@synchronizedObjective-C 指令、NSLockpthread_mutex_lock 和原子操作。

详情请阅读Threading Programming Guide 的“同步”部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-24
    相关资源
    最近更新 更多