如何在不阻塞的情况下同步线程?

时间:2015-09-16 15:03:46

标签: c linux multithreading mutex

据我所知,互斥用于同步所有共享相同数据的线程,遵循以下原则:当一个线程使用该数据时,所有其他线程应该在使用该公共资源时被阻止,直到它被解锁为止。 。最近在博客中我看过一个解释这个概念的代码,有些人写道,当一个线程访问资源时阻塞所有线程是一个非常糟糕的想法,它违背了线程的概念,这在某种程度上是真的.. < strong>然后我的问题是如何在不阻塞的情况下同步线程?

以下是该博文的链接

http://www.thegeekstuff.com/2012/05/c-mutex-examples/

5 个答案:

答案 0 :(得分:2)

根据同步的定义,您无法同步线程而不会阻止。但是,良好的同步技术会将阻塞事物的范围限制在绝对最小值。为了说明并指出文章错误的原因,请考虑以下内容:

来自文章:

pthread_t tid[2];
int counter;
pthread_mutex_t lock;

void* doSomeThing(void *arg)
{
    pthread_mutex_lock(&lock);

    unsigned long i = 0;
    counter += 1;
    printf("\n Job %d started\n", counter);

    for(i=0; i<(0xFFFFFFFF);i++);

    printf("\n Job %d finished\n", counter);

    pthread_mutex_unlock(&lock);

    return NULL;
}

应该是什么:

pthread_t tid[2];
int counter;
pthread_mutex_t lock;

void* doSomeThing(void *arg)
{
    unsigned long i = 0;

    pthread_mutex_lock(&lock);
    counter += 1;
    int myJobNumber = counter;
    pthread_mutex_unlock(&lock);

    printf("\n Job %d started\n", myJobNumber);

    for(i=0; i<(0xFFFFFFFF);i++);

    printf("\n Job %d finished\n", myJobNumber);

    return NULL;
}

请注意,在文章中,正在完成的工作(无意义的循环)是在持有锁的同时完成的。这完全是胡说八道,因为这项工作应该同时进行。需要锁定的原因只是为了保护counter变量。因此,线程只需要在更改该变量时保持锁定,如第二个示例所示。

Mutex锁保护代码的关键部分,这些代码区域一次只能触摸1个线程 - 而所有其他线程必须阻止试图同时访问关键部分。但是,如果线程1在临界区中,而线程2不在,则两者并发运行完全正常。

答案 1 :(得分:1)

您正在寻找的术语是lock free data structures

一般的想法是线程之间共享的状态被扭曲成其中一个。

这些实现各不相同,通常是编译器或平台特定的。例如,MSVC具有一组_Interlocked*函数来执行简单的原子操作。

答案 2 :(得分:1)

  

在一个线程访问资源时阻塞所有线程是一个非常糟糕的主意,它违背了线程的概念,这在某种程度上是正确的

这是一个谬误。锁仅阻止竞争线程,允许所有非竞争线程同时运行。运行在任何特定时间运行效率最高的工作而不是强制执行任何特定的排序都不符合线程的概念。

现在,如果你的许多线程如此严重地竞争,阻止竞争线程会损害性能,那么有两种可能性:

  1. 很可能你的设计非常差,你应该修理它。不要把锁定归咎于高争用设计。

  2. 在极少数情况下,其他同步机制更合适(例如无锁集合)。但这需要对特定用例进行大量专业知识和分析才能找到最佳解决方案。

  3. 通常,如果您的用例非常适合原子,请使用它们。否则,互斥量(可能与条件变量结合)应该是您首先想到的。这将涵盖典型的多线程C程序员将面临的99%的案例。

答案 3 :(得分:0)

您可以使用pthread_mutex_trylock()尝试锁定。如果失败,那么您知道 已被阻止。你不能做你想做的事,但你的线程没有被阻止,所以它可以尝试做别的事情。我认为该博客上的大多数评论都是关于避免线程之间的争用,即最大化多线程性能是关于避免线程同时在同一资源上工作。如果你通过设计而不是设计来避免这种情况,你就不需要锁定,因为你从来没有争用过。

答案 4 :(得分:0)

可以使用许多技巧来避免并发瓶颈。

  1. 不可变数据结构。这里的想法是并发读取是可以的,但写入不是。要实现这样的事情,您基本上需要将业务单位视为这些不可变数据结构的工厂,这些数据结构由其他业务部门使用。
  2. 异步-回调。这是事件驱动开发的本质。如果您有并发任务,请在资源可用时使用观察者模式执行某些逻辑。基本上我们执行一些代码直到需要共享资源,然后在资源可用时添加一个监听器。这通常导致代码不太可读,并且堆栈上存在压力,但您永远不会阻塞等待资源的线程。如果您已准备好让CPU保持运行的任务,则此模式将为您完成。
  3. 即使使用这些工具,您也永远不会完全消除某些同步的需要(计数器会浮现在脑海中)。