C++ 多線程中有時間限制的等待

C++多線程中的一些等待函數允許設定超時。有兩類可指定的超時,一是基於時間段的超時等待,一般爲 _for 後綴的方法;或者絕對超時,等到一個時間點,一般爲 _until 後綴的方法。例如, std::condition_variable 就具有 wait_for() 和 wait_until() 成員函數。

1.時鐘

就C++標準庫所關注的而言,時鐘是時間信息的來源。具體來說,時鐘提供四個不同部分信息的類。

  • 現在(now)時間。調用該時鐘類的靜態成員函數now()獲取。
  • 用來表示從時鐘獲取到的時間值的類型。通過time_point成員的typedef來指定。
  • 時鐘的節拍週期。由分數秒指定,由period成員typedef給出,每秒走25拍就具有std::ratio<1,25>的period。
  • 時鐘是否以均勻的速率進行計時,決定其是否爲勻速(steady)時鐘。如果時鐘均勻速率計時且不能被調整,則稱爲勻速的,is_steady靜態數據成員爲true。

可以參照VC種steady_clock的實現:

struct system_clock
	{	// wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
	using rep = long long;

	using period = ratio_multiply<ratio<_XTIME_NSECS_PER_TICK, 1>, nano>; //【3】

	using duration = chrono::duration<rep, period>;
	using time_point = chrono::time_point<system_clock>; //【2】
	static constexpr bool is_steady = false; //【4】

	_NODISCARD static time_point now() noexcept //【1】
		{	// get current time
		return (time_point(duration(_Xtime_get_ticks())));
		}

	_NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept
		{	// convert to __time64_t
		return ((__time64_t)(_Time.time_since_epoch().count()
			/ _XTIME_TICKS_PER_TIME_T));
		}

	_NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept
		{	// convert from __time64_t
		return (time_point(duration(_Tm * _XTIME_TICKS_PER_TIME_T)));
		}
	};

時鐘的節拍週期是由分數表示的秒來指定的,他由時鐘的period成員typedef給出。如果每秒走25拍的時鐘,就具有std::ratio<1,25>的period。

如果時鐘以均勻速率計時且不能被調整,則該時鐘被成爲勻速(steady)時鐘,對應標準庫的std::chrono::steady_clock勻速時鐘。通常,std::chrono::system_clock系統時鐘是不勻速的,因爲時鐘可以調整,可能影響now()返回值等。此外,還有std::chrono::high_resolution_clock時鐘,一般是其他時鐘之一的typedef。

2.時間段

時間間隔由std::chrono::duration<>類模板進行處理。


模板參數爲計次數類型和計次週期,計次類型可以是int/double等,計次週期是一個編譯期有理數常量,表示從一個計次到下一個的秒數。

如分鐘 std::chrono::duration<short,std::ratio<60,1>>
如毫秒 std::chrono::duration<double,std::ratio<1,1000>>

標準庫預定義有基本的時間段有seconds/minutes/hours等。
在無需截斷的場合,時間段轉換是隱式的,反之不然。顯示轉換可以通過std::chrono::duration_cast<>實現。

	//毫秒轉秒會被截斷
	std::chrono::milliseconds ms(12345);
	std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(ms);
	std::cout << ms.count() << "\t" << s.count() << std::endl;

此外,時間段可以加減一個時間段,或者乘除一個常數。
基於時間段的等待使用類內部的勻速時鐘來衡量時間,因此等待35毫秒則意味着35毫秒的逝去時間,即便系統時鐘在等待期間進行了調整(向前或向後)。當然,系統調度的多變和OS時鐘的不同精度意味着線程之間發出調用並返回的實際時間可能遠不止35毫秒。

	//基於時間段的等待是通過std::chrono::duration<>的實例完成的
	std::future<void> future_wait = std::async([]() {
		std::cout << "future fu" << std::endl;
	});
	//    若等待超時則返回std::future_status::timeout
	//    就緒則ready,任務推遲則deferred
	if (future_wait.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) {
		std::cout << "wait for fu" << std::endl;
	}

3.時間點

時間點由std::chrono::time_point<>類模板的實例來表示。


第一個模板參數指定其參考的時鐘,並且以第二個模板參數指定計量單位(std::chrono::duration<>的特化)。

如分鐘 std::chrono::time_point<std::chrono::system_clock,std::chrono::minutes>

時間點的值是時間的長度(指定時間段的倍數),因而一個特定的時間點被稱爲時鐘的紀元(epoch)。時鐘的紀元是一項基本參數,但卻也不能直接查詢,也未被C++標準指定。雖然無法找出紀元的時間所在,但可以獲取給定的time_point的time_since_epoch(),該成員返回一個時間段的值,指定了從時鐘紀元到該時間點的時間長度。典型的紀元包括1970年1月1日00點00,以及該計算機啓動的瞬間。時鐘可以共享紀元或擁有獨立的紀元。

時間點可以用作計時和等待。基於時間點的等待,典型用例是在用作程序中的一個固定點的某個時鐘::now()開始的偏移量。

	//用時計時
	//可以使用auto
	std::chrono::high_resolution_clock::time_point
		start_point = std::chrono::high_resolution_clock::now();
	//do something
	std::chrono::time_point<std::chrono::high_resolution_clock, std::chrono::duration<long long, std::ratio<1, 1000000000>>>
		stop_point = std::chrono::high_resolution_clock::now(); //名字這麼長着都着不住啊
	std::cout << "start - stop = "
		<< std::chrono::duration_cast<std::chrono::nanoseconds>(stop_point - start_point).count()
		<< " nanoseconds" << std::endl;

	//基於時間點的等待
	auto const pt_timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
	std::condition_variable pt_cv; //只是爲了代碼展示放這裏
	bool pt_done = false;
	std::mutex pt_m;
	std::unique_lock<std::mutex> pt_lk(pt_m);
	while (!pt_done) {
		if (pt_cv.wait_until(pt_lk, pt_timeout) == std::cv_status::timeout) {
			std::cout << "wait done" << std::endl;
			break;
		}
	}

 4.接受超時的部分函數

(自《C++併發編程實戰》電子書,表格寬度有點超出範圍)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章