假设我有多个线程,并且所有线程几乎在同一时间调用相同的函数。
是否存在任何时候只允许一个函数实例的调用约定?我的意思是第二个线程调用的函数只有在第一个线程调用的函数返回后才会启动。
或者这些调用约定是编译器特定的?我没有很多使用它们的经验。
假设我有多个线程,并且所有线程几乎在同一时间调用相同的函数。
是否存在任何时候只允许一个函数实例的调用约定?我的意思是第二个线程调用的函数只有在第一个线程调用的函数返回后才会启动。
或者这些调用约定是编译器特定的?我没有很多使用它们的经验。
调用约定定义了如何使用堆栈和寄存器来实现函数调用。因为每个线程都有自己的堆栈和寄存器,所以同步线程和调用约定是分开的。
为了防止多个线程同时执行相同的代码,您需要一个mutex。在您的函数示例中,您通常会将互斥锁和解锁放在函数代码中,围绕您不希望线程同时执行的语句。
一般而言:纯代码,包括函数调用,不知道线程,操作系统知道。通过使用互斥锁,您可以进入管理线程运行的系统。更多细节只需谷歌搜索即可。
请注意,新的 C 标准修订版 C11确实包含多线程支持。但这并没有改变一般概念;它只是意味着您可以使用 C 库函数而不是操作系统特定的函数。
(如果你不关心线程的 mumbo-jumbo,请跳到底部)
如前所述,这不是“调用约定”,而是计算的普遍问题:并发。两个或多个线程一次可以进入共享区域并产生不同结果的特殊情况称为竞争条件(并且还延伸到/从电子设备和其他区域延伸)。
线程的难点在于计算是一个确定性的事情,但是当涉及到线程时,它会增加一定程度的不确定性,这会因平台/操作系统而异。
单线程事务将保证它可以始终以相同的顺序执行所有任务,但是当您有多个线程并且顺序取决于它们完成任务的速度时,共享其他想要使用 CPU 的应用程序,然后底层硬件会影响结果。
没有太多“确定线程的方法”,因为有处理个别情况的技术、工具和库。
最广为人知的技术是使用信号量(或锁),而最广为人知的信号量是互斥量信号量,它一次只允许一个线程访问共享空间,方法是通过一种“标志”来提升一次一个线程已进入。
if (locked == NO)
{
locked = YES;
// Do ya' thing
locked = NO;
}
上面的代码虽然看起来可以工作,但它不能保证不会出现两个线程都通过if ()
然后设置变量的情况(线程可以轻松做到)。所以这种操作有硬件支持,保证只有一个线程可以执行它:该testAndSet
操作检查然后,如果可用,设置变量。(这是指令集中的 x86 指令)
与锁和信号量一样,还有读写锁,它允许多个读取器和一个写入器,特别适用于低波动性的事物。还有许多其他变体,其中一些限制了 X 数量的线程等等。
但总的来说,锁是蹩脚的,因为它们基本上是在强制多线程序列化,其中线程实际上需要卡住试图获得锁(或者只是测试它并离开)。有点违背了拥有多个线程的目的,不是吗?
线程方面的最佳解决方案是最小化线程需要使用的共享空间量,可能完全消除它。也许rwlocks
在波动性较低时使用,尝试使用“尝试离开”类型的线程,检查锁是否已启动,如果未启动则离开,等等。
正如我的操作系统老师曾经说过的(以禅宗的方式):“最好的锁定是你可以避免的”。
现在,线程很难,没有办法解决,这就是为什么会有模式来处理这类问题,而线程池模式是一种流行的模式,至少在引入 Grand Central Dispatch (GCD) 之后在 iOS 中是这样。
与其让一堆线程乱跑并在各处排队,不如让我们有一组线程,在“池”中等待任务,并有一系列要做的事情,理想情况下,任务不应该重叠其他。
现在,线程模式并没有解决之前讨论过的问题,但它改变了范式,使其在心理上更容易处理。不必考虑“需要执行某某的线程”,您只需将焦点切换到“需要执行的任务”,哪个线程正在执行它的问题变得无关紧要。
同样,池不能解决你所有的问题,但它会让它们更容易理解。并且更容易理解可能会带来更好的解决方案。
上面提到的所有理论上的东西都已经在 POSIX 级别实现了(semaphore.h、pthreads.h 等。pthreads 具有非常好的读/写锁定功能),尝试阅读它们。
(编辑:我认为这个帖子是关于 Obj-C 的,而不是普通的 C,删掉了所有 Foundation 和 GCD 的东西)