水分子的产生

今天为大家分享一道看起来 “高大上” 的题目。

话不多说,直接看题吧。

01、水分子的产生

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


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


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


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


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


换句话说:

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

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

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


示例 1:

  1. 输入: "HOH"
  2. 输出: "HHO"
  3. 解释: "HOH" "OHH" 依然都是有效解。


示例 2:

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


限制条件:

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


代码模板:

  1. class H2O {
  2. public H2O() {
  3. }
  4. public void hydrogen(Runnable releaseHydrogen) throws InterruptedException { // releaseHydrogen.run() outputs "H". Do not change or remove this line. releaseHydrogen.run();
  5. }
  6. public void oxygen(Runnable releaseOxygen) throws InterruptedException {
  7. // releaseOxygen.run() outputs "O". Do not change or remove this line.
  8. releaseOxygen.run();
  9. }
  10. }

02、JAVA分析

乍看之下,题目貌似很高大上,不少同学一听多线程直接就慌了神。我们一起分析一下。一个氧消耗两个氢,两个氢供给一个氧。我们只要可以模拟氢和氧的供给关系,就可以顺利进行求解。


这里先介绍一下Java中的Semaphore:Semaphore是 synchronized 的加强版,作用是控制线程的并发数量。可以通过 acquire 和 release 来进行类似 lock 和 unlock 的操作。

  1. //请求一个信号量,这时候信号量个数-1,当减少到0的时候,下一次acquire不会再执行,只有当执行一个release()的时候,信号量不为0的时候才可以继续执行acquire
  2. void acquire()
  3. //释放一个信号量,这时候信号量个数+1,
  4. void release();

什么?听不懂!大白话就是叫做 Semaphore 的这个东东,可以控制同时有多少线程可以进去,比一般的锁要稍微高级那么一点点。


由于题目中给的限制条件,已经明确说明了H是2n,O是n,所以我们不需要考虑无法构成水分子的情况。我们分别定义H和O的信号量,都初始化为2个信号量。


在每一次产生O的过程中,都需要等待产生了两个H。

  1. import java.util.concurrent.Semaphore;
  2. class H2O {
  3. public H2O() { 6
  4. }
  5. private Semaphore h = new Semaphore(2);
  6. private Semaphore o = new Semaphore(2);
  7. public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
  8. h.acquire(1);
  9. releaseHydrogen.run();
  10. o.release(1);
  11. }
  12. public void oxygen(Runnable releaseOxygen) throws InterruptedException {
  13. o.acquire(2);
  14. releaseOxygen.run();
  15. h.release(2);
  16. }
  17. }

03、C++代码分析

如果没有原生的信号量支持怎么办?其实也是一样的。我们可以通过锁来模拟信号量。这里加一个C++版本的实现。

  1. class H2O {
  2. private:
  3. int countOxygen;
  4. pthread_mutex_t lockHy;
  5. pthread_mutex_t lockOx;
  6. public:
  7. H2O() {
  8. pthread_mutex_init(&lockOx,NULL);
  9. pthread_mutex_init(&lockHy,NULL);
  10. pthread_mutex_lock(&lockOx);
  11. countOxygen = 2;
  12. }
  13. void hydrogen(function<void()> releaseHydrogen) {
  14. pthread_mutex_lock(&lockHy);
  15. releaseHydrogen();
  16. countOxygen--;
  17. if(countOxygen > 0){
  18. pthread_mutex_unlock(&lockHy);
  19. }else{
  20. pthread_mutex_unlock(&lockOx);
  21. }
  22. }
  23. void oxygen(function<void()> releaseOxygen) {
  24. pthread_mutex_lock(&lockOx);
  25. releaseOxygen();
  26. countOxygen = 2;
  27. pthread_mutex_unlock(&lockHy);
  28. }
  29. };

03、Python代码分析

好吧,其他语言都有并发。但是我PY竟然连并发都没有(杠精勿扰,我知道有 threading 库可以用。并且里边也已经提供了现成的信号量可以用)这种情况下怎么办?


还是可以解决,我们可以用队列模拟进行实现

  1. class H2O:
  2. def __init__(self):
  3. self.h, self.o = [], []
  4. def hydrogen(self, releaseHydrogen: 'Callable[[], None]') -> None:
  5. self.h.append(releaseHydrogen) 7
  6. self.res()
  7. def oxygen(self, releaseOxygen: 'Callable[[], None]') -> None:
  8. self.o.append(releaseOxygen)
  9. self.res()
  10. def res(self):
  11. if len(self.h) > 1 and len(self.o) > 0:
  12. self.h.pop(0)()
  13. self.h.pop(0)()
  14. self.o.pop(0)()

04、GO语言版本

已经提供了PY,JAVA,C++的版本。对于GO而言,不管你是通过channel来模拟信号量的方式,还是参照PY的方式进行实现,我觉得应该都可以完成。