代码之家  ›  专栏  ›  技术社区  ›  arkham knight

使用信号量的线程同步

  •  3
  • arkham knight  · 技术社区  · 6 年前

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

    如何同步两个线程,其中一个线程递增一个值,另一个线程显示该值(请注意,显示该值的线程只能在其为新值时显示一个值)

    前任: 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--;
    }
    

    然后他问如果线程中有上下文切换,你会怎么做 T1级 T2级 将c设置为1之后,但在递增x之前(在这种情况下,它将在递增x之前输入P2)

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

    3 回复  |  直到 4 年前
        1
  •  3
  •   Andy    6 年前

    这是 条件变量 在线程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
        }
    }
    
        2
  •  1
  •   Malt    6 年前

    很好的锻炼。

    您尚未指定 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 可读性)。

    输出:

    1
    2
    3
    4
    5
    6
    7
    ...
    
        3
  •  0
  •   Domso    6 年前

    问题:

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

    1、原子性

    事实上,代码中最小的粒度并不是像 i++ ,但基本的汇编指令。因此,可能不会从多个线程调用涉及写入的每个操作。(这在您的目标体系结构上有很大不同,但x86与arm64相比限制性很大)

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

    2、一致性

    编译器和处理器都可以对任何指令重新排序,只要 地方的 线程被保留。那么这意味着什么呢?

    看看你的第一条线索

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

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

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

    解决:

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

    #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 . 此实现将是go-to场景。

    #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();
       }
    }