2011-01-11 145 views
4

我有一个在多个线程中使用的类实例。我正从一个线程更新多个成员变量,并从一个线程读取相同的成员变量。什么是维护线程安全的正确方法?如何安全地从一个线程读取变量并从另一个线程修改它?

eg: 
    phthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    //unlock here? 

我应该在这里解锁互斥锁吗? (如果另一个线程现在访问obj1成员变量1和2,则访问的数据可能不正确,因为memberV2尚未更新。但是,如果我没有释放锁定,则另一个线程可能会因为耗时操作而被阻塞。下面

//perform some time consuming operation which must be done before the assignment to memberV2 and after the assignment to memberV1 
    obj1.memberV2 = update field 2 from some calculation 
pthread_mutex_unlock(&mutex1) //should I only unlock here? 

感谢

回答

0

可能是做的最好的事情是:

temp = //perform some time consuming operation which must be done before the assignment to memberV2 

pthread_mutex_lock(&mutex1) 
obj1.memberV1 = 1; 
obj1.memberV2 = temp; //result from previous calculation 
pthread_mutex_unlock(&mutex1) 
+0

我认为这可能会导致更新丢失,如果线程1执行您的示例代码和线程2更新`memberV1`在线程1之间分配`temp`并进入互斥体。 – finnw 2011-01-11 03:11:20

1

你锁定是正确的,您应该解除锁定早期只是为了让另一个线程。继续(因为这将允许其他线程看到不一致的状态的对象。)

0

我会做的是分开的,更新的计算:

temp = some calculation 
pthread_mutex_lock(&mutex1); 
obj.memberV1 = 1; 
obj.memberV2 = temp; 
pthread_mutex_unlock(&mutex1); 
1

也许会更好做是这样的:

//perform time consuming calculation 
pthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    obj1.memberV2 = result; 
pthread_mutex_unlock(&mutex1) 

当然,这是假设在计算中使用的值将不能任何其他线程修改

1

很难说出你在做什么导致问题。互斥模式非常简单。您锁定互斥锁,访问共享数据,解锁互斥锁。这可以保护数据,因为互斥锁一次只能让一个线程获得锁定。任何未能获得锁定的线程都必须等待,直到互斥锁被解锁。解锁让服务员醒来。他们然后将争取获得锁定。失败者回去睡觉。从锁定释放的时间起,唤醒所需的时间可能是多个ms或更多。确保你总是最终解锁互斥锁。

请确保您不要锁定很长一段时间的锁。大多数时候,很长一段时间就像微秒。我更喜欢围绕“几行代码”。这就是为什么人们建议你在锁定之外进行长时间运算。不锁定很长时间的原因是您增加了其他线程将锁定并且不得不旋转或休眠的次数,这会降低性能。在拥有锁的同时,您还会增加您的线程可能被抢占的可能性,这意味着锁在该线程休眠时启用。这更糟糕的表现。

失败的线程不必睡觉。纺纱意味着遇到锁定的互斥体的线程不会休眠,但会在放弃和休眠之前循环重复测试锁定预定义周期。如果您有多个核心或多个并发线程的核心,这是一个好主意。多个活动线程意味着两个线程可以同时执行代码。如果锁是在少量代码周围,那么获得锁的线程将很快完成。另一个线程在获得锁定之前只需要等待几秒钟。请记住,休眠线程是一个上下文切换和一些将线程附加到互斥量服务器上的代码,所有代码都有成本。另外,一旦你的线程休眠,你必须等待一段时间,然后调度程序才会唤醒它。这可能是多个毫秒。查找螺旋锁。

如果你只有一个核心,那么如果一个线程遇到一个锁定,这意味着另一个睡眠线程拥有该锁定,无论你旋转多长时间,它都不会解锁。所以你会使用一个锁,立即睡一个服务员,希望拥有锁的线程能够唤醒并完成。

你应该假设一个线程可以在任何机器代码指令中被抢占。你也应该假设每行c代码可能是很多机器代码指令。经典的例子是i ++。这是c中的一个陈述,但是是机器代码域中的读取,增量和存储。

如果您真的关心性能,请尝试先使用原子操作。作为最后的手段来看待互斥体。大多数并发问题很容易通过原子操作解决(谷歌gcc原子操作开始学习),很少有问题需要互斥体。互斥体的速度较慢。

保护您的共享数据,无论它在哪里书面,无论它在哪里阅读。其他...准备失败。只有一个线程处于活动状态时,您不必保护共享数据。

通常可以使用1个线程以及N个线程运行您的应用程序。这样你可以更容易地调试竞争条件。

最小化使用锁保护的共享数据。尝试将数据组织到结构中,以便单个线程可以获得对整个结构的独占访问权限(可能通过设置单个锁定标志或版本号或两者),而不必担心后面的任何事情。然后,大部分代码不会与锁和竞争条件混杂在一起。

最终写入共享变量的函数应该使用临时变量直到最后一刻,然后复制结果。编译器不仅会生成更好的代码,而且会访问共享变量,尤其是更改它们会导致L2和主内存之间的缓存行更新以及各种其他性能问题。再一次,如果你不关心性能,不考虑这一点。不过,如果你想知道更多,我建议你谷歌文档“程序员应该知道关于内存的一切”。

如果您正在从共享数据中读取单个变量,则只要该变量是整数类型而不是位域成员(可以使用多条指令读取/写入位域成员),则可能不需要锁定该变量, 。阅读原子操作。当你需要处理多个值时,你需要一个锁来确保你没有读取一个值的版本A,被抢占,然后读取下一个值的版本B.写作也是如此。

你会发现数据的副本,甚至整个结构的副本都派上用场。您可以在构建数据的新副本,然后通过使用一个原子操作更改指针来交换它。您可以制作一份数据副本,然后对其进行计算,而不用担心它是否发生变化。

因此,也许你想要做的是:

lock the mutex 
    Make a copy of the input data to the long running calculation. 
unlock the mutex 

L1: Do the calculation 

Lock the mutex 
    if the input data has changed and this matters 
    read the input data, unlock the mutex and go to L1 
    updata data 
unlock mutex 

也许,在上面的例子中,你还是把结果存储在输入变化,但回去重新计算。这取决于其他线程是否可以使用稍微过时的答案。当其他线程看到一个线程已经在进行计算时,只需更改输入数据并将其保留到繁忙线程中即可注意到并重新计算(如果你这样做,将会遇到需要处理的竞态条件,并且容易)。这样,其他线程可以完成其他工作,而不仅仅是睡眠。

欢呼声。

相关问题