使用信号量进行线程同步

时间:2018-02-14 18:18:55

标签: multithreading semaphore thread-synchronization context-switch binary-semaphore

这是一个面试问题,任何帮助都将不胜感激

如何同步两个线程,其中一个增加一个值而另一个显示它(P.S。显示该值的线程必须仅在其新值时显示一个值)

Ex:int x = 5; T1:将其增加到6 T2:必须显示6(仅一次),并且当它变为7

时必须再次显示

我回答说我会使用类似

的信号量
int c=0; // variable that I used to synchronize


// In T1
if( c = 0 )
{
   c++;
   x++; // value that is incremented
}

// in T2
if( c == 1 )
{
   cout<<x;
   c--;
}

然后他询问如果在将c设置为1之后但在增加x之前从线程T1切换到T2时会怎么做(在这种情况下,它会在递增x之前进入P2)

我无法回答这一部分。任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:3)

这是条件变量的经典用例,在线程2运行以处理它之前,该值可以在线程1中轻松更新多次:

// In some scope common to both threads
int c_ = 0; // variable
std::mutex mutex_();
std::condition_variable cond_();

// Thread 1
{ 
    std::lock_guard<std::mutex> lock(mutex_);
    ++c_;
}
cond_.notify_one();

// Thread 2
{ 
    std::lock_guard<std::mutex> lock( mutex_ );
    int cLocal = c_;
    while ( !done ) { 
        cond_.wait( lock, [] { return c_ != cLocal; } );
        while ( cLocal++ < c_ ) 
            ... // Display new *local* value
    }
}

答案 1 :(得分:0)

很好的运动。

您尚未在问题中指定c++标记,但问题本身包含cout<<x,因此您可能正在面试C ++职位。尽管如此,我将用Java回答,因为这是一个面试问题,只要我避免使用任何过于特定于Java的东西,语言都不应该太重要。

正如你的采访者指出的那样,同步必须在两个方向发生:

  • 打印线程必须等待递增线程完成其作业
  • 递增线程必须等待打印线程完成其作业

所以我们需要一些东西让我们知道打印机已完成(因此增量器可以运行),另一个让我们知道增量器已完成。我使用了两个信号量:

Working version on Ideone

import java.util.concurrent.Semaphore;

class IncrementDemo {
    static int x = 0;

    public static void main(String[] args) {
        Semaphore incrementLock = new Semaphore(0);
        Semaphore printLock = new Semaphore(0);

        Thread incrementer = new Thread(() -> {
            for(;;) {
                incrementLock.acquire(); //Wait to be allowed to increment
                x++;
                printLock.release(); //Allow the printer to print
            }
        });

        Thread printer = new Thread(() -> {
            for (;;) {
                incrementLock.release(); //Let the incrementer to its job
                printLock.acquire(); //Wait to be allowed to print
                System.out.println(x);
            }
        });

        incrementer.setDaemon(false); //Keep the program alive after main() exits
        printer.setDaemon(false);

        incrementer.start(); //Start both threads
        printer.start();
    }

}

(为了便于阅读,我删除了acquire周围的try / catch块。)

输出:

1
2
3
4
5
6
7
...

答案 2 :(得分:0)

问题:

一般来说,并行代码存在两个主要问题。

<强> 1。原子

代码中最小的粒度实际上不是像i++这样的单个操作,而是底层的汇编指令。因此,可能无法从多个线程调用涉及写入的每个操作。 (这在您的目标架构上有很大不同,但x86与arm64相比具有非常严格的限制)

但幸运的是,c ++提供了std::atomic操作,它为您提供了一种很好的平台独立方式来修改多个线程中的变量。

<强> 2。稠度

只要保留本地线程的一致性,就允许编译器和处理器重新排序任何指令。那是什么意思呢?

看看你的第一个帖子

if( c = 0 )
{
   c++;
   x++; // value that is incremented
}

您有3个操作c == 0c++x++。两个增量都不相互依赖,因此允许编译器交换它们。在运行时,核心可能会对它们进行重新排序,使您处于非常模糊的状态。在顺序世界中,这非常精细并且提高了整体性能(除非它导致像熔化一样的安全漏洞)。遗憾的是,编译器或cpu都不能识别并行代码,因此任何优化都可能会破坏并行程序。

但是再一次,c ++为这个问题std::memory_order提供了一个内置的解决方案,它强制执行特定的一致性模型。

解决方案:

简单互斥: 互斥体是一种简单但功能强大的工具。它通过提供阻止并行执行的所谓关键部分来解决原子性和一致性问题。这意味着,在给定的示例中,两个线程中的if子句是顺序的,并且永远不会并行执行。 实施工作,但有一个缺陷。如果其中一个线程非常慢,另一个线程将通过连续检查newValue标志来浪费大量的cpu-time。

#include <mutex>

std::mutex mutex;
int value = true;
bool newValue = false;

void producer_thread() {
   while(true) {
       std::lock_guard<std::mutex> lg(mutex);
        if (newValue == false) {
            value++;
            newValue = true;
        }
   }
}

void consumer_thread() {
   while(true) {
       std::lock_guard<std::mutex> lg(mutex);
        if (newValue == true) {
            std::cout << value;
            newValue = false;
        }
   }
}

条件变量

条件变量基本上只是一个“等待通知” - 构造。您可以通过调用wait来阻止当前执行,直到另一个线程调用notify。这种实现将成为首选方案。

#include <mutex>
#include <condition_variable>

std::mutex mutex;
std::condition_variable cond;
int value = true;
bool newValue = false;

void producer() {
   while(true) {
       std::unique_lock<std::mutex> ul(mutex);

        while (newValue == true) {
            cond.wait(ul);
        }

        value++;
        newValue = true;
        cond.notify_all();
   }
}

void consumer() {
   while(true) {
       std::unique_lock<std::mutex> ul(mutex);

        while (newValue == false) {
            cond.wait(ul);
        }

        std::cout << value;
        newValue = false;
        cond.notify_all();
   }
}