LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口

简介: 本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。

1114. 按序打印

我们提供了一个类:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

  • 一个将会调用 first() 方法
  • 一个将会调用 second() 方法
  • 还有一个将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

示例 1:

输入: [1,2,3]
输出: "firstsecondthird"
解释: 
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。
正确的输出是 "firstsecondthird"。

示例 2:

输入: [1,3,2]
输出: "firstsecondthird"
解释: 
输入 [1,3,2] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 third() 方法,线程 C 将会调用 second() 方法。
正确的输出是 "firstsecondthird"。

提示:

  • 尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。
  • 你看到的输入格式主要是为了确保测试的全面性。

解法

作者:zintrulcre

链接:https://leetcode-cn.com/problems/print-in-order/solution/c-hu-chi-suo-tiao-jian-bian-liang-xin-hao-liang-yi/

来源:力扣(LeetCode)

class Foo {
   
    std::atomic<bool> a{
    false };
    std::atomic<bool> b{
    false };
public:
    void first(function<void()> printFirst) {
   
        printFirst();
        a = true;
    }

    void second(function<void()> printSecond) {
   
        while (!a)
            this_thread::sleep_for(chrono::milliseconds(1));
        printSecond();
        b = true;
    }

    void third(function<void()> printThird) {
   
        while (!b)
            this_thread::sleep_for(chrono::milliseconds(1));
        printThird();
    }
};
class Foo {
   
    function<void()> task = []() {
   };
    packaged_task<void()> pt_1{
    task }, pt_2{
    task };

public:
    void first(function<void()> printFirst) {
   
        printFirst();
        pt_1();
    }

    void second(function<void()> printSecond) {
   
        pt_1.get_future().wait();
        printSecond();
        pt_2();
    }

    void third(function<void()> printThird) {
   
        pt_2.get_future().wait();
        printThird();
    }
};
class Foo {
   
    promise<void> pro1, pro2;

public:
    void first(function<void()> printFirst) {
   
        printFirst();
        pro1.set_value();
    }

    void second(function<void()> printSecond) {
   
        pro1.get_future().wait();
        printSecond();
        pro2.set_value();
    }

    void third(function<void()> printThird) {
   
        pro2.get_future().wait();
        printThird();
    }
};
#include <semaphore.h>

class Foo {
   
private:
    sem_t sem_1, sem_2;

public:
    Foo() {
   
        sem_init(&sem_1, 0, 0), sem_init(&sem_2, 0, 0);
    }

    void first(function<void()> printFirst) {
   
        printFirst();
        sem_post(&sem_1);
    }

    void second(function<void()> printSecond) {
   
        sem_wait(&sem_1);
        printSecond();
        sem_post(&sem_2);
    }

    void third(function<void()> printThird) {
   
        sem_wait(&sem_2);
        printThird();
    }
};
class Foo {
   
    condition_variable cv;
    mutex mtx;
    int k = 0;
public:
    void first(function<void()> printFirst) {
   
        printFirst();
        k = 1;
        cv.notify_all();    // 通知其他所有在等待唤醒队列中的线程
    }

    void second(function<void()> printSecond) {
   
        unique_lock<mutex> lock(mtx);   // lock mtx
        cv.wait(lock, [this](){
    return k == 1; });  // unlock mtx,并阻塞等待唤醒通知,需要满足 k == 1 才能继续运行
        printSecond();
        k = 2;
        cv.notify_one();    // 随机通知一个(unspecified)在等待唤醒队列中的线程
    }

    void third(function<void()> printThird) {
   
        unique_lock<mutex> lock(mtx);   // lock mtx
        cv.wait(lock, [this](){
    return k == 2; });  // unlock mtx,并阻塞等待唤醒通知,需要满足 k == 2 才能继续运行
        printThird();
    }
};
class Foo {
   
    mutex mtx_1, mtx_2;
    unique_lock<mutex> lock_1, lock_2;
public:
    Foo() : lock_1(mtx_1, try_to_lock), lock_2(mtx_2, try_to_lock) {
   
    }

    void first(function<void()> printFirst) {
   
        printFirst();
        lock_1.unlock();
    }

    void second(function<void()> printSecond) {
   
        lock_guard<mutex> guard(mtx_1);
        printSecond();
        lock_2.unlock();
    }

    void third(function<void()> printThird) {
   
        lock_guard<mutex> guard(mtx_2);
        printThird();
    }
};
class Foo {
   
    mutex mtx1, mtx2;
public:
    Foo() {
   
        mtx1.lock(), mtx2.lock();
    }

    void first(function<void()> printFirst) {
   
        printFirst();
        mtx1.unlock();
    }

    void second(function<void()> printSecond) {
   
        mtx1.lock();
        printSecond();
        mtx1.unlock();
        mtx2.unlock();
    }

    void third(function<void()> printThird) {
   
        mtx2.lock();
        printThird();
        mtx2.unlock();
    }
};

1115. 交替打印FooBar

我们提供一个类:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。

请设计修改程序,以确保 "foobar" 被输出 n 次。

示例 1:

输入: n = 1
输出: "foobar"
解释: 这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。

示例 2:

输入: n = 2
输出: "foobarfoobar"
解释: "foobar" 将被输出两次。

解法

作者:mu-lang-ren

链接:https://leetcode-cn.com/problems/print-foobar-alternately/solution/c-jiao-ti-da-yin-foobar-ti-jie-zheng-li-by-mu-lang/

来源:力扣(LeetCode)

class FooBar {
   
private:
    int n;

public:
    FooBar(int n) {
   
        this->n = n;
    }

    void foo(function<void()> printFoo) {
   

        for (int i = 0; i < n; i++) {
   
            std::unique_lock<std::mutex> lk(Mu);//获取Mu锁
            v.wait(lk,[this](){
   return count == 1;});//看v是否满足条件,(锁和变量)
            // printFoo() outputs "foo". Do not change or remove this line.
            printFoo();
            count++;//变量+1,=2,等于2时,foo会阻塞,bar的condition_variable 满足条件
            v.notify_one();//通知并唤醒阻塞在v2里面的线程
        }
    }

    void bar(function<void()> printBar) {
   
        //注释同上
        for (int i = 0; i < n; i++) {
   
            std::unique_lock<std::mutex> lk(Mu);
            v.wait(lk,[this](){
   return count == 2;});
            // printBar() outputs "bar". Do not change or remove this line.
            printBar();
            count--;
            v.notify_one();
        }
    }
private:
    int count = 1;//条件变量
    std::condition_variable v;//条件变量对象
    std::mutex Mu;//定义一个锁
};
class FooBar {
   
private:
    int n;
    mutex m1,m2;

public:
    FooBar(int n) {
   
        this->n = n;
        m2.lock();
    }

    void foo(function<void()> printFoo) {
   
        for (int i = 0; i < n; i++) {
   
            m1.lock();
            // printFoo() outputs "foo". Do not change or remove this line.
            printFoo();
            m2.unlock();
        }
    }

    void bar(function<void()> printBar) {
   

        for (int i = 0; i < n; i++) {
   
            m2.lock();
            // printBar() outputs "bar". Do not change or remove this line.
            printBar();
            m1.unlock();
        }
    }
};
class FooBar {
   
private:
    int n;
    atomic<bool> fooed = false;

public:
    FooBar(int n) {
   
        this->n = n;
    }

    void foo(function<void()> printFoo) {
   
        for (int i = 0; i < n; i++) {
   
            while(fooed.load())this_thread::yield();
            // printFoo() outputs "foo". Do not change or remove this line.
            printFoo();
            fooed.store(true);
        }
    }

    void bar(function<void()> printBar) {
   

        for (int i = 0; i < n; i++) {
   
            while(!fooed.load())this_thread::yield();
            // printBar() outputs "bar". Do not change or remove this line.
            printBar();
            fooed.store(false);
        }
    }
};

1116. 打印零与奇偶数

假设有这么一个类:

class ZeroEvenOdd {
  public ZeroEvenOdd(int n) { ... }      // 构造函数
  public void zero(printNumber) { ... }  // 仅打印出 0
  public void even(printNumber) { ... }  // 仅打印出 偶数
  public void odd(printNumber) { ... }   // 仅打印出 奇数
}

相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:

  1. 线程 A 将调用 zero(),它只输出 0 。
  2. 线程 B 将调用 even(),它只输出偶数。
  3. 线程 C 将调用 odd(),它只输出奇数。

每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506... ,其中序列的长度必须为 2n

示例 1:

输入:n = 2
输出:"0102"
说明:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 "0102"。

示例 2:

输入:n = 5
输出:"0102030405"

解法

看到这个题我第一反应是三个线程交叉进行

--根据图例那么就需要两个锁,分别是两个bool值 SingleMic为true打印 奇数,DoubleMic为true打印 偶数,都为false 打印0
--打完奇数之后两个锁都为false准备打印0,偶数同理,唯一的冲突点在于打印0之后是打印奇数还是偶数,这里我选择声明一个int(此处curnum)来记录当前该打的数
--通过打印0函数之后让这个int ++之后来判断下一个该打的数是奇数还是偶数,这样就可以打开对应的锁

即以下代码(如有不妥望指正)

作者:tu-ma-ma
链接:https://leetcode-cn.com/problems/print-zero-even-odd/solution/c-yuan-zi-cao-zuo-jiao-cha-da-yin-by-tu-vruh9/
来源:力扣(LeetCode)

class ZeroEvenOdd {
   
private:
    int n;
    int curnum = 0;//当前应该打印数字
public:
    ZeroEvenOdd(int n) {
   
        this->n = n;
    }
    atomic<bool> SingleMic = false;
    atomic<bool> DoubleMic = false;

    void zero(function<void(int)> printNumber) {
   
        for(int i = 0; i < n; i++)
        {
   
            while(SingleMic || DoubleMic)//当奇和偶其中一个准备打印
                this_thread::yield();

            printNumber(0);

            curnum++;//当前应该打印的数字++,根据当前应该打印奇偶来判断开哪个锁
            if(curnum % 2 == 0)
            {
   
                SingleMic = false;
                DoubleMic = true;
            }
            else
            {
   
                SingleMic = true;
                DoubleMic = false;
            }
        }
    }

    void even(function<void(int)> printNumber) {
   //偶数
        for(int i = 1; i <= n; i++)
        {
   
            if(i % 2 != 0) continue;
            while(!DoubleMic)//当偶不准备打印
                this_thread::yield();

            printNumber(i);
            SingleMic = false;
            DoubleMic = false;
        }
    }

    void odd(function<void(int)> printNumber) {
   //奇数
        for(int i = 1; i <= n; i++)
        {
   
            if(i % 2 == 0) continue;
            while(!SingleMic)//当奇不准备打印
                this_thread::yield();

            printNumber(i);
            SingleMic = false;
            DoubleMic = false;
        }
    }
};

作者:c-bee-9F5W7dRrwd

链接:https://leetcode-cn.com/problems/print-zero-even-odd/solution/cchun-hu-chi-suo-chun-xin-hao-liang-liang-chong-fa/

来源:力扣(LeetCode)

class ZeroEvenOdd {
   
private:
    int n;
    pthread_mutex_t mutex0;
    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;
public:
    ZeroEvenOdd(int n) {
   
        this->n = n;

        pthread_mutex_init(&mutex0, NULL);
        pthread_mutex_init(&mutex1, NULL);
        pthread_mutex_init(&mutex2, NULL);

        //lock
        pthread_mutex_lock(&mutex1);
        pthread_mutex_lock(&mutex2);        
    }

    // printNumber(x) outputs "x", where x is an integer.
    void zero(function<void(int)> printNumber) {
   
        for(int i = 1; i <= n; i++) {
   
            pthread_mutex_lock(&mutex0);
            printNumber(0);
            if(i & 1) pthread_mutex_unlock(&mutex1);
            else pthread_mutex_unlock(&mutex2);
        }
    }

    void even(function<void(int)> printNumber) {
   
        for(int i = 2; i <= n; i+=2) {
   
            pthread_mutex_lock(&mutex2);
            printNumber(i);
            pthread_mutex_unlock(&mutex0); 
        }
    }

    void odd(function<void(int)> printNumber) {
   
        for(int i = 1; i <= n; i+=2) {
   
            pthread_mutex_lock(&mutex1);
            printNumber(i);
            pthread_mutex_unlock(&mutex0); 
        }  
    }
};
#include <semaphore.h>
class ZeroEvenOdd {
   
private:
    int n;
    sem_t zero_sem;
    sem_t odd_sem;
    sem_t even_sem;
public:
    ZeroEvenOdd(int n) {
   
        this->n = n;

        sem_init(&zero_sem, 0, 1); //初始化一个0
        sem_init(&odd_sem, 0, 0);
        sem_init(&even_sem, 0, 0);     
    }

    // printNumber(x) outputs "x", where x is an integer.
    void zero(function<void(int)> printNumber) {
   
        for(int i = 1; i <= n; i++) {
   
            sem_wait(&zero_sem);
            printNumber(0);
            if(i & 1) sem_post(&odd_sem);
            else sem_post(&even_sem);
        }
    }

    void even(function<void(int)> printNumber) {
   
        for(int i = 2; i <= n; i+=2) {
   
            sem_wait(&even_sem);
            printNumber(i);
            sem_post(&zero_sem); 
        }
    }

    void odd(function<void(int)> printNumber) {
   
        for(int i = 1; i <= n; i+=2) {
   
            sem_wait(&odd_sem);
            printNumber(i);
            sem_post(&zero_sem); 
        }  
    }
};

1117. H2O 生成

现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。

存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。

氢和氧线程会被分别给予 releaseHydrogenreleaseOxygen 方法来允许它们突破屏障。

这些线程应该三三成组突破屏障并能立即组合产生一个水分子。

你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。

换句话说:

  • 如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
  • 如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。

书写满足这些限制条件的氢、氧线程同步代码。

示例 1:

输入: "HOH"
输出: "HHO"
解释: "HOH" 和 "OHH" 依然都是有效解。

示例 2:

输入: "OOHHHH"
输出: "HHOHHO"
解释: "HOHHHO", "OHHHHO", "HHOHOH", "HOHHOH", "OHHHOH", "HHOOHH", "HOHOHH" 和 "OHHOHH" 依然都是有效解。

提示:

  • 输入字符串的总长将会是 3n, 1 ≤ n ≤ 50;
  • 输入字符串中的 “H” 总数将会是 2n 。
  • 输入字符串中的 “O” 总数将会是 n 。

解法

作者:ffreturn

链接:https://leetcode-cn.com/problems/building-h2o/solution/cjian-dan-yi-dong-de-condition_variables-gwfp/

来源:力扣(LeetCode)

class H2O {
   
public:
    // 氧气的计数
    int cntO;
    // 氢气的计数
    int cntH;
    mutex m;
    condition_variable cv;

    H2O() {
   
        cntO = 0;
        cntH = 0;
    }

    void hydrogen(function<void()> releaseHydrogen) {
   
        unique_lock<mutex> l(m);
        cv.wait(l, [this]()
        {
   
            // 氢气最大是2
            return this->cntH < 2;
        });
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        releaseHydrogen();
        ++cntH;
        // 已经构成 H2O ,重置计数器
        if (cntH + cntO == 3)
        {
   
            cntH = 0;
            cntO = 0;
        }
        cv.notify_one();
    }

    void oxygen(function<void()> releaseOxygen) {
   
        unique_lock<mutex> l(m);
        cv.wait(l, [this]()
        {
   
            // 氧气最大是1
            return this->cntO < 1;
        });
        // releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen();
        ++cntO;
        // 已经构成 H2O ,重置计数器
        if (cntH + cntO == 3)
        {
   
            cntH = 0;
            cntO = 0;
        }
        cv.notify_one();
    }
};

注意题目的要求,重点用加粗显示:

如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。  
如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。

所以当一个氢/氧线程到达时,如果与已经到达的其余元素无法组成 H2O,那么它必须被卡在 releaseHydrogen() / releaseOxygen() 之前。看到挺多代码是用计数的方法,数到两个 H 和一个 O 就把信号量重置,这样是不对的。

作者:zerotrac2
链接:https://leetcode-cn.com/problems/building-h2o/solution/c-shou-xie-xin-hao-liang-man-zu-ti-mu-yao-qiu-de-x/
来源:力扣(LeetCode)

class Semaphore {
   
private:
    int n_;
    mutex mu_;
    condition_variable cv_;

public:
    Semaphore(int n): n_{
   n} {
   }

public:
    void wait() {
   
        unique_lock<mutex> lock(mu_);
        if (!n_) {
   
            cv_.wait(lock, [this]{
   return n_;});
        }
        --n_;
    }

    void signal() {
   
        unique_lock<mutex> lock(mu_);
        ++n_;
        cv_.notify_one();
    }
};

class H2O {
   
private:
    Semaphore s_hIn, s_oIn;
    Semaphore s_hBarrier, s_oBarrier;

public:
    H2O(): s_hIn{
   2}, s_oIn{
   1}, s_hBarrier{
   0}, s_oBarrier{
   0} {
   }

    void hydrogen(function<void()> releaseHydrogen) {
   
        s_hIn.wait();
        s_oBarrier.signal();
        s_hBarrier.wait();
        releaseHydrogen();
        s_hIn.signal();
    }

    void oxygen(function<void()> releaseOxygen) {
   
        s_oIn.wait();
        s_oBarrier.wait();
        s_oBarrier.wait();
        s_hBarrier.signal();
        s_hBarrier.signal();
        releaseOxygen();
        s_oIn.signal();
    }
};

使用原子操作即可完成线程间的同步。

作者:NiceBlueChai

链接:https://leetcode-cn.com/problems/building-h2o/solution/cpp-shi-yong-yuan-zi-cao-zuo-jin-xing-tong-bu-da-y/

来源:力扣(LeetCode)

class H2O {
   
    atomic<int> h2;
public:
    H2O() {
   
        h2=0;
    }

    void hydrogen(function<void()> releaseHydrogen) {
   

        // releaseHydrogen() outputs "H". Do not change or remove this line.
        while(h2.load()>1)std::this_thread::yield();
        releaseHydrogen();
        h2++;
    }

    void oxygen(function<void()> releaseOxygen) {
   

        // releaseOxygen() outputs "O". Do not change or remove this line.
        while(h2.load()!=2)std::this_thread::yield();
        releaseOxygen();
        h2.store(0);
    }
};

1118. 设计有限阻塞队列

题目

leetcode原题:1188. 设计有限阻塞队列

实现一个拥有如下方法的线程安全有限阻塞队列:

  • BoundedBlockingQueue(int capacity) 构造方法初始化队列,其中capacity代表队列长度上限。

  • void enqueue(int element) 在队首增加一个element. 如果队列满,调用线程被阻塞直到队列非满。

  • int dequeue() 返回队尾元素并从队列中将其删除. 如果队列为空,调用线程被阻塞直到队列非空。

  • int size() 返回当前队列元素个数。

你的实现将会被多线程同时访问进行测试。每一个线程要么是一个只调用enqueue方法的生产者线程,要么是一个只调用dequeue方法的消费者线程。size方法将会在每一个测试用例之后进行调用。

请不要使用内置的有限阻塞队列实现,否则面试将不会通过。

示例 1:

输入:
1
1
["BoundedBlockingQueue","enqueue","dequeue","dequeue","enqueue","enqueue","enqueue","enqueue","dequeue"]
[[2],[1],[],[],[0],[2],[3],[4],[]]

输出:
[1,0,2,2]

解释:
生产者线程数目 = 1
消费者线程数目 = 1

BoundedBlockingQueue queue = new BoundedBlockingQueue(2); // 使用capacity = 2初始化队列。

queue.enqueue(1); // 生产者线程将1插入队列。
queue.dequeue(); // 消费者线程调用dequeue并返回1。
queue.dequeue(); // 由于队列为空,消费者线程被阻塞。
queue.enqueue(0); // 生产者线程将0插入队列。消费者线程被解除阻塞同时将0弹出队列并返回。
queue.enqueue(2); // 生产者线程将2插入队列。
queue.enqueue(3); // 生产者线程将3插入队列。
queue.enqueue(4); // 生产者线程由于队列长度已达到上限2而被阻塞。
queue.dequeue(); // 消费者线程将2从队列弹出并返回。生产者线程解除阻塞同时将4插入队列。
queue.size(); // 队列中还有2个元素。size()方法在每组测试用例最后调用。

示例 2:

输入:
3
4
["BoundedBlockingQueue","enqueue","enqueue","enqueue","dequeue","dequeue","dequeue","enqueue"]
[[3],[1],[0],[2],[],[],[],[3]]

输出:
[1,0,2,1]

解释:
生产者线程数目 = 3
消费者线程数目 = 4

BoundedBlockingQueue queue = new BoundedBlockingQueue(3); // 使用capacity = 3初始化队列。

queue.enqueue(1); // 生产者线程P1将1插入队列。
queue.enqueue(0); // 生产者线程P2将0插入队列。
queue.enqueue(2); // 生产者线程P3将2插入队列。
queue.dequeue(); // 消费者线程C1调用dequeue。
queue.dequeue(); // 消费者线程C2调用dequeue。
queue.dequeue(); // 消费者线程C3调用dequeue。
queue.enqueue(3); // 其中一个生产者线程将3插入队列。
queue.size(); // 队列中还有1个元素。

由于生产者/消费者线程的数目可能大于1,我们并不知道线程如何被操作系统调度,即使输入看上去隐含了顺序。因此任意一种输出[1,0,2]或[1,2,0]或[0,1,2]或[0,2,1]或[2,0,1]或[2,1,0]都可被接受。

解法

算法与数据结构-设计有限阻塞队列 - Ging - 博客园

解析

阻塞队列,首先想到要使用ReetrantLock来实现锁,同时需要使用lock来创建两个的等待条件,一个是非空一个是非满,生产者线程等待非满条件入队,消费者线程等待非空条件出队。同时题目需求,入队在队首,出队在队尾,可以选择使用LinkList中的addFirst和removeLast来实现。

这样大框架就出来了。

class BoundedBlockingQueue {
   

    private LinkedList<Integer> innerQueue;
    private int capacity;

    private ReentrantLock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BoundedBlockingQueue(int capacity) {
   
        innerQueue = new LinkedList<Integer>();
        this.capacity = capacity;
    }

    public void enqueue(int element) throws InterruptedException {
   
        try{
   
            lock.lockInterruptibly();
            while(size() == capacity){
   
                notFull.await();
            }
            innerQueue.addFirst(element);
            notEmpty.signalAll();
        }finally{
   
            lock.unlock();
        }
    }

    public int dequeue() throws InterruptedException {
   
        try{
   
            lock.lockInterruptibly();
             while(size() == 0){
   
                notEmpty.await();
            }
            int result = innerQueue.removeLast();
            notFull.signalAll();
            return result;
        }finally{
   
            lock.unlock();
        }
    }

    public int size() {
   
        try{
   
            lock.lock();
            return innerQueue.size();
        }finally{
   
            lock.unlock();
        }
    }
}

1188:设计有限阻塞队列Tortoise007的博客-CSDN博客\设计有限阻塞队列

public class BoundedBlockingQueue {
   

    private LinkedList<Integer> queue=new LinkedList<>();

    private ReentrantLock lock=new ReentrantLock();
    private Condition empty=lock.newCondition();
    private Condition full=lock.newCondition();
    //目前队列中多少元素
    private Integer size=0;
    //队列大小
    private Integer cap=null;

    //构造函数初始化有界队列大小
    public BoundedBlockingQueue(int capacity) {
   
        if(cap==null){
   
            lock.lock();
            try{
   
                if(cap==null){
   
                    cap=capacity;
                }
            }finally {
   
                lock.unlock();
            }
        }
    }

    //入队方法
    public void enqueue(int element) throws InterruptedException {
   
        lock.lock();
        try{
   
            //如果队列已满,阻塞元素入队
            while(size>=cap){
   
                full.await();
            }
            queue.offerFirst(element);
            size+=1;
            //唤醒empty
            empty.signalAll();
        }finally {
   
            lock.unlock();
        }
    }

    //出队方法
    public int dequeue() throws InterruptedException {
   
        lock.lock();
        int res=-1;
        try {
   
            //队列空,阻塞元素出队
            while(size==0){
   
                empty.await();
            }
            res=queue.pollLast();
            size-=1;
            //唤醒full
            full.signalAll();
        } finally {
   
            lock.unlock();
        }
        return res;
    }

    public int size() {
   
        return size;
    }
}

方法1:两个信号量
主要思路:
(1)使用两个信号量实现同步有序执行;
(2)两个信号量 sem_enqueue 初始化为需要的容量capacity,sem_dequeue初始化为 0,配合实现对 队列的共享;
(3)调用函数 enqueue 时,表示有元素要入队列,则sem_enqueue先调用wait函数,若是队列已满,则此时的sem_enqueue的值已经为0,则阻塞在wait函数,否则,元素入队,然后对信号量sem_dequeue调用post函数,表示队列中有元素存在,可以执行出队操作,若是有线程阻塞到 sem_dequeue 的wait函数,则此时可以接着执行;
(4)调用函数 dequeue时,表示有元素要出队,此时先调用信号量 sem_dequeue,此时若队列中没有元素,既若是为空,则阻塞在这里(注意其初始值为0,既若没有执行过入队函数,则一定阻塞在这里),当有元素压入到队列中时,才可以执行,弹出元素后,要对信号领 sem_enqueue执行post操作,表示队列中腾出了空间,可以接着执行入队操作了,若是有线程阻塞在其对应的wait函数,则此时可以接着执行;

方法2:使用互斥量和条件变量
主要思路:
(1)使用两个条件变量来标识队列的满 或 空 的状态,使用互斥量实现对 队列的互斥访问;
(2)注意将两个信号量的wait函数放入到while循环中,避免惊群现象;
————————————————
版权声明:本文为CSDN博主「王培琳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44171872/article/details/107570938

#include<semaphore.h>
class BoundedBlockingQueue {
   
private:
    queue<int> q;
    sem_t sem_enqueue;
    sem_t sem_dequeue;
    int cap;
public:

    BoundedBlockingQueue(int capacity) {
   
        cap=capacity;
        //初始化两个信号量,一个用于入对,一个用于出队
        sem_init(&sem_enqueue,0,capacity);
        sem_init(&sem_dequeue,0,0);
    }

    void enqueue(int element) {
   
        //入队的信号量若大于0,表示有空间剩余,可以接着入队,否则阻塞
        sem_wait(&sem_enqueue);
        q.push(element);
        //出队信号量加1,表示此时队列中有元素存在,可以执行出队操作
        sem_post(&sem_dequeue);
    }

    int dequeue() {
   
        //出队信号量若大于0,表示队列中有元素存在,可以执行出队操作,否则阻塞
        sem_wait(&sem_dequeue);
        int tmp=q.front();
        q.pop();
        //入队信号量加1,表示有空余的空间了,可以执行入队操作
        sem_post(&sem_enqueue);
        return tmp;
    }

    int size() {
   
        return q.size();
    }
};
#include<pthread.h>
class BoundedBlockingQueue {
   
private:
    pthread_mutex_t mutex;
    pthread_cond_t de_cond;
    pthread_cond_t en_cond;
    int cap;
    queue<int> q;    
public:
    BoundedBlockingQueue(int capacity) {
   
        int cap=capacity;
        pthread_cond_init(& de_cond,0);
        pthread_cond_init(&en_cond,0);
        pthread_mutex_init(& mutex,0);
    }

    void enqueue(int element) {
   
        pthread_mutex_lock(&mutex);//使用互斥量和条件变量配合,需要先对互斥量加锁
        while(q.size()==cap){
   //注意将wait函数放入到while循环中,避免惊群现象
            pthread_cond_wait(&en_cond,&mutex);
        }
        q.push(element);
        pthread_cond_signal(&de_cond);//此时队列中有元素,故可以给入队的信号量发信号,解除阻塞
        pthread_mutex_unlock(&mutex);//解锁互斥量
    }

    int dequeue() {
   
        pthread_mutex_lock(& mutex);
        while(q.empty()){
   //避免惊群现象
            pthread_cond_wait(&de_cond,& mutex);
        }
        int tmp=q.front();
        q.pop();
        pthread_cond_signal(&en_cond);//此时队列中有空间存在,可以给出队的信号量发信号,解除阻塞
        pthread_mutex_unlock(& mutex);//解除信号量
        return tmp;
    }

    int size() {
   
        return q.size();
    }
};

1195. 交替打印字符串

编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:

  • 如果这个数字可以被 3 整除,输出 "fizz"。
  • 如果这个数字可以被 5 整除,输出 "buzz"。
  • 如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz"。

例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz

假设有这么一个类:

class FizzBuzz {
  public FizzBuzz(int n) { ... }               // constructor
  public void fizz(printFizz) { ... }          // only output "fizz"
  public void buzz(printBuzz) { ... }          // only output "buzz"
  public void fizzbuzz(printFizzBuzz) { ... }  // only output "fizzbuzz"
  public void number(printNumber) { ... }      // only output the numbers
}

请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:

  1. 线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz
  2. 线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz
  3. 线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz
  4. 线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。

提示:

  • 本题已经提供了打印字符串的相关方法,如 printFizz() 等,具体方法名请参考答题模板中的注释部分。

解法

1、使用linux barrier 屏障
调用pthread_barrier_wait,计数没到设定的值(这里是4) 线程就会休眠
每调用一次pthread_barrier_wait 计数加1,加到4唤醒所有线程,又重新从0开始计数

作者:eric-345
链接:https://leetcode-cn.com/problems/fizz-buzz-multithreaded/solution/1195-jiao-ti-da-yin-zi-fu-chuan-by-eric-tyr4w/
来源:力扣(LeetCode)


#include <pthread.h>

class FizzBuzz {
   
private:
    int n;
    pthread_barrier_t b;


public:
    FizzBuzz(int n) {
   
        this->n = n;
        pthread_barrier_init(&b, NULL, 4);  // 4个线程
    }

    ~FizzBuzz() {
   
        pthread_barrier_destroy(&b);
    }

    // printFizz() outputs "fizz".
    void fizz(function<void()> printFizz) {
   
        for(int i = 1; i <= n; ++i) {
   
            // 仅被3整除
            if (i % 3 == 0 && i % 5 != 0) {
   
                printFizz();
            }
            pthread_barrier_wait(&b);
        }
    }

    // printBuzz() outputs "buzz".
    void buzz(function<void()> printBuzz) {
   
        for(int i = 1; i <= n; ++i) {
   
             // 仅被5整除
            if (i % 3 != 0 && i % 5 == 0) {
   
                printBuzz();
            }
            pthread_barrier_wait(&b);
        }
    }

    // printFizzBuzz() outputs "fizzbuzz".
    void fizzbuzz(function<void()> printFizzBuzz) {
   
        for(int i = 1; i <= n; ++i) {
   
            if(i % 3 == 0 && i % 5 == 0) {
   
                printFizzBuzz();
            }
            pthread_barrier_wait(&b);
        }
    }

    // printNumber(x) outputs "x", where x is an integer.
    void number(function<void(int)> printNumber) {
   
        for(int i = 1; i <= n; ++i) {
   
            if(i % 3 != 0 && i % 5 != 0) {
   
                printNumber(i);
            }
            pthread_barrier_wait(&b);
        }
    }
};

mutex + condition_variable 的实现

每个线程都维持自己需要等待的状态,知道num > n的时候才结束  
要注意能结束线程,避免死循环

作者:ffreturn
链接:https://leetcode-cn.com/problems/fizz-buzz-multithreaded/solution/czhong-gui-zhong-ju-de-mutexcondition_va-lkao/
来源:力扣(LeetCode)

class FizzBuzz {
   
private:
    int n;
    int num;
    condition_variable cv;
    mutex m;

public:
    FizzBuzz(int n) {
   
        this->n = n;
        num = 1;
    }

    // printFizz() outputs "fizz".
    void fizz(function<void()> printFizz) {
   
        unique_lock<mutex> l(m);
        while (num <= n)
        {
   
            if ((num % 3 == 0) && (num % 5 != 0))
            {
   
                printFizz();
                ++num;
                cv.notify_all();
            }

            cv.wait(l, [this]{
   
                // 结束条件是 num超过范围 或者是 不能被3和5整除的数字
                return (num > n) || ((num % 3 == 0) && (num % 5 != 0));
            });
        }        
    }

    // printBuzz() outputs "buzz".
    void buzz(function<void()> printBuzz) {
   
        unique_lock<mutex> l(m);
        while (num <= n)
        {
   
            if ((num % 3 != 0) && (num % 5 == 0))
            {
   
                printBuzz();
                ++num;
                cv.notify_all();
            }

            cv.wait(l, [this]{
   
                // 结束条件是 num超过范围 或者是 不能被3和5整除的数字
                return (num > n) || ((num % 3 != 0) && (num % 5 == 0));
            });
        }   
    }

    // printFizzBuzz() outputs "fizzbuzz".
    void fizzbuzz(function<void()> printFizzBuzz) {
   
        unique_lock<mutex> l(m);
        while (num <= n)
        {
   
            if ((num % 3 == 0) && (num % 5 == 0))
            {
   
                printFizzBuzz();
                ++num;
                cv.notify_all();
            }

            cv.wait(l, [this]{
   
                // 结束条件是 num超过范围 或者是 能被3和5整除的数字
                return (num > n) || ((num % 3 == 0) && (num % 5 == 0));
            });
        }   
    }

    // printNumber(x) outputs "x", where x is an integer.
    void number(function<void(int)> printNumber) {
   
        unique_lock<mutex> l(m);
        while (num <= n)
        {
   
            if ((num % 3 != 0) && (num % 5 != 0))
            {
   
                printNumber(num);
                ++num;
                // 关键一步,避免死循环
                if (num > n)
                {
   
                    cv.notify_all();
                    break;
                }

                if ((num % 3 != 0) && (num % 5 != 0))
                {
   
                    // 依然满足条件,继续下一个num处理
                    continue;
                }
                else
                {
   
                    // 不满足,则把控制权给其他线程
                    cv.notify_all();
                }
            }

            cv.wait(l, [this]{
   
                // 结束条件是 num超过范围 或者是 能被3和5整除的数字
                return (num > n) || ((num % 3 != 0) && (num % 5 != 0));
            });
        }
    }
};

1226. 哲学家进餐

5 个沉默寡言的哲学家围坐在圆桌前,每人面前一盘意面。叉子放在哲学家之间的桌面上。(5 个哲学家,5 根叉子)

所有的哲学家都只会在思考和进餐两种行为间交替。哲学家只有同时拿到左边和右边的叉子才能吃到面,而同一根叉子在同一时间只能被一个哲学家使用。每个哲学家吃完面后都需要把叉子放回桌面以供其他哲学家吃面。只要条件允许,哲学家可以拿起左边或者右边的叉子,但在没有同时拿到左右叉子时不能进食。

假设面的数量没有限制,哲学家也能随便吃,不需要考虑吃不吃得下。

设计一个进餐规则(并行算法)使得每个哲学家都不会挨饿;也就是说,在没有人知道别人什么时候想吃东西或思考的情况下,每个哲学家都可以在吃饭和思考之间一直交替下去。

问题描述和图片来自维基百科 wikipedia.org

哲学家从 04顺时针 编号。请实现函数 void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)

  • philosopher 哲学家的编号。
  • pickLeftForkpickRightFork 表示拿起左边或右边的叉子。
  • eat 表示吃面。
  • putLeftForkputRightFork 表示放下左边或右边的叉子。
  • 由于哲学家不是在吃面就是在想着啥时候吃面,所以思考这个方法没有对应的回调。

给你 5 个线程,每个都代表一个哲学家,请你使用类的同一个对象来模拟这个过程。在最后一次调用结束之前,可能会为同一个哲学家多次调用该函数。

示例:

输入:n = 1
输出:[[4,2,1],[4,1,1],[0,1,1],[2,2,1],[2,1,1],[2,0,3],[2,1,2],[2,2,2],[4,0,3],[4,1,2],[0,2,1],[4,2,2],[3,2,1],[3,1,1],[0,0,3],[0,1,2],[0,2,2],[1,2,1],[1,1,1],[3,0,3],[3,1,2],[3,2,2],[1,0,3],[1,1,2],[1,2,2]]
解释:
n 表示每个哲学家需要进餐的次数。
输出数组描述了叉子的控制和进餐的调用,它的格式如下:
output[i] = [a, b, c] (3个整数)
- a 哲学家编号。
- b 指定叉子:{1 : 左边, 2 : 右边}.
- c 指定行为:{1 : 拿起, 2 : 放下, 3 : 吃面}。
如 [4,2,1] 表示 4 号哲学家拿起了右边的叉子。

提示:

  • 1 <= n <= 60

解法

  1. 学过操作系统课程的同学都知道这个题目,参考答案
  2. 总共有三种办法可以避免死锁的发生;
  3. 限定哲学家就餐数量;

作者:mike-meng

链接:https://leetcode-cn.com/problems/the-dining-philosophers/solution/zhe-xue-jia-jiu-can-wen-ti-by-mike-meng/

来源:力扣(LeetCode)

class Semaphore {
   
public:
  Semaphore(int count = 0) : count_(count) {
   
  }

  void Set(int count){
   
      count_ = count;
  }

  void Signal() {
   
    std::unique_lock<std::mutex> lock(mutex_);
    ++count_;
    cv_.notify_one();
  }

  void Wait() {
   
    std::unique_lock<std::mutex> lock(mutex_);
    while(count_ <= 0){
   
        cv_.wait(lock);
    }
    --count_;
  }

private:
  std::mutex mutex_;
  std::condition_variable cv_;
  int count_;
};

class DiningPhilosophers {
   
public:
    DiningPhilosophers() {
   
        guid.Set(4);
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
   
        int l = philosopher;
        int r = (philosopher+1)%5;
        guid.Wait();        

        lock[l].lock();
        lock[r].lock();
        pickLeftFork();
        pickRightFork();
        eat();
        putRightFork();
        putLeftFork();
        lock[r].unlock();
        lock[l].unlock();

        guid.Signal();
    }
private:
    std::mutex lock[5];
    Semaphore guid;
};

同时抬起左右拿叉子;

class DiningPhilosophers {
   
public:
    DiningPhilosophers() {
   
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
   
        int l = philosopher;
        int r = (philosopher+1)%5;
        guid.lock();        
        lock[l].lock();
        lock[r].lock();
        pickLeftFork();
        pickRightFork();
        guid.unlock();
        eat();
        putRightFork();
        putLeftFork();
        lock[l].unlock();
        lock[r].unlock();
    }
private:
    std::mutex lock[5];
    std::mutex guid;
};

限定就餐策略;

class DiningPhilosophers {
   
public:
    DiningPhilosophers() {
   
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
   
        int l = philosopher;
        int r = (philosopher+1)%5;
        if(philosopher%2 == 0){
   
            lock[r].lock();
            lock[l].lock();
            pickLeftFork();
            pickRightFork();
        }else{
   
            lock[l].lock();
            lock[r].lock();
            pickLeftFork();
            pickRightFork();
        }

        eat();
        putRightFork();
        putLeftFork();
        lock[l].unlock();
        lock[r].unlock();
    }
private:
    std::mutex lock[5];
};

(3条消息) 哲学家就餐问题_一切还不都是因为菜的博客-CSDN博客_哲学家进餐问题https://blog.csdn.net/thelostlamb/article/details/80741319

// https://blog.csdn.net/thelostlamb/article/details/80741319
// Plan B

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>

class DiningPhilosophers {
   
public:
    DiningPhilosophers() = default;

    void wantsToEat(int philosopher, std::function<void()> pickLeftFork,
                    std::function<void()> pickRightFork, std::function<void()> eat,
                    std::function<void()> putLeftFork, std::function<void()> putRightFork) {
   
        auto left = philosopher % N;
        auto right = (philosopher + 1) % N;
        andLock.lock();
        std::lock_guard<std::mutex> leftLock(forkMutex[left]);
        std::lock_guard<std::mutex> righLock(forkMutex[right]);
        pickLeftFork();
        pickRightFork();
        andLock.unlock();
        eat();
        putLeftFork();
        putRightFork();
    }

private:
    static constexpr int N = 5;
    std::mutex forkMutex[N];
    std::mutex andLock;
};

int main() {
   
    auto pickLeftFork = []() {
    std::cout << "[" << std::this_thread::get_id() << ", 1, 1], "; };
    auto pickRightFork = []() {
    std::cout <<  "[" << std::this_thread::get_id() << ", 2, 1], "; };
    auto eat = []() {
    std::cout << "[" << std::this_thread::get_id() << ", 0, 3], "; };
    auto putLeftFork = []() {
    std::cout << "[" << std::this_thread::get_id() << ", 1, 2], "; };
    auto putRightFork = []() {
    std::cout << "[" << std::this_thread::get_id() << ", 2, 2], "; };
    auto execute = [&](int n) {
   
        DiningPhilosophers diningPhilosophers;
        std::thread t1(&DiningPhilosophers::wantsToEat, &diningPhilosophers, 1, pickRightFork,
            pickRightFork, eat, putRightFork, putRightFork);
        std::thread t2(&DiningPhilosophers::wantsToEat, &diningPhilosophers, 2, pickRightFork,
            pickRightFork, eat, putRightFork, putRightFork);
        std::thread t3(&DiningPhilosophers::wantsToEat, &diningPhilosophers, 3, pickRightFork,
            pickRightFork, eat, putRightFork, putRightFork);
        std::thread t4(&DiningPhilosophers::wantsToEat, &diningPhilosophers, 4, pickRightFork,
            pickRightFork, eat, putRightFork, putRightFork);
        std::thread t5(&DiningPhilosophers::wantsToEat, &diningPhilosophers, 5, pickRightFork,
            pickRightFork, eat, putRightFork, putRightFork);
        t1.join(); t2.join(); t3.join(); t4.join(); t5.join();
    };

    execute(1);
    std::cout << std::endl;
}

作者:jcong
链接:https://leetcode.cn/problems/the-dining-philosophers/solution/the-dining-philosophers-c-by-jcong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define N 5

//信号量使用的参数
sem_t chopsticks[N];
sem_t r;
int philosophers[N] = {
   0, 1, 2, 3, 4};

//swap指令需要的参数
int islocked[N] = {
   0};

//互斥量使用的参数
pthread_mutex_t chops[N];

//延迟函数
void delay (int len) {
   
    int i = rand() % len;
    int x;
    while (i > 0) {
   
        x = rand() % len;
        while (x > 0) {
   
            x--;
        }
        i--;
    }
}

//交换函数:目前为发现bug
void xchg(int *x, int *y) {
   
    __asm__("xchgl %0, %1" : "=r" (*x) : "m" (*y));
}

//这个函数使用的解决办法是最多允许四个哲学家拿起左筷子
void philosopher (void* arg) {
   
    int i = *(int *)arg;
    int left = i;
    int right = (i + 1) % N;
    int leftkey;
    int rightkey;
    while (1) {
   
        leftkey = 1;
        rightkey = 1;


        printf("哲学家%d正在思考问题\n", i);
        delay(50000);

        printf("哲学家%d饿了\n", i);
        sem_wait(&r);

        //sem_wait(&chopsticks[left]);
        //pthread_mutex_lock(&chopsticks[left]);
        do {
   
            xchg(&leftkey, &islocked[left]);
        }while (leftkey);
        printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);

        //sem_wait(&chopsticks[right]);
        //pthread_mutex_lock(&chopsticks[right]);
        do {
   
            xchg(&rightkey, &islocked[right]);
        }while (rightkey);
        printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, right);
        delay(50000);

        //sem_post(&chopsticks[left]);
        //pthread_mutex_unlock(&chopsticks[left]);
        islocked[left] = 0;
        printf("哲学家%d放下了%d号筷子\n", i, left);

        //sem_post(&chopsticks[right]);
        //pthread_mutex_unlock(&chopsticks[right]);
        islocked[right] = 0;
        printf("哲学家%d放下了%d号筷子\n", i, right);

        sem_post(&r);
    }
}

//这个函数使用的解决办法是奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。
void philosopher2 (void* arg) {
   
    int i = *(int *)arg;
    int left = i;
    int right = (i + 1) % N;
    while (1) {
   
        printf("哲学家%d正在思考问题\n", i);
        delay(50000);

        printf("哲学家%d饿了\n", i);
        if (i % 2 == 0) {
   //偶数哲学家,先右后左
            sem_wait(&chopsticks[right]);
            printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, right);
            sem_wait(&chopsticks[left]);
            printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, left);
            delay(50000);
            sem_post(&chopsticks[left]);
            printf("哲学家%d放下了%d号筷子\n", i, left);
            sem_post(&chopsticks[right]);
            printf("哲学家%d放下了%d号筷子\n", i, right);
        } else {
   //奇数哲学家,先左后又
            sem_wait(&chopsticks[left]);
            printf("哲学家%d拿起了%d号筷子, 现在有两支筷子,开始进餐\n", i, left);
            sem_wait(&chopsticks[right]);
            printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, right);
            delay(50000);
            sem_post(&chopsticks[right]);
            printf("哲学家%d放下了%d号筷子\n", i, right);
            sem_post(&chopsticks[left]);
            printf("哲学家%d放下了%d号筷子\n", i, left);
        }
    }
}

int main (int argc, char **argv) {
   
    srand(time(NULL));
    pthread_t PHD[N];


    for (int i=0; i<N; i++) {
   
        sem_init(&chopsticks[i], 0, 1);
    }
    sem_init(&r, 0, 4);

    for (int i=0; i<N; i++) {
   
        pthread_mutex_init(&chops[i], NULL);
    }

    for (int i=0; i<N; i++) {
   
        pthread_create(&PHD[i], NULL, (void*)philosopher2, &philosophers[i]);
    }
    for (int i=0; i<N; i++) {
   
        pthread_join(PHD[i], NULL);
    }

    for (int i=0; i<N; i++) {
   
        sem_destroy(&chopsticks[i]);
    }
    sem_destroy(&r);
    for (int i=0; i<N; i++) {
   
        pthread_mutex_destroy(&chops[i]);
    }
    return 0;
}

1242. 多线程网页爬虫

给你一个初始地址 startUrl 和一个 HTML 解析器接口 HtmlParser,请你实现一个 多线程的网页爬虫,用于获取与 startUrl 有 相同主机名 的所有链接。

以 任意 顺序返回爬虫获取的路径。

爬虫应该遵循:

从 startUrl 开始
调用 HtmlParser.getUrls(url) 从指定网页路径获得的所有路径。
不要抓取相同的链接两次。
仅浏览与 startUrl 相同主机名 的链接。

如上图所示,主机名是 example.org 。简单起见,你可以假设所有链接都采用 http 协议,并且没有指定 端口号。举个例子,链接 http://leetcode.com/problems 和链接 http://leetcode.com/contest 属于同一个 主机名, 而 http://example.org/testhttp://example.com/abc 并不属于同一个 主机名。

HtmlParser 的接口定义如下:

interface HtmlParser {
// Return a list of all urls from a webpage of given url.
// This is a blocking call, that means it will do HTTP request and return when this request is finished.
public List getUrls(String url);
}
注意一点,getUrls(String url) 模拟执行一个HTTP的请求。 你可以将它当做一个阻塞式的方法,直到请求结束。 getUrls(String url) 保证会在 15ms 内返回所有的路径。 单线程的方案会超过时间限制,你能用多线程方案做的更好吗?

对于问题所需的功能,下面提供了两个例子。为了方便自定义测试,你可以声明三个变量 urls,edges 和 startUrl。但要注意你只能在代码中访问 startUrl,并不能直接访问 urls 和 edges。

拓展问题:

假设我们要要抓取 10000 个节点和 10 亿个路径。并且在每个节点部署相同的的软件。软件可以发现所有的节点。我们必须尽可能减少机器之间的通讯,并确保每个节点负载均衡。你将如何设计这个网页爬虫?
如果有一个节点发生故障不工作该怎么办?
如何确认爬虫任务已经完成?

示例 1:

输入:
urls = [
"http://news.yahoo.com",
"http://news.yahoo.com/news",
"http://news.yahoo.com/news/topics/",
"http://news.google.com",
"http://news.yahoo.com/us"
]
edges = [[2,0],[2,1],[3,2],[3,1],[0,4]]
startUrl = "http://news.yahoo.com/news/topics/"
输出:[
"http://news.yahoo.com",
"http://news.yahoo.com/news",
"http://news.yahoo.com/news/topics/",
"http://news.yahoo.com/us"
]
示例 2:

输入:
urls = [
"http://news.yahoo.com",
"http://news.yahoo.com/news",
"http://news.yahoo.com/news/topics/",
"http://news.google.com"
]
edges = [[0,2],[2,1],[3,2],[3,1],[3,0]]
startUrl = "http://news.google.com"
输出:["http://news.google.com"\]
解释:startUrl 链接与其他页面不共享一个主机名。

提示:

1 <= urls.length <= 1000
1 <= urls[i].length <= 300
startUrl 是 urls 中的一个。
主机名的长度必须为 1 到 63 个字符(包括点 . 在内),只能包含从 “a” 到 “z” 的 ASCII 字母和 “0” 到 “9” 的数字,以及中划线 “-”。
主机名开头和结尾不能是中划线 “-”。
参考资料:https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames
你可以假设路径都是不重复的。

解法

这道题目是test case 有问题还是写的代码有问题。 为啥不给一下开启的线程数量。 MAX_ALIVE_THREAD_NUM 这个量定小了就会超时,多了就会超出内存限制。

我给的做法是用 CountDownLatch。 起某一数量的线程,把耗时的操作htmlParser.getUrls() 放到一个独立的线程中去进行操作。

这里需要维护一个 线程安全的 queue (也可以去限制queue的大小,e.g boundedblockingqueue) 来去保存需要 CrawlWorker 去 crawl 的request url .

因为这里queue和set 都会有多个线程同时读写。所以 要用线程安全的 queue 和set。ConcurrentLinkedQueue 已经保证了多个线程同时读/写访问的安全性了。

每个阶段开的线程数量取决于queue的size和 MAX_ALIVE_THREAD_NUM 中的最小值。

这道题目的本质就是找到独立互不影响的操作, 开启一个线程去执行。对于这道题目就是对每一个url 爬虫都是一个独立request。 然后从爬出的url选出同一个host以及没出现在结果集的作为新的request放到 queue中。

还有一个比较好的练习可以写一下, Merge k sorted list Multithreaded ,关键也是找到线程独立的操作。

/**
* // This is the HtmlParser's API interface.
* // You should not implement it, or speculate about its implementation
* interface HtmlParser {
* public List<String> getUrls(String url) {}
* }
*/
class Solution {
   


class CrawlWorker implements Runnable {
   

private String startUrl;

private CountDownLatch countDownLatch;

private HtmlParser htmlParser;

CrawlWorker(String startUrl, CountDownLatch countDownLatch,HtmlParser htmlParser){
   
this.startUrl = startUrl;
this.countDownLatch = countDownLatch;
this.htmlParser = htmlParser;
}

@Override
public void run() {
   
parse();
}

private void parse(){
   
urlSet.add(startUrl);
List<String> urlList = htmlParser.getUrls(startUrl);
for(String url : urlList){
   
if(urlSet.contains(url) || !getHost(url).equals(hostName)) continue;
queue.offer(url);
}

this.countDownLatch.countDown();
}
}

private final Set<String> urlSet = ConcurrentHashMap.newKeySet();
private final Queue<String> queue = new ConcurrentLinkedQueue<>();

private String hostName;
private static final Integer MAX_ALIVE_THREAD_NUM = 128;


public List<String> crawl(String startUrl, HtmlParser htmlParser) {
   

hostName = getHost(startUrl);

queue.offer(startUrl);
while(!queue.isEmpty()){
   

int curThreadNum = Math.min(MAX_ALIVE_THREAD_NUM, queue.size());

CountDownLatch countDownLatch = new CountDownLatch(curThreadNum);

for(int idx = 0; idx < curThreadNum ;idx++){
   
String curUrl = queue.poll();
CrawlWorker crawlWorker = new CrawlWorker(curUrl,countDownLatch, htmlParser);
Thread thread = new Thread(crawlWorker);
thread.start();
}

try {
   
countDownLatch.await();
} catch (InterruptedException e) {
   
e.printStackTrace();
}
}
return new ArrayList<>(urlSet);
}


private static String getHost(String url){
   
String host = url.substring(7); // all urls use http protocol
int idx = host.indexOf('/');
if(idx == -1) return host;
return host.substring(0,idx);
}
}

作者:huya-402994
链接:https://leetcode-cn.com/problems/web-crawler-multithreaded/solution/qiu-tao-lun-zhe-dao-xie-ding-e-de-ti-by-huya-40299/
来源:力扣(LeetCode)

1279. 红绿灯路口

题目描述:
这是两条路的交叉路口。第一条路是 A 路,车辆可沿 1 号方向由北向南行驶,也可沿 2 号方向由南向北行驶。第二条路是 B 路,车辆可沿 3 号方向由西向东行驶,也可沿 4 号方向由东向西行驶。

每条路在路口前都有一个红绿灯。红绿灯可以亮起红灯或绿灯。

绿灯表示两个方向的车辆都可通过路口。
红灯表示两个方向的车辆都不可以通过路口,必须等待绿灯亮起。
两条路上的红绿灯不可以同时为绿灯。这意味着,当 A 路上的绿灯亮起时,B 路上的红灯会亮起;当 B 路上的绿灯亮起时,A 路上的红灯会亮起.

开始时,A 路上的绿灯亮起,B 路上的红灯亮起。当一条路上的绿灯亮起时,所有车辆都可以从任意两个方向通过路口,直到另一条路上的绿灯亮起。不同路上的车辆不可以同时通过路口。

给这个路口设计一个没有死锁的红绿灯控制系统。

实现函数 void carArrived(carId, roadId, direction, turnGreen, crossCar) :

carId 为到达车辆的编号。
roadId 为车辆所在道路的编号。
direction 为车辆的行进方向。
turnGreen 是一个函数,调用此函数会使当前道路上的绿灯亮起。
crossCar 是一个函数,调用此函数会允许车辆通过路口。
当你的答案避免了车辆在路口出现死锁,此答案会被认定为正确的。当路口已经亮起绿灯时仍打开绿灯,此答案会被认定为错误的。

示例 1:
输入: cars = [1,3,5,2,4], directions = [2,1,2,4,3], arrivalTimes = [10,20,30,40,50]
输出: [
“Car 1 Has Passed Road A In Direction 2”, // A 路上的红绿灯为绿色,1 号车可通过路口。
“Car 3 Has Passed Road A In Direction 1”, // 红绿灯仍为绿色,3 号车通过路口。
“Car 5 Has Passed Road A In Direction 2”, // 红绿灯仍为绿色,5 号车通过路口。
“Traffic Light On Road B Is Green”, // 2 号车在 B 路请求绿灯。
“Car 2 Has Passed Road B In Direction 4”, // B 路上的绿灯现已亮起,2 号车通过路口。
“Car 4 Has Passed Road B In Direction 3” // 红绿灯仍为绿色,4 号车通过路口。
]

示例 2:
输入: cars = [1,2,3,4,5], directions = [2,4,3,3,1], arrivalTimes = [10,20,30,40,40]
输出: [
“Car 1 Has Passed Road A In Direction 2”, // A 路上的红绿灯为绿色,1 号车可通过路口。
“Traffic Light On Road B Is Green”, // 2 号车在 B 路请求绿灯。
“Car 2 Has Passed Road B In Direction 4”, // B 路上的绿灯现已亮起,2 号车通过路口。
“Car 3 Has Passed Road B In Direction 3”, // B 路上的绿灯现已亮起,3 号车通过路口。
“Traffic Light On Road A Is Green”, // 5 号车在 A 路请求绿灯。
“Car 5 Has Passed Road A In Direction 1”, // A 路上的绿灯现已亮起,5 号车通过路口。
“Traffic Light On Road B Is Green”, // 4 号车在 B 路请求绿灯。4 号车在路口等灯,直到 5 号车通过路口,B 路的绿灯亮起。
“Car 4 Has Passed Road B In Direction 3” // B 路上的绿灯现已亮起,4 号车通过路口。
]
解释: 这是一个无死锁的方案。注意,在 A 路上的绿灯亮起、5 号车通过前让 4 号车通过,也是一个正确且可被接受的方案。

提示:
1 <= cars.length <= 20
cars.length = directions.length
cars.length = arrivalTimes.length
cars 中的所有值都是唯一的。
1 <= directions[i] <= 4
arrivalTimes 是非递减的。

解法

版权声明:本文为CSDN博主「王培琳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44171872/article/details/107590904

方法1:使用信号量
主要思路:
(1)使用信号量保证对路口的拥有,并使用变量flag标识当前的路径之前是否为green的路径,若不是,则需要调用truegreen函数,改变路灯为绿灯,同时调整flag为当前路径(因为当前路径为green);
(2)当当前车通过后,将拥有权先释放;

#include<semaphore.h>
class TrafficLight {
   
public:

    sem_t sem;
    int flag;//标识为绿灯的路径号

    TrafficLight() {
   
        flag=1;
        sem_init(&sem,0,1);
    }

    void carArrived(
        int carId,                   // ID of the car
        int roadId,                  // ID of the road the car travels on. Can be 1 (road A) or 2 (road B)
        int direction,               // Direction of the car
        function<void()> turnGreen,  // Use turnGreen() to turn light to green on current road
        function<void()> crossCar    // Use crossCar() to make car cross the intersection
    ) {
   
        sem_wait(&sem);//先尝试获得拥有路口的权利
        //若当前路径不是之前为绿灯的路径,则调整路灯和路径
        if(flag!=roadId){
   
            turnGreen();
            flag=3-flag;
        }
        crossCar();
        sem_post(&sem);//释放拥有权
    }
};

方法2:使用互斥量
主要思路:
(1)这里由于信号量的初始值是1,则可以直接使用互斥量代替信号量;

#include<pthread.h>
class TrafficLight {
   
public:

    pthread_mutex_t mutex;
    int flag;

    TrafficLight() {
   
        flag=1;
        pthread_mutex_init(&mutex,0);
    }

    void carArrived(
        int carId,                   // ID of the car
        int roadId,                  // ID of the road the car travels on. Can be 1 (road A) or 2 (road B)
        int direction,               // Direction of the car
        function<void()> turnGreen,  // Use turnGreen() to turn light to green on current road
        function<void()> crossCar    // Use crossCar() to make car cross the intersection
    ) {
   
        pthread_mutex_lock(&mutex);
        if(flag!=roadId){
   
            turnGreen();
            flag=3-flag;
        }
        crossCar();
        pthread_mutex_unlock(&mutex);
    }
};
相关文章
|
22天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3
|
22天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
15 2
|
22天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
22天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
22天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
33 1
|
22天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
24 1
|
30天前
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,在编码阶段规避潜在风险
【10月更文挑战第12天】线程死循环确实是多线程编程中的一个常见问题,在编码阶段规避潜在风险
45 2
|
1月前
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,它可能导致应用程序性能下降,甚至使整个系统变得不稳定。
线程死循环是多线程编程中常见的问题,可能导致性能下降或系统不稳定。通过代码审查、静态分析、日志监控、设置超时、使用锁机制、测试、选择线程安全的数据结构、限制线程数、使用现代并发库及培训,可有效预防和解决死循环问题。
49 1
|
1月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
40 1
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行