LeetCode---H2O生成问题

原题链接

H2O生成

题目

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

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

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

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

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

换句话说:

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

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

 

示例 1:

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

示例 2:

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

 

限制条件:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/building-h2o
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码模板

class H2O {
public:
    H2O() {
        
    }

    void hydrogen(function<void()> releaseHydrogen) {
        
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        releaseHydrogen();
    }

    void oxygen(function<void()> releaseOxygen) {
        
        // releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen();
    }
};

本地调试框架

// vs2015 update3 win64
#include 
#include 
#include 
#include 

// 题目的代码模板
class H2O {
public:
    H2O() {
        
    }

    void hydrogen(function<void()> releaseHydrogen) {
        
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        releaseHydrogen();
    }

    void oxygen(function<void()> releaseOxygen) {
        
        // releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen();
    }
};
// 题目的代码模板 -- end

H2O c;

void HThread() {
	c.hydrogen([]() {
		printf("H");
	});
}

void OThread() {
	c.oxygen([]() {
		printf("O");
	});
}

void main() {
	std::string test = "HOHOHHOOHOHHHHHOHHHOH";

	printf("input str: %s\n", test.c_str());
	for (int i = 0; i < test.size(); ++i) {
		if (test[i] == 'O') {
			std::thread *th = new std::thread(OThread);
		}
		else if (test[i] == 'H') {
			std::thread *th = new std::thread(HThread);
		}
	}

	std::this_thread::sleep_for(std::chrono::seconds(100));
	system("pause");
}

解题

思考了下,这题主要的问题应该是在氢气线程和氧气线程如何同步上,需要等到2个氢气线程都执行过releaseHydrogen,并且氧气线程也执行过releaseOxygen后,才能继续释放其他线程

这里需要注意一个问题,如果在氧气线程中解锁氢气线程的mutex时,会报unlock of unowned mutex,网上查出来问题是,标准库的thread不允许解锁其他线程的互斥变量,参考这里

试过改用Qt的线程库,也是一样的问题

所以得用多个变量来记录2个线程的状态

class H2O {
private:
	std::mutex h_;
	std::mutex o_;
	std::mutex h_count_mutex_;
	std::mutex pass_mutex_;
	int h_count_;
	bool pass_[2]; // 记录氢气和氧气各自是否已经完成
    // 已经完成后将状态置回的工作放到下一个线程中执行,比如先是氢气线程发现已经都完成了,则将此变量设置为true,让氧气线程来讲状态设置回来
	bool pass_do_; 
	enum e_fun {
		e_h = 0,
		e_o = 1,

		e_total,
	};
public:
	H2O() {
		h_count_ = 0;
		pass_[e_h] = false;
		pass_[e_o] = false;
		pass_do_ = false;
	}

	void hydrogen(std::function<void()> releaseHydrogen) {
		h_.lock();

		// releaseHydrogen() outputs "H". Do not change or remove this line.
		releaseHydrogen();
		++h_count_;

		if (h_count_ >= 2) {
			ready(e_h);
			h_count_ = 0;
		}

		h_.unlock();
	}

	void oxygen(std::function<void()> releaseOxygen) {
		o_.lock();
		// releaseOxygen() outputs "O". Do not change or remove this line.
		releaseOxygen();

		ready(e_o);

		o_.unlock();
	}

	// 氢气和氧气线程都完成各自的一组后,进入到这里,判断下是否完整的一组已经做完
	// 函数会阻塞,返回后表示已完成一组
	// 同一时刻只会有一个氢气线程和一个氧气线程进来
	void ready(int fun) {
		pass_mutex_.lock();

		// 这里是判断自己的线程
		while (1) {
			if (!pass_[fun]) {
				pass_[fun] = true;
				break;
			}
			else {
				// 前一组还未完成
				pass_mutex_.unlock();
				std::this_thread::sleep_for(std::chrono::microseconds(1));
				pass_mutex_.lock();
			}
		}

		// 再判断别的线程
		int other_fun = (fun + 1) % e_total;
		while (1) {
			if (pass_[other_fun]) {
				// 表示一组已完成
				if (!pass_do_) {
					pass_do_ = true; // 交给后一个线程来设置
				} else {
					// 假设是氢气线程到这里,此时氧气线程已经也已经退出了
					pass_do_ = false; // 改回来
					pass_[0] = false;
					pass_[1] = false;
				}
				break;
			}
			else {
				// 别的线程还未完成
				pass_mutex_.unlock();
				std::this_thread::sleep_for(std::chrono::microseconds(1));
				pass_mutex_.lock();
			}
		}

		pass_mutex_.unlock();
	}
};

执行时间最短的24ms,最多的有96ms,看来性能还有提升的空间
LeetCode---H2O生成问题_第1张图片

代码中等待的功能用的是循环加sleeplockunlock的形式,虽然sleep的时间已经非常短了,但如果可以直接用互斥变量锁住的话,可能效率会更高些

然后看到leetcode12ms的范例,就是用这种解锁其他线程的互斥变量,我也不知道该说啥了,是Linux的线程就支持可以解锁其他线程的互斥变量,还是说leetcode在测试的时候用的不是标准库的线程库呢,反正在win上是会报unlock of unowned mutex错误的,这里pthread_mutex_tLinux下的线程库

而且关键的关键,如果氢气线程在解锁氧气的mutex时,氧气线程并没有开始锁定mutex,那这次解锁,肯定就失败了吧,后续的也肯定是错误的了。

class H2O {
private:
    int countOxygen;
    pthread_mutex_t lockHy;
    pthread_mutex_t lockOx;
    
public:
    H2O() {
        pthread_mutex_init(&lockHy, NULL);
        pthread_mutex_init(&lockOx, NULL);
        pthread_mutex_lock(&lockOx);
        this->countOxygen = 2;
    }

    void hydrogen(function<void()> releaseHydrogen) {
        
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        pthread_mutex_lock(&lockHy);
        if(this->countOxygen > 0){
            releaseHydrogen();
            countOxygen--;
            if(this->countOxygen > 0){
                pthread_mutex_unlock(&lockHy);
            }else{
                pthread_mutex_unlock(&lockOx);
            }
        }
    }

    void oxygen(function<void()> releaseOxygen) {
        
        // releaseOxygen() outputs "O". Do not change or remove this line.
        pthread_mutex_lock(&lockOx);
        if(this->countOxygen == 0){
            releaseOxygen();
            this->countOxygen = 2;
            pthread_mutex_unlock(&lockHy);
        }
        
    }
};

你可能感兴趣的:(LeetCode)