Objective-C/Cocoa 线程和消息传递难题

发布于 2024-08-12 23:14:50 字数 2528 浏览 3 评论 0原文

我有一个问题已经困扰我一段时间了,我已经想出了一个解决方案,我将在下面详细介绍,虽然它似乎运行良好,但从设计的角度来看,我对它并不是非常热衷,我我很好奇是否有人对更好的方法有任何建议。

基本上,我有一个共享资源,可以说它是一个文件目录。我有一个管理此资源的对象(我们将其称为 BossOfEverthing 类的实例)。 BossOfEverthing 处理该目录中文件的添加、删除、修改和检索数据。当另一个对象想要访问共享资源时,它通过 BossOfEverthing 实例来实现。 BossOfEverthing 在内部使用锁,因为它的客户端对象可以并且确实存在于单独的线程上。 BossOfEverthing 还维护一组对遵守 BossOfEverthingClient 协议的客户端对象的引用。当 BossOfEverthing 即将更改有关共享资源的任何内容时(可能是由于来自其客户端之一的请求),它会通过为每个客户端调用适当的选择器来提前通知所有客户端,以便它们都有机会响应第一的。 BossOfEverthing 实际上是 Boss,即。客户无权决定是否同意更改共享资源,但他们有机会首先执行任何必要的清理活动。在我看来,BossOfEverthing 好像有很多“代表”。我需要做的和正常委托模式之间的区别在于:

  1. 有许多委托/客户端
  2. 在 BossOfEverthing 实例的整个生命周期中,委托/客户端被创建和销毁多次(实际上它存在于整个应用程序的整个生命周期中) )。

当一个对象想要成为 BossOfEverthing 实例的客户端时,它会调用 [BossOfEverthing addMeToYourClientsList:](通常来自其 init 方法),而当一个对象想要停止成为 BossOfEverthing 的客户端时,它会调用 [BossOfEverthing removeMeFromYourClientList:](来自其 dealloc 方法) )。这样,BossOfEverthing 就知道共享资源发生变化时要通知谁(以及在哪个线程上)。

通常我会使用通知或 KVO 向客户端发送消息,但问题是所有客户端都需要有机会在资源实际更改之前做出适当的响应(就像在正常的委托模式中一样)。当接收者响应时,通知或 KVO 都不会阻塞。

好吧,这一切看起来都很棒,但是考虑一下这个场景:

  1. BossOfEverthing 即将更改共享资源,例如。 [BossOfEverthingchangeSomething] 由线程 1 上的某个对象调用
  2. [BossOfEverthingchangeSomething] 获取与客户端数组关联的锁
  3. [BossOfEverthingchangeSomething] 开始迭代客户端数组,并在每个客户端的相应线程上调用每个客户端的 SomethingIsAboutToBeChanged
  4. 方法其中一个客户端 (clientX) 即将消失,因此它从其 dealloc 方法 [BossOfEverthing removeMeFromYourClientList:] 中调用线程 2 上的
  5. [BossOfEverthing removeMeFromYourClientList:] 尝试获取与线程 2 上的客户端数组关联的锁,因此它可以删除 clientX

这里发生的情况是我最终陷入死锁,因为:

  1. [BossOfEverthing changeSomething] (已获取线程 1 上的锁)正在等待 [clientX someIsAboutToBeChanged] 从 thread2 返回
  2. 线程 2 被卡住等待获取当前由线程 1 上的 [BossOfEverthing changeSomething] 拥有的锁,无法响应其 SomethingIsAboutToBeChanged 方法

是我为解决此问题所做的操作:

  1. 这 它的释放方法(仅当retainCount==1时)
  2. [BossOfEverthingremoveMeFromYourClientList:]而不是等待锁,只是尝试获取它,如果不能获取它,则返回NO。
  3. 如果[BossOfEverthing removeMeFromYourClientList:]返回NO给[clientXrelease]那么[clientXrelease]调用[selfperformSelector:@selector(release)withObject:nilafterDelay:0.1],否则它只是调用[superrelease]

这样clientX就有机会响应 [BossOfEverthing changeSomething] 可能发送的任何消息,并允许 [BossOfEverthing changeSomething] 完成其业务并释放锁定,同时对 [clientX release] 的另一个调用在延迟中排队。

我遇到的问题是:

  1. 客户端对象属于各种类,因此我必须将覆盖的释放方法复制粘贴到每个类中。将相同的代码复制并粘贴到多个类中有些事情让我感到厌烦。如果 Objective-C 允许多重继承,那么我也许可以创建另一个类“BossOfEverthingClient”,其唯一定义的方法将是重写版本。
  2. 整个过程看起来有点复杂。

不管怎样,非常感谢您阅读我冗长的帖子,我期待任何人的意见。 再次感谢!

I have a problem that has been plaguing me for awhile now, I have come up with a solution which I will detail below, and although it seems to be working well I'm not super enthusiastic about it from a design point of view and I'm curious if anyone would have any recommendations about a better way to do this.

Basically, I have a shared resource, lets just say it's a directory of files. I have a single object that manages this resource (we'll call it an instance of class BossOfEverthing). BossOfEverthing handles adding, deleting, modifying and retrieving data from the files within that directory. When another object wants to access the shared resource it does so through the BossOfEverthing instance. BossOfEverthing uses locks internally since its client objects can and do exist on separate threads. BossOfEverthing also maintains an array of references to client objects that observe the BossOfEverthingClient protocol. When BossOfEverthing is about to change anything about the shared resource (perhaps due to a request from one of its clients) it notifies all of the clients ahead of time by calling an appropriate selector for each client so that they can all have a chance to respond first. BossOfEverthing is in fact the Boss, ie. the clients have no say so as to whether they approve of the shared resource being changed, but they are given the chance to perform any requisite cleanup activities first. The way I look at it, it's as if BossOfEverthing has many 'delegates'. The difference between what I need to do and the normal delegation pattern is that:

  1. There are many delegates/clients
  2. Delegates/clients are created and destroyed many times throughout the lifetime of the BossOfEverthing instance (which in fact exists throughout the lifetime of the entire application).

When an object wants to be a client of the BossOfEverthing instance it calls [BossOfEverthing addMeToYourClientsList:] (usually from its init method) and when an object wants to stop being a client of BossOfEverthing it calls [BossOfEverthing removeMeFromYourClientList:] (from its dealloc method). This way BossOfEverthing knows who to notify (and on what thread) when the shared resource changes.

Normally I would have used notifications or KVO to message the clients, but the hitch is that all of the clients need to have a chance to respond appropriately BEFORE the resource actually changes (like in a normal delegation pattern). Neither notifications or KVO block while the receivers are responding.

OK, that all seems great, but take this scenario:

  1. BossOfEverthing is about to change the shared resource, eg. [BossOfEverthing changeSomething] is called by some object on thread 1
  2. [BossOfEverthing changeSomething] acquires the lock associated with the client array
  3. [BossOfEverthing changeSomething] begins iterating through the client array and calling each client's somethingIsAboutToBeChanged method on the appropriate thread for each client
  4. In the meantime one of the clients (clientX) is about to go out of existence, so it calls [BossOfEverthing removeMeFromYourClientList:] on thread 2 from within its dealloc method
  5. [BossOfEverthing removeMeFromYourClientList:] attempts to acquire the lock associated with the client array on thread 2 so that it can remove clientX

What happens here is that I end up in a deadlock because:

  1. [BossOfEverthing changeSomething] (who has acquired the lock on thread 1) is waiting for [clientX somethingIsAboutToBeChanged] to return from thread2
  2. Thread 2 is stuck waiting to acquire the lock which is currently owned by [BossOfEverthing changeSomething] on thread 1 and is unable to respond to its somethingIsAboutToBeChanged method

Here's what I have done to remedy this:

  1. Rather than clientX calling [BossOfEverthing removeMeFromYourClientList:] from its dealloc method, it calls it from its release method (only when retainCount==1)
  2. [BossOfEverthing removeMeFromYourClientList:] rather than waiting on the lock just ATTEMPTS to acquire it and if it can't it returns NO.
  3. If [BossOfEverthing removeMeFromYourClientList:] returns NO to [clientX release] then [clientX release] calls [self performSelector:@selector(release) withObject:nil afterDelay:0.1], otherwise it just calls [super release]

This way clientX has a chance to respond to any messages that [BossOfEverthing changeSomething] might be sending it and allow [BossOfEverthing changeSomething] to finish up it's business and release the lock, while another call to [clientX release] is queued up in the delay.

The problem that I have with this is that:

  1. Client objects are of a variety of classes and so I have to copy paste my overriden release method to each of them. There's something that irks me about copying and pasting the same code to multiple classes. If objective-c allowed multiple inheritance then I could perhaps create another class 'BossOfEverthingClient' whose only defined method would be an overriden release.
  2. This whole procedure seems a bit convoluted.

Anyway, Thanks so much for reading my long winded post and I'll look forward to any input that anyone has.
Thanks again!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

谜兔 2024-08-19 23:14:50

-release 的重载是一个好兆头,表明您遇到了麻烦......

IMO 真正的问题是您在步骤 2 中获取了长期锁定以便迭代列表。由于可能发生所有其他步骤(其中一些是可重入的),因此很难保持此锁的原子性。

解决这个问题的一个办法是放松对数据的控制。首先,每个客户端都应该有一个唯一的标识符(如果没有更方便的东西,它的内存位置就可以了)。创建一个标识符->客户端的字典。现在,在第二步中,锁定字典并拍摄标识符的快照。然后立即解锁词典。当呼叫者希望删除自己时,锁定字典,删除它们,然后解锁。

现在,如果有人在你反复提问时消失了,那也没关系。你会拿着你的标识符,去查字典,然后发现没有这样的对象。扔掉标识符(它只是一个字符串)并移至下一个。如果有人出现在迭代中间,您不会在那一轮询问他们,但希望这在第一次迭代中是可以接受的(当然,如果不是,也有解决方案)。

编辑:解决所有这些问题的一个更简单的解决方案可能是简单地让 BossOfEverthing -retain 在开始向客户提问之前保留所有客户,然后在完成后-release 他们。这将确保在提问期间它们都不会释放。如果他们都真正回答问题很重要,那么这是一个很好的解决方案。

The overloading of -release is a good sign that you're in trouble....

The real problem IMO is that you take a long-lived lock in step 2 in order to iterate over the list. It's hard to keep this lock atomic because of all the other steps that can happen (some of which re-entrant).

A solution to this problem is to loosen up your hold on the data. First, every client should have a unique identifier (it's memory location is fine if you don't have anything more convenient). Create a dictionary of identifier->client. Now, in step two, lock the dictionary and take a snapshot of the identifiers. Then immediately unlock the dictionary. When callers wish to remove themselves, lock the dictionary, remove them, and and unlock.

Now, if someone vanishes while you're iterating over asking questions, that's ok. You'll take your identifier, go look in the dictionary, and find that there is no such object. Toss the identifier (it's just a string) and move onto the next one. If someone shows up in the middle of an iteration, you won't ask them that round, but hopefully that's acceptable in the first iteration (of course there are solutions if it isn't).

EDIT: A simpler solution to all this may be to simply let BossOfEverthing -retain all of its clients before it begins asking them questions, and then -release them when it's done. This will ensure that none of them dealloc during the question asking. This is a good solution if it's important that they all actually answer the question.

仅此而已 2024-08-19 23:14:50

当您要迭代客户端数组时,我会这样做:

  1. 获取锁
  2. 制作客户端数组的浅表副本
  3. 释放锁
  4. 迭代数组副本

通过复制数组,每个元素都会保留一个保留客户端对象,这将阻止它们在迭代期间被释放。此外,在迭代开始之前释放锁,这将避免死锁。此外,它还允许客户端在迭代期间调用 removeMeFromYourClientList:,这可能有益,也可能无益。

When you're about to iterate over the client array, I'd do it like so:

  1. Acquire the lock
  2. Make a shallow copy of the client array
  3. Release the lock
  4. Iterate over the array copy

By copying the array, a retain is put on every client object which will stop them from being deallocated during iteration. Also, the lock is released before the iteration begins which will avoid the deadlock. Also, it allows clients to call removeMeFromYourClientList: during iteration, which may or may not be beneficial.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文