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

How to synchronize threads without blocking?

现在据我所知,互斥锁用于同步共享相同数据的所有线程,遵循以下原则:当一个线程正在使用该数据时,所有其他线程在使用该公共资源时应被阻塞,直到它被解锁...最近在一篇博文中,我看到一段代码解释了这个概念,有些人写道,在一个线程访问资源时阻塞所有线程是一个非常糟糕的主意,它违背了线程的概念,而线程的概念在某种程度上是正确的。 . 那么我的问题是如何在不阻塞的情况下同步线程?

这是该博文的link

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

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

来自文章:

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;
}

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

互斥锁保护代码的临界区,这是一次只有 1 个线程应该触及的代码区域 - 以及所有其他线程 如果试图同时访问临界区,则必须 阻塞。但是,如果线程 1 在临界区,而线程 2 不在,那么两者并发 运行 是完全没问题的。

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

您要查找的字词是lock free data structures

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

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

有许多技巧可用于避免并发瓶颈。

  1. 不可变数据结构。这里的想法是并发读取是可以的,但写入不是。要实现这样的功能,您基本上需要将业务单元视为这些由其他业务单元使用的不可变数据结构的工厂。
  2. 异步回调。这就是事件驱动开发的本质。如果您有并发任务,请使用观察者模式在资源可用时执行一些逻辑。基本上我们执行一些代码直到需要共享资源,然后在资源可用时添加一个侦听器。这通常会导致代码可读性降低和堆栈压力增加,但您永远不会阻塞等待资源的线程。如果您已准备好让 CPU 运行 保持热度的任务,此模式将为您完成。

即使使用这些工具,您也永远无法完全消除对某些同步的需求(想到计数器)。

blocking all the threads while one thread is accessing the resources is a very bad idea and it goes against the concept of threading which is true somehow

这是谬论。锁只阻塞竞争线程,允许所有非竞争线程并发运行。 运行 在任何特定时间对 运行 最有效的工作而不是强制任何特定顺序根本不违背线程的概念。

现在,如果您的线程中有如此多的竞争非常严重,以至于阻塞竞争线程会损害性能,则有两种可能性:

  1. 很可能你的设计很糟糕,你应该修复它。不要将高竞争设计归咎于锁。

  2. 您处于其他同步机制更合适(例如无锁集合)的罕见情况。但这需要大量专业知识和对特定用例的分析才能找到最佳解决方案。

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