電動車電池大戰增溫,歐盟擬組國家隊拚後發先至

歐盟為趕上汽車電動化的大趨勢,擬開放成員國補助電池研究,並將提供數以億計的資金協助企業在歐洲興建大型電池廠。

電池是電動車量產的關鍵,但在電池供應方面,歐洲車廠極度仰賴亞洲供應商,歐盟對此感到憂心。據統計,亞洲佔全球現有與計畫中電池產能的八成左右,美國占 15%,歐洲則不到 4%。(金融時報)

有感於發展電動車必先建立自己的電池產能,歐盟於一年前推出促進電池產業發展計畫,現在已推出 5 種輔助資金,希望催生歐洲版的特斯拉超級電池工廠(Gigafactory)。據歐盟執委會能源部副總裁 Maros Sefcovic 表示,目前已有 4 個集團打算響應建廠,整個供應鏈則有 260 家企業參與計畫推展。

亞洲競爭者也聞風而至,LG Chem 目前正在波蘭打造大型鋰電池廠,三星 SDI 與 SK Innovation 等南韓同業也在匈牙利投資。

根據 EV Volumes 資料顯示,歐洲 2018 上半年電動車(含純電池動力與插電式混合動力車)銷售量年增 42% 來到 19.5 萬輛,全年預估上看 43 萬輛。

(本文內容由 授權使用。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

Gogoro 能源網路平台重大進展,與 YAMAHA、宏佳騰、PGO 三大機車廠合作

繼與中華郵政、YAMAHA 合作後,Gogoro 創辦人暨執行長陸學森 17 日再宣布與台灣 DHL 國際快遞,以及宏佳騰、PGO 兩大機車大廠展開合作。

開創物流綠時代,台灣 DHL 國際快遞與中華郵政響應 Gogoro

Gogoro 為因應廣大商用市場需求,在今年 8 月已推出 Gogoro 2 Utility 商務用車,正式跨足 B2B 市場。

Gogoro 今日宣佈與台灣 DHL 國際快遞合作,打造綠色物流車隊。此外,Gogoro 亦響應政府逐年淘汰燃油機車並增購電動機車的政策,宣布推出郵務車專案,打造符合台灣郵務士投遞特性的電動機車,參與中華郵政公開招標作業。

DHL 國際快遞台灣總經理黃湧君表示,創造永續環境與減低碳排放,是 DHL 最重要的使命。身為綠色物流標竿企業,DHL 在台灣積極推動各種環保解決方案與服務,這次將 Gogoro 電動機車加入台灣運務車隊,象徵朝 2050 年零碳排放目標又更進了一步。

繼 YAMAHA 後,再加入宏佳騰、PGO 兩大夥伴

宏佳騰與 PGO 將於 Gogoro 的電控系統、智慧電池、電池交換系統的基礎上,研發、生產以及銷售電動機車,其中宏佳騰預計在 2019 年夏季推出第一款電動機車,PGO 則計畫 2019 年下半年推出。

宏佳騰執行長林東閔表示,宏佳騰從 1998 年創立至今已 20 週年,堅持用技術與速度為顧客創造最大的價值。電動機車是未來趨勢,在 2016 年 2 月就與 Gogoro 接觸,討論雙方合作的可能,期望打造擁有宏佳騰的靈魂、Gogoro 的能源之車款。其中為了解決消費者最在乎的換電問題,宏佳騰所推出的新款電動機車也採用 Gogoro 的智慧電池與 GoStation 換電站,讓車主便於更換電池。

PGO 創研中心協理李榮宜則指出,與顧客有更強情感層面連結的騎乘樂趣,是 PGO 相信的品牌價值,選擇 Gogoro 能源網路的電池交換系統,與其品牌精神不謀而合。透過便利的電池交換系統,PGO 希望打造最具騎乘樂趣的電動機車,提供消費者輕鬆、時尚的電動機車新選項。

經濟部長沈榮津今日也播空出席 Gogoro 記者會為其站台,他表示政府自 2006 年推動電動車產業發展,至今已有 15 萬輛的成績,尤其在 Gogoro 加入後首年即達到 2 萬輛,今年更有 4 萬輛的成績。他分析由於 Gogoro 的動力性能足夠,符合台灣車主的行為模式與感受,加上創新的營運模式,使得 Gogoro 能有現在的表現。

(合作媒體:。圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

光陽聯手印度電動車新創 22Motors,挺進世界最大機車市場

光陽(Kymco)日前宣布要進軍印度市場,現在揭曉將聯手印度電動車新創 22Motors。未來 22Motors 的 Flow scooter 車款將全面採用 Kymco Ionex 車能網,成為光陽插旗印度的夥伴。

2018 年以來電動機車市場就異常熱鬧,機車大廠光陽與 Gogoro 之間正面交鋒的態勢已經日漸明朗。雙方也各自找來友軍助陣,Gogoro 選擇和國產機車三雄之一的 Yamaha 結盟,光陽則在中國與阿里巴巴合作,如今又攜手印度的 22Motors。光陽這次與 22Motors 的合作也是著眼於印度政府在 2030 年前銷售新燃油車的調控政策,試圖在全球最大機車市場電動化的過程中搶得一席之地。

光陽指出印度新創電動車公司 22Motors 具有優異的技術能力,不僅解決了鋰離子電池開發和電池管理系統(BMS)等問題,還擁有高效率的快速充電系統,並開發 AI 和機器學習功能。光陽表示,在 3 月的 Ionex 東京發表會之後,22Motors 便前來積極接洽雙方的合作,打造一台會思考且有學習能力的 AI Scooter 智慧電動車。能夠跨足世界最大的機車市場無疑對光陽是一劑強心針,未來兩大陣營將會如何交手值得繼續觀察。

(合作媒體:。首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

2019 年台灣電動機車市佔率將達 10%

根據 TrendForce 綠能研究(EnergyTrend)最新調查顯示,隨著國際油價回漲,加上動力電池價格快速下滑,台灣電動機車市場加速成長,2018 年台灣電動機車總量預估為 7.8 萬輛,市佔率達 8%,2019 年市佔率將成長至10%,其中,重型電動機車的比重也將從 85% 提高至 90%。

EnergyTrend 資深研究經理呂理舜指出,台灣汽油機車市場已飽和,近年來由於電動汽車已逐漸成為趨勢,政府也積極推動相關機車電動化政策,再加上共享單車帶起的綠色運輸熱潮,皆推升消費者購買電動機車的意願。此外,目前台灣政府為了推行全面性導入再生能源的計畫,積極建置周邊充電基礎建設,各地方政府也針對購買整車與換電式服務提供補貼。

至於鋰電池價格走勢,由於中國廠商加入新能源車電池製造的行列,近幾年電池材料的生產也逐漸以中國為主,都讓電池系統價格快速下滑,車用電池系統價格從 2015 年的 550 USD / kWh 下滑至 2017 年的 350 USD / kWh,2018 年則將降至 250 USD / kWh,加速各類電池應用的普及。

根據 EnergyTrend 的調查,2018 年電動機車總量預估將達 7.8 萬輛,約占整體機車市場的 8%。從統計資料也可發現,過往台灣的電動機車主要以輕型為主(輕型電動機車為低於 5 馬力的車款,重型為 5 馬力以上),但隨著 2016 年睿能(Gogoro)的興起,逐漸推升重型電動機車的需求,再加上 2017 年的平價車款推出(高階價格約台幣 6 萬元起跳,平價版則低於台幣 4 萬元),讓重型電動機車與汽油機車的價差縮小,目前預估 2018 年整體電動機車市場中,重型機車的佔比將超過 85%。隨著睿能、光陽、三陽等主要品牌陸續投入重型電動機車領域,預期 2019 年重型電動機車將達 9 萬輛,佔比約 90%。

(首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分佈式事務

分佈式事務

分佈式環境下的事務

要了解分佈式事務,首先要了解分佈式環境

分佈式

如一網站,訪問一個服務A(查詢自己用戶信息), 提供服務A的服務器分別有A1(上海)A2(廣州) A3(新加坡)

同一個服務分佈在三個區域的服務器上,這就是分佈式。你可以訪問 上海的服務器,廣州的或者新加坡的,但是

三個服務器之前通信是有延遲的,所以數據同步需要一定時間

分佈式中的問題

舉例一個分佈式場景

如果用戶Y個人信息 名字為 “南柯一夢” Y改為 “南柯夢”, 同一時間,Y用戶好友查看Y的名字,好友查詢的結果是”南柯一夢” 還是 “南柯夢” 這是分佈式系統常見的問題(數據修改發生在上海,訪問發生在新加坡)。

CAP原則

CAP原則又稱CAP定理,指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多只能同時實現兩點,不可能三者兼顧。

參考文章
An Illustrated Proof of the CAP Theorem
CAP 定理的含義

以為網絡有延遲和不可測故障,因此分佈式系統是保持服務穩定的常用手段,但是分佈式因為服務機器分佈在不同地點,因此也會有分佈式的特點問題。

分佈式中 一致性 可用性 容災性 是三個指標

Consistency

數據一致性,分佈式環境下,不同地點的服務器,數據庫數據同步一致。

Availability

服務可用性,分佈式環境下,調用服務,都可用

Partition tolerance

分區容錯性,容災能力

分佈式環境多台服務器運行,其中一部分機器故障了,整個系統仍然可以正常運行提供服務

CAP不能同時滿足

必須滿足P

首先分佈式環境,系統需要穩定運行,一台服務器意外斷電,不應該影響系統整體功能正常,另一台或多台服務器還能穩定提供服務,所以分區容錯是必須要滿足。

滿足C

數據一致性,所指的是同一個服務所在不同服務器的數據是同步的。如上改名字的場景 南柯一夢 改為 南柯夢 (在上海的數據庫被修改) 那麼系統要做到滿足數據一致性,必須馬上同步廣州和新加坡的數據庫,這樣才能滿足廣州或者新加坡的訪問者獲得的結果也一致是 “南柯夢” 而不是”南柯一夢”

滿足A

服務可用性,指任何時候訪問服務,都返回結果

A與C是衝突的,上海服務器南柯一夢改為南柯夢后,為了服務可用,此時間訪問新加坡和廣州的服務器,返回的結果應該是南柯一夢(任何時候服務都返回結果) 但是嚴格上講,數據是錯誤的,因為用戶已經改了名字,改為南柯夢,但是數據在上海的才是正確的。

滿足數據一致性必須犧牲服務可用性 或者相反

要達到數據一致性的要求,必須在上海服務器修改數據的同時,同步廣州和新加坡的數據庫,並且在數據同步完成之前,訪問廣州和新加坡的數據庫中這條數據需要等待,返回同步后的結果(一致性)。

失去了服務可用性(這裏服務是等待數據同步完成才返回結果,而不是立刻返回)

因此CAP 要麼 滿足AP (分區服務可用)要麼 CP (分區數據一致)

分佈式中事務

商品購買中的事務

以商品購買生成訂單為例子

網絡上用戶A 購買 一雙鞋子 價格50 付款後生成消費訂單

事務中包含子的服務

這裏簡單設為三個服務,他們是事務相關的

1.商品信息服務

提供商品信息等服務

鞋子 顏色 價格 庫存數量等信息 這裏設 價格price為 50 庫存數 num 9

2.商家賬號收款服務

提供金額收入信息等服務

用戶購買鞋子,需要付款50元到商家賬號

3.用戶消費訂單服務

提供購買消費憑證信息等服務

首先分析用戶購買鞋子,三個服務分別要做什麼

@1 鞋子庫存減1

@2 商家賬號金額增加50

@3 生成 用戶購買鞋子的訂單記錄, 包括數量金額等信息

事務特性

原子性

@1 @2 @3 要麼同時發生,要麼都不發生

一致性

鞋子庫存減少1,收入增加50

隔離性

鞋子庫存減1,後續用戶最多只能購買(9-1=8)雙鞋子

持久性

動作執行成功后,訂單生效,收入新增50生效,庫存減1生效

上述三個服務他們可以在不同的地點,不同機器上部署的,並很常見。

保證數據正確

開啟事務

確定要執行的服務,每個服務的數據庫事務開啟

執行業務

調用庫存減1,轉賬,生成訂單等子服務

提交

業務執行過程中沒有意外,各子服務的數據庫提交事務,生效數據修改

回退

回退,如果服務調用出現了差錯,或者某個子服務執行失敗,可以通過回滾所有數據庫達到數據正確。

補償

某些情況下,某個子服務執行失敗,但是不影響整體業務,也可以提交事務,後續補償機制將失敗的子服務重新執行。

補償機制

個人認為就商品購買而言,補償機制多數情況可以使用且實用。(對強一致要求沒那麼高的情況下)

@1 庫存減1

@2 收入增加50

@ 3生成訂單記錄

如果這次執行的動作, 只有@3失敗,@1 @2成功 說明金額交易,商品庫存業務都沒問題,只是訂單記錄失敗,這是可以提交事務的,訂單錯誤可以生成一條記錄(攜帶商品,金額等信息),發送到MQ消息隊列(或者其他設計)通過消息隊列通知訂單相關服務,補償重新執行生成訂單,達到最終一致性。

分佈式事務控制問題

不同服務在不同區運行

不管是從安全性,穩定性,還是服務粒度細化方便維護等多因素考慮,都是很有必要讓不同的服務分開在不同服務區運行。

單體數據庫的事務不被支持,購買商品到生成訂單所有操作加起來算一個事務,涉及的數據在不同一服務(不同的數據庫),並且同一個服務可能運行在多台服務器上。

數據庫開啟事務針對的是單台服務器,多個服務多個數據庫,並不支持數據庫的事務,需要額外設計處理數據一致性問題(或者最終一致性)

同一個服務運行在多個區

不同服務不在一個服務器,同樣的,分佈式為穩定性可用而生,因此,一個服務大多有在多個區的服務器上運行,開啟事務的時候,如何保證事務開啟提交等事務相關命令每次發送到同一個區的同一個服務器,也是一定要考慮的問題。

分佈式事務處理方式

如上所述分佈式服務代表多個數據庫,不支持數據庫的事務,

如何保證事務中涉及的數據庫數據修改都提交生效或者都回滾。

建立控制中心

控制中心在執行業務時,統一發送開始事務的命令給三個服務,返回狀態

狀態沒問題執行數據修改,

都沒問題就發送給三個服務,提交事務,否在回滾事務

消息機制事務

MQ消息隊列,達到控制事務正確目的,項目中kafka聽的比較多,可在高併發環境下穩定運行,可以通過消息機制發送事務處理結果到子服務,子服務收到消息,通過分析消息內容,做出對應的操作,達到事務一致性或者最終一致性等目的
思考圖:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

JSR133提案-修復Java內存模型

目錄

  • 1. 什麼是內存模型?
  • 2. JSR 133是關於什麼的?
  • 3. 再談指令重排序
  • 4.同步都做了什麼?
  • 5. final字段在舊的內存模型中為什麼可以改變?
  • 6.“初始化安全”與final字段?
  • 7. 增強volatile語義
  • 8. 修復“double-checked locking”的問題
  • 9. 為什麼要關心這些問題?
  • 延伸閱讀

1. 什麼是內存模型?

在多處理器系統中,為了提高訪問數據的速度,通常會增加一層或多層高速緩存(越靠近處理器的緩存速度越快)。
但是緩存同時也帶來了許多新的挑戰。比如,當兩個處理器同時讀取同一個內存位置時,看到的結果可能會不一樣?
在處理器維度上,內存模型定義了一些規則來保證當前處理器可以立即看到其他處理器的寫入,以及當前處理器的寫入對其他處理器立即可見。這些規則被稱為緩存一致性協議
有些多處理器架構實現了強一致性,所有的處理器在同一時刻看到的同一內存位置的值是一樣的。
而其他處理器實現的則是較弱的一致性,需要使用被稱為內存屏障的特殊機器指令使來實現最終一致性(通過刷新緩存或使緩存失效)。
這些內存屏障通常在釋放鎖和獲取鎖時被執行;對於高級語言(如Java)的程序員來說,它們是不可見的。

在強一致性的處理器上,由於減少了對內存屏障的依賴,編寫併發程序會更容易一些。
但是,相反的,近年來處理器設計的趨勢是使用較弱的內存模型,因為放寬對緩存一致性的要求可以使得多處理器系統有更好的伸縮性和更大的內存。

此外,編譯器、緩存或運行時還被允許通過指令重排序改變內存的操作順序(相對於程序所表現的順序)。
例如,編譯器可能會往後移動一個寫入操作,只要移動操作不改變程序的原本語義(as-if-serial語義),就可以自由進行更改。
再比如,緩存可能會推遲把數據刷回到主內存中,直到它認為時機合適了。
這種靈活的設計,目的都是為了獲得得最佳的性能,
但是在多線程環境下,指令重排會使得跨線程可見性的問題變的更複雜。

為了方便理解,我們來看個代碼示例:

Class Reordering {
  int x = 0, y = 0;
  //thread A
   public void writer() {
        x = 1;
        y = 2;
    }

    //thread B
    public void reader() {
        int r1 = y;
        int r2 = x;
     }
}

假設這段代碼被兩個線程併發執行,線程A執行writer(),線程B執行reader()。
如果線程B在reader()中看到了y=2,那麼直覺上我們會認為它看到的x肯定是1,因為在writer()中x=1y=2之前 。
然而,發生重排序時y=2會早於x=1執行,此時,實際的執行順序會是這樣的:

y=2;
int r1=y;
int r2=x;
x=1;

結果就是,r1的值是2,r2的值是0。
從線程A的角度看,x=1與y=2哪個先執行結果是一樣的(或者說沒有違反as-if-serial語義),但是在多線程環境下,這種重排序會產生混亂的結果。

我們可以看到,高速緩存指令重排序提高了效率的同時也引出了新的問題,這顯然使得編寫併發程序變得更加困難。
Java內存模型就是為了解決這類問題,它對多線程之間如何通過內存進行交互做了明確的說明。
更具體點,Java內存模型描述了程序中的變量與實際計算機的存儲設備(包括內存、緩存、寄存器)之間交互的底層細節。
例如,Java提供了volatile、final和 synchronized等工具,用於幫助程序員向編譯器表明對併發程序的要求。
更重要的是,Java內存模型保證這些同步工具可以正確的運行在任何處理器架構上,使Java併發應用做到“Write Once, Run Anywhere”。

相比之下,大多數其他語言(例如C/C++)都沒有提供显示的內存模型。
C程序繼承了處理器的內存模型,這意味着,C語言的併發程序在一個處理器架構中可以正確運行,在另外一個架構中則不一定。

2. JSR 133是關於什麼的?

Java提供的跨平台內存模型是一個雄心勃勃的計劃,在當時是具有開創性的。
但不幸的是,定義一個即直觀又一致的內存模型比預期的要困難得多。
自1997年以來,在《Java語言規範》的第17章關於Java內存模型的定義中發現了一些嚴重的缺陷。
這些缺陷使一些同步工具產生混亂的結果,例如final字段可以被更改。
JSR 133為Java語言定義了一個新的內存模型,修復了舊版內存模型的缺陷(修改了final和volatile的語義)
JSR的主要目標包括不限於這些:

  1. 正確同步的語義應該更直觀更簡單。
  2. 應該定義不完整或不正確同步的語義,以最小化潛在的安全隱患
  3. 程序員應該有足夠的自信推斷出多線程程序如何與內存交互的。
  4. 提供一個新的初始化安全性保證(initialization safety)。
    如果一個對象被正確初始化了(初始化期間,對象的引用沒有逃逸,比如構造函數里把this賦值給變量),那麼所有可以看到該對象引用的線程,都可以看到在構造函數中被賦值的final變量。這不需要使用synchronized或volatile。

3. 再談指令重排序

在許多情況下,出於優化執行效率的目的,數據(實例變量、靜態字段、數組元素等)可以在寄存器、緩存和內存之間以不同於程序中聲明的順序被移動。
例如,線程先寫入字段a,再寫入字段b,並且b的值不依賴a,那麼編譯器就可以自由的對這些操作重新排序,在寫入a之前把b的寫入刷回到內存。
除了編譯器,重排序還可能發生在JIT、緩存、處理器上。
無論發生在哪裡,重排序都必須遵循as-if-serial語義,這意味着在單線程程序中,程序不會覺察到重排序的存在,或者說給單線程程序一種沒有發生過重排序的錯覺。
但是,重排序在沒有同步的多線程程序中會產生影響。在這種程序中,一個線程能夠觀察到其他線程的運行情況,並且可能檢測到變量訪問順序與代碼中指定的順序不一致。
大多數情況下,一個線程不會在乎另一個線程在做什麼,但是,如果有,就是同步的用武之地。

4.同步都做了什麼?

同步有很多面,最為程序員熟知的是它的互斥性,同一時刻只能有一個線程持有monitor。
但是,同步不僅僅是互斥性。同步還能保證一個線程在同步塊中的寫內存操作對其他持有相同monitor的線程立即可見。
當線程退出同步塊時(釋放monitor),會把緩存中的數據刷回到主內存,使主內存中保持最新的數據。
當線程進入同步塊時(獲取monitor),會使本地處理器緩存失效,使得變量必須從主內存中重新加載。
我們可以看到,之前的所有寫操作對後來的線程都是可見的。

5. final字段在舊的內存模型中為什麼可以改變?

證明final字段可以改變的最佳示例是String類的實現(JDK 1.4版本)。
String對象包含三個字段:一個字符串數組的引用value、一個記錄數組中開始位置的offset、字符串長度length。
通過這種方式,可以實現多個String/StringBuffer對象共享一個相同的字符串數組,從而避免為每個對象分配額外的空間。
例如,String.substring()通過與原String對象共享一個數組來產生一個新的對象,唯一的不同是length和offset字段。

String s1 = "/usr/tmp";
String s2 = s1.substring(4); 

s2和s1共享一個字符串數組”/usr/tmp”,不同的是s2的offset=4,length=4,s1的offset=0,length=8。
在String的構造函數運行之前,根類Object的構造函數會先初始化所有字段為默認值,包括final的length和offset字段。
當String的構造函數運行時,再把length和offset賦值為期望的值。
但是這一過程,在舊的內存模型中,如果沒有使用同步,另一個線程可能會看到offset的默認值0,然後在看到正確的值4.
結果導致一個迷幻的現象,開始看到字符串s2的內容是’/usr’,然後再看到’/tmp’。
這不符合我們對final語義的認識,但是在舊內存模型中確實存在這樣的問題。
(JDK7開始,改變了substring的實現方式,每次都會創建一個新的對象)

6.“初始化安全”與final字段?

新的內存模型提供一個新初始化安全( initialization safety)保障。
意味着,只要一個對象被正確的構造,那麼所有的線程都會看到這些在構造函數中被賦值的final字段。
“正確”的構造是指在構造函數執行期間,對象的引用沒有發生逃逸。或者說,在構造函數中沒有把該對象的引用賦值給任何變量。

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

示例中,初始化安全保證執行reader()方法的線程看到的f.x=3,因為它是final字段,但是不保證能看到y=4,因為它不是final的。
但是如果構造函數像這樣:

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  global.obj = this;  //  allowing this to escape
}

初始化安全不能保證讀取global.obj的線程看到的x的值是3,因為對象引用this發生了逃逸。

不僅如此,任何通過final字段(構造函數中被賦值的)可以觸達的變量都可以保證對其他線程可見。
這意味着如果一個final字段包含一個引用,例如ArrayList,除了該字段的引用對其他線程可見,ArrayList中的元素對其他線程也是可見的。

初始化安全增強了final的語義,使其更符合我們對final的直觀感受,任何情況下都不會改變。

7. 增強volatile語義

volatile變量是用於線程之間傳遞狀態的特殊變量,這要求任何線程看到的都是volatile變量的最新值。
為實現可見性,禁止在寄存器中分配它們,還必須確保修改volatile后,要把最新值從緩存刷到內存中。
類似的,在讀取volatile變量之前,必須使高速緩存失效,這樣其他線程會直接讀取主內存中的數據。
在舊的內存模型中,多個volatile變量之間不能互相重排序,但是它們被允許可以與非volatile變量一起重排序,這消弱了volatile作為線程間交流信號的作用。
我們來看個示例:

Map configs;
volatile boolean initialized = false;
. . .
 
// In thread A
configs  =  readConfigFile(fileName);
processConfigOptions( configs);
initialized = true;
. . .
 
// In thread B
while (initialized) {
    // use configs
}

示例中,線程A負責配置數據初始化工作,初始化完成后線程B開始執行。
實際上,volatile變量initialized扮演者守衛者的角色,它表示前置工作已經完成,依賴這些數據的其他線程可以執行了。
但是,當volatile變量與非volatile變量被編譯器放到一起重新排序時,“守衛者”就形同虛設了。
重排序發生時,可能會使readConfigFile()中某個動作在initialized = true之後執行,
那麼,線程B在看到initialized的值為true后,在使用configs對象時,會讀取到沒有被正確初始化的數據。
這是volatile很典型的應用場景,但是在舊的內存模型中卻不能正確的工作。

JSR 133專家組決定在新的內存模型中,不再允許volatile變量與其他任務內存操作一起重排序
這意味着,volatile變量之前的內存操作不會在其後執行,volatile變量之後的內存操作不會在其前執行。
volatile變量相當於一個屏障,重排序不能越過對volatile的內存操作。(實際上,jvm確實使用了內存屏障指令)
增強volatile語義的副作用也很明顯,禁止重排序會有一定的性能損失。

8. 修復“double-checked locking”的問題

double-checked locking是單例模式的其中一種實現,它支持懶加載且是線程安全的。
大概長這個樣子:

private static Something instance = null;

public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();//
    }
  }
  return instance;
}

它通過兩次檢查巧妙的避開了在公共代碼路徑上使用同步,從而避免了同步所帶來的性能開銷。
它唯一的問題就是——不起作用。為什麼呢?
instance的賦值操作會與SomeThing()構造函數中的變量初始化一起被編譯器或緩存重排序,這可能會導致把未完全初始化的對象引用賦值給instance。
現在很多人知道把instance聲明為volatile可以修復這個問題,但是在舊的內存模型(JDK 1.5之前)中並不可行,原因前面有提到,volatile可以與非volatile字段一起重排序。

儘管,新的內存模型修復了double-checked locking的問題,但仍不鼓勵這種實現方式,因為volatile並不是免費的。
相比之下,Initialization On Demand Holder Class更值得被推薦,
它不僅實現了懶加載和線程安全,還提供了更好的性能和更清晰的代碼邏輯。大概長這個樣子:

public class Something {
    private Something() {}
    //static innner class
    private static class LazyHolder {
        static final Something INSTANCE = new Something(); //static  field
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

這種實現完全沒有使用同步工具,而是利用了Java語言規範的兩個基本原則,
其一,JVM保證靜態變量的初始化對所有使用該類的線程立即可見;
其二,內部類首次被使用時才會觸發類的初始化,這實現了懶加載。

9. 為什麼要關心這些問題?

併發問題一般不會在測試環境出現,生成環境的併發問題又不容易復現,這兩個特點使得併發問題通常比較棘手。
所以你最好提前花點時間學習併發知識,以確保寫出正確的併發程序。我知道這很困難,但是應該比排查生產環境的併發問題容易的多。

延伸閱讀

1.JSR 133 (Java Memory Model) FAQ,2004
2.volatile關鍵字
3.Double-checked問題
4.內存屏障和volatile語義
5.修復Java內存模型
6.String substring 在jdk7中會創建新的數組
7.Memory Ordering
8.有MESI協議為什麼還需要volatile?
9.Initialization On Demand Holder Class
10.The JSR-133 Cookbook for Compiler Writers

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

一起玩轉微服務(3)——微服務架構設計模式

一、聚合器微服務設計模式

這是一種最常見也最簡單的設計模式,效果如下圖所示。
聚合器調用多個服務實現應用程序所需的功能。它可以是一個簡單的Web頁面,將檢索到的數據進行處理展示。它也可以是一個更高層次的組合微服務,對檢索到的數據增加業務邏輯後進一步發布成一個新的微服務,這符合DRY原則。另外,每個服務都有自己的緩存和數據庫。如果聚合器是一個組合服務,那麼它也有自己的緩存和數據庫。

二、代理微服務設計模式

這是聚合模式的一個變種,如下圖所示。
在這種情況下,客戶端並不聚合數據,但會根據業務需求的差別調用不同的微服務。代理可以僅僅委派請求,也可以進行數據轉換工作。每個微服務都有自己獨立的緩存和數據庫系統,彼此獨立。

三、鏈式微服務設計模式

這種模式在接收到請求後會產生一個經過合併的響應,如下圖所示:
在這種情況下,服務A接收到請求後會與服務B進行通信,類似地,服務B會同服務C進行通信。所有服務都使用同步消息傳遞。在整個鏈式調用完成之前,客戶端會一直阻塞。
因此,服務調用鏈不宜過長,以免客戶端長時間等待。

四、分支微服務設計模式

這種模式是聚合器模式的擴展,允許同時調用兩個微服務鏈,如下圖所示:
每個調用鏈分別調用自己的服務。當某個調用出現問題時,互相之間不會造成影響。

五、數據共享微服務設計模式

自治是微服務的設計原則之一,就是說微服務是全棧式服務。但在重構現有的“單體應用(monolithic application)”時,SQL數據庫反規範化可能會導致數據重複和不一致。
因此,在單體應用到微服務架構的過渡階段,可以使用這種設計模式,如下圖所示:
在這種情況下,部分微服務可能會共享緩存和數據庫存儲。不過,這隻有在兩個服務之間存在強耦合關係時才可以。對於基於微服務的新建應用程序而言,這是一種反模式。
 

六、異步消息傳遞微服務設計模式

雖然REST設計模式非常流行,但它是同步的,會造成阻塞。因此部分基於微服務的架構可能會選擇使用消息隊列代替REST請求/響應,如下圖所示:
各個服務之間通過異步的消息隊列進行交互,當服務出現問題時,不會造成阻塞,隊列會幫助緩存消息,直到消費服務開始工作。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

JPS/JPS+ 尋路算法

目錄

  • 概念
    • 強迫鄰居(Forced Neighbour)
    • 跳點(Jump Point)
  • JPS 尋路算法(Jump Point Search)
    • 實現原理
    • 示例過程
  • JPS+(Jump Point Search Plus)
    • 預處理
    • 示例過程
  • 總結
  • 參考

概念

JPS(jump point search)算法實際上是對A* 尋路算法的一個改進,因此在閱讀本文之前需要先了解A*算法。A* 算法在擴展節點時會把節點所有鄰居都考慮進去,這樣openlist中點的數量會很多,搜索效率較慢。

若不了解A*算法,可以參考博主以前寫的一篇文章 A* 尋路算法 – KillerAery – 博客園

例如在無遮擋情況下(往往會有多條等價路徑),而我們希望起點到終點實際只取其中一條路徑,而該路徑外其它節點可以沒必要放入openlist(不希望加入沒必要的鄰居)。

其次我們還希望直線方向上中途的點不用放入openlist,如果只放入每段直線子路徑的起點和終點,那openlist又可以少放很多沒必要的節點:

可以看到 JPS 算法搜到的節點總是“跳躍性”的,這是因為這些關鍵性的節點都是需要改變行走方向的拐點,因此這也是 Jump Point 命名的來歷。

在介紹JPS等算法具體實現前,我們必須先掌握下面的概念。

強迫鄰居(Forced Neighbour)

強迫鄰居:節點 x 的8個鄰居中有障礙,且 x 的父節點 p 經過x 到達 n 的距離代價比不經過 x 到達的 n 的任意路徑的距離代價小,則稱 n 是 x 的強迫鄰居。

看定義也許十分晦澀難懂。直觀來說,實際就是因為前進方向(父節點到 x 節點的方向為前進方向)的某一邊的靠後位置有障礙物,因此想要到該邊靠前的空位有最短的路徑,就必須得經過過 x 節點。

可能的情況見圖示,黑色為障礙,紅圈即為強迫鄰居:

(左圖為直線方向情況下的強迫鄰居,右圖為斜方向情況下的強迫鄰居)

跳點(Jump Point)

跳點:當前點 x 滿足以下三個條件之一:

  • 節點 x 是起點/終點。
  • 節點 x 至少有一個強迫鄰居。
  • 如果父節點在斜方向(意味着這是斜向搜索),節點x的水平或垂直方向上有滿足條件a,b的點。

節點y的水平或垂直方向是斜向向量的拆解,比如向量d=(1,1),那麼水平方向則是(1,0),並不會往左搜索,只會看右邊,如果向量d=(-1,-1),那麼水平方向是(-1,0),只會搜索左邊,不看右邊,其他同理。

下圖舉個例子,由於黃色節點的父節點是在斜方向,其對應分解成向上和向右兩個方向,因為在右方向發現一個藍色跳點,因此黃色節點也應被判斷為跳點:

JPS 尋路算法(Jump Point Search)

實現原理

JPS 算法和A* 算法非常相似,步驟大概如下:

  1. openlist取一個權值最低的節點,然後開始搜索。(這些和A*是一樣的)
  2. 搜索時,先進行 直線搜索(4/8個方向,跳躍搜索),然後再 斜向搜索(4個方向,只搜索一步)。如果期間某個方向搜索到跳點或者碰到障礙(或邊界),則當前方向完成搜索,若有搜到跳點就添加進openlist。

跳躍搜索是指沿直線方向一直搜下去(可能會搜到很多格),直到搜到跳點或者障礙(邊界)。一開始從起點搜索,會有4個直線方向(上下左右),要是4個斜方向都前進了一步,此時直線方向會有8個。

  1. 若斜方向沒完成搜索,則斜方向前進一步,重複上述過程。

因為直線方向是跳躍式搜索,所以總是能完成搜索。

  1. 若所有方向已完成搜索,則認為當前節點搜索完畢,將當前節點移除於openlist,加入closelist。
  2. 重複取openlist權值最低節點搜索,直到openlist為空或者找到終點。

下面結合圖片更好說明過程2和3:首先我們從openlist取出綠色的節點,作為搜索的開始,先進行直線搜索,再斜向搜索,沒有找到任何跳點。

斜方向前進一步后,重複直線搜索和斜向搜索過程,仍沒發現跳點。

斜方向前進兩步后,重複直線搜索和斜向搜索過程,仍沒發現跳點。

斜方向前進了三步后(假設當前位置為 x),在水平直線搜索上發現了一個跳點(紫色節點為強迫鄰居)。

於是 x 也被判斷為跳點,添加進openlist。斜方向結束,綠色節點的搜索過程也就此結束,被移除於openlist,放入closelist。

示例過程

下面展示JPS算法更加完整的過程:
假設起點為綠色節點,終點為紅色節點

重複直線搜索和斜向搜索過程,斜方向前進了3步。在第3步判斷出黃色節點為跳點(依據是水平方向有其它跳點),將黃色跳點放入openlist,然後斜方向搜索完成,綠色節點移除於openlist,放入closelist。

對openlist下一個權值最低的節點(即黃色節點)開啟搜索,在直線方向上發現了藍色節點為跳點(依據是紫色節點為強迫鄰居),類似地,放入openlist。

由於斜方向還沒結束,繼續前進一步。最後一次直線搜索和斜向搜索都碰到了邊界,因此黃色節點搜索完成,移除於openlist,放入closelist。

對openlist下一個權值最低的節點(原為藍色節點,下圖變為黃色節點)開啟搜索,直線搜索碰到邊界,斜向搜索無果。斜方繼續前進一步,仍然直線搜索碰到邊界,斜向搜索無果。

由於斜方向還沒結束,繼續前進一步。

最終在直線方向上發現了紅色節點為跳點,因此藍色節點先被判斷為跳點,只添加藍色節點進openlist。斜方向完成,黃色節點搜索完成。

最後openlist取出的藍色節點開啟搜索,在水平方向上發現紅色節點,判斷為終點,算法完成。

回憶起跳點的第三個判斷條件(如果父節點在斜方向,節點x的水平或垂直方向上有滿足條件a,b的點),會發現這個條件判斷是最複雜的。在尋路過程中,它使尋路多次在水平節點上搜到跳點,也只能先添加它本身。其次,這也是算法中需要使用到遞歸的地方,是JPS算法性能瓶頸所在。

JPS+(Jump Point Search Plus)

JPS+ 本質上也是 JPS尋路,只是加上了預處理來改進,從而使尋路更加快速。

預處理

我們首先對地圖每個節點進行跳點判斷,找出所有主要跳點:

然後對每個節點進行跳點的直線可達性判斷,並記錄好跳點直線可達性:

若可達還需記錄號跳點直線距離:

類似地,我們對每個節點進行跳點斜向距離的記錄:

剩餘各個方向如果不可到達跳點的數據記為0或負數距離。如果在對應的方向上移動1步后碰到障礙(或邊界)則記為0,如果移動n+1步後會碰到障礙(或邊界)的數據記為負數距離-n

最後每個節點的8個方向都記錄完畢,我們便完成了JPS+的預處理過程:

以上預處理過程需要有一個數據結構存儲地圖上每個格子8個方向距離碰撞或跳點的距離。

示例過程

做好了地圖的預處理之後,我們就可以使用JPS+算法了。大致思路與JPS算法相同,不過這次有了預處理的數據,我們可以更快的進行直線搜索斜向搜索

在某個搜索方向上有:

  • 對於正數距離 n(意味着距離跳點 n 格),我們可以直接將n步遠的節點作為跳點添加進openlist
  • 對於0距離(意味着一步都不可移動),我們無需在該方向搜索;
  • 對於負數距離 -n(意味着距離邊界或障礙 n 格),我們直接將n步遠的節點進行一次跳點判斷(有可能滿足跳點的第三條件,不過得益於預處理的數據,這步也可以很快完成)。

如下圖示,起始節點通過已記錄的向上距離,直接將3步遠的跳點添加進openlist,而不再像以前需要迭代三步(還每步都要判斷是否跳點):

其它過程也是類似的:

總結

可以看到 JPS/JPS+ 算法里只有跳點才會被加入openlist里,排除了大量不必要的點,最後找出來的最短路徑也是由跳點組成。這也是 JPS/JPS+ 高效的主要原因。

JPS

  • 絕大部分地圖,使用 JPS 算法都會比 A* 算法更快,內存佔用也更小(openlist里節點少了很多)。
  • JPS 在跳點判斷上,要盡可能避免遞歸的深度過大(或者期待一下以後出現避免遞歸的算法),否則在超大型的地圖裡遞歸判斷跳點可能會造成災難。
  • JPS 也可以用於動態變化的地圖,只是每次地圖改變都需要再進行一次 JPS 搜索。
  • JPS 天生擁有合併節點(亦或者說是在一條直線里移除中間不必要節點)的功能,但是仍存在一些可以繼續合併的地方。
  • JPS 只適用於 網格(grid)節點類型,不支持 Navmesh 或者路徑點(Way Point)。

JPS+

  • JPS+ 相比 JPS 算法又是更快上一個檔次(特別是避免了過多層遞歸判斷跳點),內存佔用則是每個格子需要額外記錄8個方向的距離數據。
  • JPS+ 算法由於包含預處理過程,這讓它面對動態變化的地圖有天生的劣勢(幾乎是不可以接受動態地圖的),因此更適合用於靜態地圖。
  • JPS+ 預處理的複雜度為 \(O(n)\) ,n 代表地圖格子數。
算法 性能 內存佔用 支持動態地圖 預處理 支持節點類型
A* 中等 支持 網格、Navmesh、路徑點
JPS 偏小 支持 網格
JPS+ 非常快 中等 不支持 有,\(O(n)\) 網格

綜上,JPS/JPS+ 是A*算法的優秀替代者,絕大部分情況下更快和更小的內存佔用已經足夠誘人。在GDC 2015 關於 JPS+ 算法的演講中,Steve Rabin 給出的數據甚至是比A* 算法快70~350倍。

參考

[1] 從頭理解JPS尋路算法 – 簡書 by ElephantKing

[2] JPS+: Over 100x Faster than A* | GDC 2015

[3] JPS+ with GoalBounding C++實現,和上面GDC2015的演講人是同一個人 Steve Rabin。

[4] 一個在線可視化的JPS實現附說明 A Visual Explanation Of Jump Point Search

[5] JPS 算法原作者論文 github Harabor, Daniel Damir, and Alban Grastien. “Online Graph Pruning for Pathfinding On Grid Maps.” AAAI. 2011.

博主其它相關文章:
遊戲AI 系列文章 – KillerAery – 博客園
遊戲AI之路徑規劃 – KillerAery – 博客園

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

02 . Ansible高級用法(運維開發篇)

自動化任務簡介

假設我們要在10台linux服務器上安裝一個nginx服務,手動是如何做的?

# 第一步, ssh登錄NUM(1,n)服務器
# 第二步,輸入對應服務器密碼
# 第三步,執行命令: yum install nginx 循環操作n=10
# 第四步,執行命令: service nginx start
# 第五步,退出登錄

自動化任務執行的意義

# 意義一, 提升運維工作效率,減少一份工作成本
# 意義二, 提高準確度.

自動化任務執行的應用

# 應用一, 批量命令執行
# 應用二, 定時程序任務執行
# 應用三, 批量程序應用服務安裝
# 應用四, 批量配置文件同步
# 應用五, 批量代碼部署

ansible配置

ansible是python中的一套模塊,系統中的一套自動化工具,可以用作系統管理,自動化命令等任務

ansible優勢
# 1.ansible是python中的一套完整的自動化執行任務模塊
# 2.ansible的play_book模式,不用yaml配置,對於自動化任務執行一目瞭然.
# 3.自動化場景支持豐富
ansible配置文件
1. inventory
# 該參數表示資源清單inventory文件的位置,資源清單就是一些Ansible需要連接管理的主機列表  
# inventory = /root/ansible/hosts

2. library
# Ansible的操作動作,無論是本地或遠程,都使用一小段代碼來執行,這小段代碼稱為模塊,這個library參數就是指向存放Ansible模塊的目錄  
# library = /usr/share/ansible

3. forks
# 設置默認情況下Ansible最多能有多少個進程同時工作,默認設置最多5個進程并行處理。具體需要設置多少個,可以根據控制主機的性能和被管理節點的數量來確定。  
# forks = 5

4. sudo_user
# 這是設置默認執行命令的用戶,也可以在playbook中重新設置這個參數
# sudo_user = root
# 注意: 新版本已經做了修改,如ansible2.4.1下已經為:
# default_sudo_user = root

5. remote_port
# 這是指定連接被關節點的管理端口,默認是22,除非設置了特殊的SSH端口,不然這個參數是不需要被修改的
# remote_port = 22

6. host_key_checking
# 這是設置是否檢查ssh主機的秘鑰,可以設置為True或者False
# host_key_checking = False

7. timeout
# 這是設置ssh連接的超時間隔,單位是秒
# timeout = 20

8. log_path
# ansible系統默認是不記錄日誌的,如果想把ansible系統的輸出記錄到指定地方,需要設置log_path來指定一個存儲Ansible日誌的文件

9. private_key_file
# 在使用ssh公鑰私鑰登錄系統時使用的秘鑰路徑
# private_key_file=/path/to/file.pem
ansible.cfg
[defaults]
inventory = /tmp/hosts
forks = 5
default_sudo_user = root
remote_port = 22
host_key_checking = Falsetimeout = 20
log_path = /var/log/ansible.log
#private_key_file=/tmp/file.pem

ansible安裝

# 1. 通過系統的方式,yum,apt,get等
# 2. 通過python的方式


# (推薦)python ./setup.py install 
easy_install ansible
pip install ansible	

Ansible基礎操作

當我們將Ansible安裝好以後,可以通過一些命令開始深入了解Ansible了.
我們最先展示的並非那強大的集配置,部署,自動化於一身的playbook.而是如何初始化.

遠程連接概述

在我們開始前要先理解Ansible如何通過SSH與遠程服務器連接是很重要的.
Ansible1.3及之後的版本默認會在本地的OpenSSH可用時會嘗試用其遠程通訊,這會啟用ControlPersist(一個性能特性),Kerberos,和在~/.ssh/config中的配置選項如 Jump Host setup.然而,當你使用Linux企業版6作為主控機(紅帽企業版及其衍生版如CentOS),其OpenSSH版本可能過於老舊無法支持ControIPersist,在這些操作系統中,Ansible將會退回並採用paramiko(由Python實現的高質量OpenSSH庫).如果你希望能夠使用像是Kerberized SSH之類的特性,煩請考慮使用Fedora,OS X,或Ubuntu作為你的主控機直到相關平台上有更新版本的OpenSSH可供使用,或者啟用Ansible的”accelerated mode”.

在Ansible1.2及之前的版本,默認將會使用paramiko,本地OpenSSH必須通過-c ssh或者配置文件中設定.

我們偶爾會遇到不支持SFTP的設備,雖然很少見,但有概率中獎,可以通過ansible配置文件切換至scp模式來與之連接.

說起遠程設備,Ansible會默認假定你使用SSH key(當然也推薦這種)但是密碼一樣可以,通過在需要的地方添加-ask-pass選項來啟用密碼驗證,如果使用了sudo特性,當sudo需要密碼時,也同樣適當的提供了-ask-sudo-pass選項.

也許這是常識,但也值得分享:任何管理系統受益於被管理的機器在主控機附近運行.如果在雲中運行,可以考慮在使用雲中的一台機器來運行Ansible.

作為一個進階話題,Ansible不止支持SSH來遠程連接.連接方式是插件化的而且還有許多本地化管理的選項諸如管理 chroot, lxc, 和 jail containers.一個叫做‘ansible-pull’的模式能夠反轉主控關係並使遠程系統通過定期從中央git目錄檢出 並 拉取 配置指令來實現背景連接通信

第一條命令(公鑰認證)

我們已經安裝ansible了,第一件事就是編輯或者創建/etc/ansible/hosts並在其中加入一個或多個遠程系統,我們的public SSH key必須在這些系統的authorized_keys中.

# 我們現在ansible控制機上主機名解析
tail /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
121.36.43.223	node1
120.77.248.31	node2
116.196.83.113	master

# 將解析過的主機名放到ansible配置文件裏面
tail -2 /etc/ansible/hosts 
node1
node2

# ansible控制機生成公鑰並傳給需要被控制的機器上
ssh-copy-id node1
ssh-copy-id node2
# 因為考慮到安全問題,會有主機秘鑰的檢查,但如果在內網非常信任的服務器就沒必要了.
sed -i 's/# *StrictHostKeyChecking *ask/StrictHostKeyChecking no/g' /etc/ssh/ssh_config

# 然後我們就可以執行第一條命令來查看能ping通控制的所有節點.
ansible all -m ping
node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}
node2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

Ansible會像SSH那樣試圖用你的當前用戶名來連接你的遠程機器.要覆寫遠程用戶名,只需使用’-u’參數. 如果你想訪問 sudo模式,這裏也有標識(flags)來實現:

ansible all -m ping -u bruce

ansible all -m ping -u bruce --sudo

ansible all -m ping -u bruce --sudo --sudo-user batman

(如果你碰巧想要使用其他sudo的實現方式,你可以通過修改Ansible的配置文件來實現.也可以通過傳遞標識給sudo(如-H)來設置.) 現在對你的所有節點運行一個命令:

ansible all -a "/bin/echo hello"
node1 | CHANGED | rc=0 >>
hello

node2 | CHANGED | rc=0 >>
hello

公鑰認證

Ansible1.2.1及其之後的版本都會默認啟用公鑰認證

如果有個主機重新安裝並在“known_hosts”中有了不同的key,這會提示一個錯誤信息直到被糾正為止.在使用Ansible時,你可能不想遇到這樣的情況:如果有個主機沒有在“known_hosts”中被初始化將會導致在交互使用Ansible或定時執行Ansible時對key信息的確認提示.

如果你想禁用此項行為並明白其含義,你能夠通過編輯 /etc/ansible/ansible.cfg or ~/.ansible.cfg來實現:

[defaults]
host_key_checking = False

同樣注意在paramiko 模式中 公鑰認證 相當的慢.因此,當使用這項特性時,切換至’SSH’是推薦做法.

密碼認證

因為我們接下來要將存取的密碼放到主機清單甚至存到Mysql裏面,我們可以裝一個ssh_pass

apt-get install sshpass

我們將之前的公鑰.ssh目錄都刪掉,主機名解析不用管.

注意,刪除.ssh目錄過後記得關閉主機秘鑰檢查.

tail -3 /etc/hosts
121.36.43.223	node1
120.77.248.31	node2
116.196.83.113	master

tail -2  /etc/ansible/hosts 
node1
node2

ansible all -m ping -k
# 並不是真的ping,只是檢查客戶端的22號端口是否提供工作.不指定用戶默認root用戶
# -k 輸入密碼
# -m 指定模塊
SSH password: 
node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}
node2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}
# 如果不想指定用戶名和密碼,避免ansible每次執行到相應主機都會要求輸入密碼.
tail -2 /etc/ansible/hosts 
node1 ansible_ssh_user='root' ansible_ssh_pass='youmen'
node2 ansible_ssh_user='root' ansible_ssh_pass='youmen'  ansible_ssh_port=22

ansible all -m ping
node1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}
node2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

Ansible常用模塊

常用模塊

模塊名 作用 用例
command 默認模塊 ansible webserver -a “/sbin/reboot” -f 10
shell 執行shell命令 ansible test -m shell -a “echo $HOSTNAME”
filetransfer 文件傳輸 ansible test -m copy -a “src=/etc/hosts dest=/tmp/hosts”
managingpackages 管理軟件包 ansible test -m yum -a “name=nginx state=present”
user and groups 用戶和組 ansible test -m user -a “name=jeson password=123456”
Deploying 部署模塊 ansible test -m git -a “repo=https://github.com/iopsgroup/imoocc dest=/opt/iops version=HEAD”
managingservices 服務管理 ansible test -m service -a “name=nginx state=started”
BackgroundOperatiions 後台運行 ansible test -B 3600 -a “/usr/bin/running_operation –do-stuff”
gatheringfacts 搜集系統信息 ansible test -m setup

playbook

playbook由YAML語言編寫,YAML參考了其他多種語言,包括: XML,C語言,Python,Perl以及电子郵件格式RFC2822,Clark Evans在2001年5月首次發表了這種語言,另外Ingy dt Net與Oren-Kiki也是這語言的共同設計者.

playbook的優勢

# 1. 功能比adhoc更全
# 2. 控制好依賴
# 3. 展現更直觀
# 4. 持久使用

ansible-playbook執行常用命令參數:

執行方式:ansible-playbook playbook.yml [options]

 -u REMOTE_USER, --user=REMOTE_USER  
# ssh 連接的用戶名
 -k, --ask-pass    
# ssh登錄認證密碼
 -s, --sudo           
# sudo 到root用戶,相當於Linux系統下的sudo命令
 -U SUDO_USER, --sudo-user=SUDO_USER    
# sudo 到對應的用戶
 -K, --ask-sudo-pass     
# 用戶的密碼(—sudo時使用)
 -T TIMEOUT, --timeout=TIMEOUT 
# ssh 連接超時,默認 10 秒
 -C, --check      
# 指定該參數后,執行 playbook 文件不會真正去執行,而是模擬執行一遍,然後輸出本次執行會對遠程主機造成的修改

 -e EXTRA_VARS, --extra-vars=EXTRA_VARS    
# 設置額外的變量如:key=value 形式 或者 YAML or JSON,以空格分隔變量,或用多個-e

 -f FORKS, --forks=FORKS    
# 進程併發處理,默認 5
 -i INVENTORY, --inventory-file=INVENTORY   
# 指定 hosts 文件路徑,默認 default=/etc/ansible/hosts
 -l SUBSET, --limit=SUBSET    
# 指定一個 pattern,對- hosts:匹配到的主機再過濾一次
 --list-hosts  
# 只打印有哪些主機會執行這個 playbook 文件,不是實際執行該 playbook
 --list-tasks   
# 列出該 playbook 中會被執行的 task

 --private-key=PRIVATE_KEY_FILE   
# 私鑰路徑
 --step    
# 同一時間只執行一個 task,每個 task 執行前都會提示確認一遍
 --syntax-check  
# 只檢測 playbook 文件語法是否有問題,不會執行該 playbook 
 -t TAGS, --tags=TAGS   
# 當 play 和 task 的 tag 為該參數指定的值時才執行,多個 tag 以逗號分隔
 --skip-tags=SKIP_TAGS   
# 當 play 和 task 的 tag 不匹配該參數指定的值時,才執行
 -v, --verbose   
# 輸出更詳細的執行過程信息,-vvv可得到所有執行過程信息。
使用場景

yaml語法

  1. playbook配置
---
- hosts: 39.108.140.0
  remote_user: root
  vars:
   touch_file: youmen.file
  tasks:
  - name: touch file
    shell: "touch /tmp/{{touch_file}}"

yaml主要由三個部分組成:

> hosts部分:
# 使用hosts指示使用哪個主機或主機組來運行下面的tasks,
# 每個playbook都必須指定hosts,hosts也可以使用通配符格式。
# 主機或主機組在inventory清單中指定,可以使用系統默認的/etc/ansible/hosts,
# 也可以自己編輯,在運行的時候加上-i選項,指定清單的位置即可。
# 在運行清單文件的時候,--list-hosts選項會显示那些主機將會參与執行task的過程中。

> remote_user:指定遠端主機中的哪個用戶來登錄遠端系統,
# 在遠端系統執行task的用戶,可以任意指定,也可以使用sudo,
# 但是用戶必須要有執行相應task的權限。

> tasks:指定遠端主機將要執行的一系列動作。tasks的核心為ansible的模塊,
# 前面已經提到模塊的用法。tasks包含name和要執行的模塊,name是可選的,
# 只是為了便於用戶閱讀,不過還是建議加上去,模塊是必須的,同時也要給予模塊相應的參數。

執行

ansible-playbook -i /tmp/hosts --list-hosts ./f1.yaml
ansible-playbook ./f1.yaml

# 執行結果返回
# 紅色: 表示有task執行失敗或者提醒的信息
# 黃色: 表示執行了且改變了遠程主機狀態
# 綠色: 表示執行成功

yaml語法和數據結構

yaml語法

YAML格式是類似於JSON的文件格式,以便於人理解和閱讀,同時便於書寫,首先學習了解一下YAML的格式,對我們後面書寫playbook很有幫助.
以下為playbook常用到的YAML格式

# 大小寫敏感
# 使用縮緊表示層級關係(只能空格不能使用tab)
# yaml文件以"---"作為文檔的開始
# 在同一行中,#之後的內容表示註釋,類似於shell,python和ruby.
# YAML中的列表元素以"-"開頭,然後緊跟着一個空格,後面為元素內容,就像這樣
- apple
- orange
等價於JSON的這種格式
[
 "apple",
 "orange"
]

# 同一個列表中的元素應該保持相同的縮進,否則會被當做錯誤處理.
# play中hosts,variables,roles,tasks等對象的表示方法都是鍵值中間以":"分割表示,":"後面還要增加一個空格.
變量定義方式

變量名可以為字母,数字以及下劃線

playbook里的變量

 1. playbook的yaml文件中定義變量賦值
> 2. --exxtra-vars執行參數賦給變量
> 3. 在文件中定義變量
> 4. 註冊變量
# register關鍵字可以存儲指定命令的輸出結果到一個自定義的變量中.
---
- hosts: database
  remote_user: root
  vars:
    touch_file: youmen_file
  tasks:
    - name: get date
      command: date
      register: date_output
    - name: touch
      shell: "touch /tmp/{{touch_file}}"
    - name: echo  date_output
      shell: "echo {{date_output.stdout}}>/tmp/{{touch_file}}"
數據結構

yaml支持的數據結構
字典

{name:jeson}

列表

- Apple
- Mango
- Orange

純量: 数字,布爾,字符串

條件判斷
循環
循環類型 關鍵字
標準循環 with_items
嵌套循環 with_nested
遍歷字典 with_dict
并行遍歷列表 with_together
遍歷列表和索引 with_indexed_items
遍歷文件列表的內容 with_file
遍歷目錄文件 with_fileglog
重試循環 until
查找第一個匹配文件 with_first_found
隨機選擇 with_random_choice
在序列中循環 with_sequence

條件循環語句復用

種類一, 標準循環

---
- hosts: nginx
  tasks:
  - name: add serveral users
    user: name={{ item.name }} state=present groups={{ item.groups }}
    with_items:
      - { name: 'testuser1',groups: 'wheel' }
      - { name: 'testuser2',groups: 'root'  }

種類二, 遍歷字典

---
- hosts: nginx
  remote_user: root
  tasks:
    - name: add serveral users
      user: name={{ item.key }} state=present groups={{ item.value }}
      with_dict:
        { 'testuser3':'wheel','testuser4':'root' }

種類三, 遍歷目錄中內容

---
- hosts: nginx
  remote_user: root
  tasks:
   - file: dest=/tmp/aa state=directory
   - copy: src={{ item }} dest=/tmp/bb owner=root mode=600
     with_fileglob:
       - aa/*n
條件語句結合循環語句使用場景
---
- hosts: nginx
  remote_user: root
  tasks:
    - debug: msg="{{ item.key }} is the winner"
      with_dict: {'jeson':{'english':60,'chinese':30},'tom':{'english':20,'chinese':30}}
      when: item.value.english >= 10
異常

異常處理和相關操作

異常處理

1.忽略錯誤

默認會檢查命令和模塊的返回狀態,遇到錯誤就中斷playbook的執行
加入參數: ignore_errors: yes
Example

- hosts: nginx
  remote_user: root
  tasks:
    - name: ignore false
      command: /bin/false
      ignore_errors: yes
    - name: touch a file
      file: path=/tmp/test22 state=touch mode=0700 owner=root group=root

2. tags標籤處理

---
- hosts: nginx
  remote_user: root
  tasks:
    - name: get process
      shell: touch /tmp/change_test2
      changed_when: false

打標籤

意義: 通過tags和任務對象進行捆綁,控制部分或者指定的task執行

# 打標籤
# 	對一個對象打一個標籤
# 	對一個對象打多個標籤
# 	打標籤的對象包括: 單個task任務,include對象,roles對象等.

標籤使用

-t : 執行指定的tag標籤任務
--skip-tags: 執行 --skip-tags之外的標籤任務
  1. 自定義change狀態
---
- hosts: nginx
  remote_user: root
  tasks:
    - name: get process
      shell: ps -ef |wc -l
      register: process_count
      failed_when: process_count > 3
    - name: touch a file
      file: path=/tmp/test1 state=touch mode=0700 owner=root group=root
roles角色和場景演練

使用roles角色
include的用法

include_tasks/include: 動態的包含tasks任務列表執行

什麼是roles

是一種利用在大型playbook中的劇本配置模式,在這自己特定結構

為什麼需要用到roles

和面向對象開發思想相似
利用於大型的項目任務中,盡可能的將公共的任務,變量等內容獨立

劇本結構和設計思路
ansible官方網站的建議playbook劇本結構如下:

production        # 正式環境的inventory文件
staging           #測試環境用得inventory文件
group_vars/  	  # 機器組的變量文件
      group1        
      group2
host_vars/   	 #執行機器成員的變量
      hostname1     
      hostname2
================================================
site.yml               # 主要的playbook劇本
webservers.yml         # webserver類型服務所用的劇本
dbservers.yml          # 數據庫類型的服務所用的劇本

roles/
      webservers/        #webservers這個角色相關任務和自定義變量
           tasks/
               main.yml
           handlers/
               main.yml
           vars/            
                main.yml
        dbservers/         #dbservers這個角色相關任務和定義變量
            ...
      common/       	  # 公共的
           tasks/        
                main.yml    
           handlers/    
                main.yml    # handlers file.
           vars/            # 角色所用到的變量
                main.yml    # 
===============================================
      templates/    #
            ntp.conf.j2 # 模版文件
      files/        #   用於上傳存放文件的目錄
            bar.txt     
            foo.sh     
      meta/         # 角色的依賴
            main.yml   
場景演練

Nginx工程方式的編譯安裝

# 劇本分解
ansible.cfg
  - files		#	存放上傳文件
    - index.html
    - nginx    # 系統init中,控制nginx啟動腳本
    - nginx-1.12.2.tar.gz  # nginx的安裝包文件

production	  # 線上的主機配置文件
roles		  # roles角色執行
  - apache
  - common
     tasks
       main.yml
     vars
	main.yml
    meta
    nginx
      - handlers   通過notify觸發
        main.yml
      - tasks
	  - basic.yml
	  - main.yml
	 - nginx.yml
      - vars
	= main.yml
    tasks

staging			線下測試環境使用的主機配置文件
  - templates		模板(配置,html)
    - nginx1.conf	nginx的自定義conf文件
webserver.yaml		web服務相關主執行文件

Ansible的核心類介紹

核心類 用途 所在的模塊路徑
DataLoader 用於讀取yaml,json格式的文件 ansible.parsing.dataloader
Play 存儲執行hosts的角色信息 ansible.playbook.play
TaskQueueManager ansible底層用到的任務隊列 ansible.executor.task_queue_manager
PlaybookExecutor 核心累執行playbook劇本 ansible.executor.playbook_executor
CallbackBase 狀態回調,各種成功失敗的狀態 ansible.plugins.callback
InventoryManager 用於導入inventory文件 ansible.inventory.manager
VariableManager 用於存儲各類變量信息 ansible.vars.manager
Host,Group 用於操作單個主機或者主機組信息 ansible.inventory.host
InventoryManager

用來管理主機和主機組信息

from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager

# InventoryManager類
loader = DataLoader()
InventoryManager(loader=loader,sources=['youmen_hosts'])

# 1. 添加主機到指定主機組 add_host()  
# 2. 查看主機組資源get_groups_dict()  
# 3. 獲取指定的主機對象get_host()

# VariableManager類
# loader: 實例對象
# inventory: 調用InventoryManager返回的實例對象.
VariableManager(loader=loader,inventory=inventory)

# 查看主機變量方法 get_vars()
# 設置主機變量方法set_host_variable()
# 添加擴展變量extra_vars
ad-hoc模式調用場景

ansible -m command -a "ls /tmp" testgroup -i /etc/ansible/hosts -f 5

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

多線程實例——遍歷文件夾分割文件識別文件內容

需求:遍歷文件夾下的所有pdf文件,對每個pdf文件根據二維碼進行分割,再對分割后的文件的內容進行識別。

可以拆分為以下幾個關鍵方法:

1.GetFileList方法:遍歷文件,獲取源文件動態數組(這裏假設3個文件夾,每個文件夾下有3個文件,則源文件個數為9),耗時忽略不計

 1 static List<string> GetFileList(string strFilefolder)
 2         {
 3             List<string> list_file = new List<string>();
 4 
 5             for (int i = 0; i <= 2; i++)
 6             {
 7                 for (int j = 0; j <= 2; j++)
 8                     list_file.Add("File" + i + j);
 9             }
10 
11             return list_file;
12         }

View Code

2.SplitProcess方法:分割原始pdf文件,識別二維碼(假設耗時500ms),將一個pdf文件分割為N(這裏假設個數為6)個子文件

 1 static void SplitProcess(string sourcefile)
 2         {
 3             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
 4             for (int i = 0; i <= 5; i++)
 5             {
 6                 //模擬分割單個文件的過程,花費500ms
 7                 Thread.Sleep(500);
 8                 string split_file = sourcefile + i;
 9                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
10                 RecognizeProcess(split_file);
11             }
12             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
13         }

View Code

3.RecognizeProcess方法:識別子文件的內容:加載識別庫,設置識別參數,截取識別區域圖像,圖像處理(如縮放,降噪,灰度轉換等),識別(假設耗時5000ms

1 static void RecognizeProcess(string split_file)
2         {
3             //模擬識別的過程,花費5000ms
4             Thread.Sleep(5000);
5             Console.WriteLine("ocrFile Completed:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
6         }

View Code

單線程處理:

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("Enter Main" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 4             string strFilefolder = "";
 5             OcrProcess(strFilefolder);
 6             Console.WriteLine("Main Completed" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 7             Console.ReadKey();
 8         }
 9 
10         static void OcrProcess(string strFilefolder)
11         {            
12             List<string> list_sourcefile = GetFileList(strFilefolder);
13             list_sourcefile.ForEach((sourcefile) =>
14             {
15                 Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
16                 //這裏對文件進行分割
17                 SplitProcess(sourcefile);
18             });            
19         }

View Code

這個單線程處理的執行結果我們可以預估一下,應該大於 9 * 6 * (0.5 + 5) = 297 秒。

實際結果:

 …… 

 開始時間 2020-06-17 15:22:28 6104 結束時間 2020-06-17 15:27:26 1541 

由於是線性處理,整個過程耗費的時間約5分鐘,所以必須要進行優化,所以考慮用多線程來提高效率。

優化方向:

1.多線程,使用Task并行對源文件進行分割 

 1 static void OcrProcess(string strFilefolder)
 2         {
 3             List<Task> tasks = new List<Task>();  
 4             List<string> list_sourcefile = GetFileList(strFilefolder);
 5             list_sourcefile.ForEach((sourcefile) =>
 6             {
 7                 Task task = Task.Factory.StartNew( () =>
 8                 { 
 9                     Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
10                     //這裏對文件進行分割
11                     SplitProcess(sourcefile);
12                 });
13                 tasks.Add(task);                
14             });
15             Task.WaitAll(tasks.ToArray());
16         }

View Code

 ……

 

開始時間 2020-06-17 15:51:54 5458 結束時間 2020-06-17 15:52:35 3144 

整個過程耗費的時間約41秒,優化效果明顯。

2.每分割出來一個文件,開啟子線程,進行識別

 1 static void SplitProcess(string sourcefile)
 2         {
 3             List<Task> tasks = new List<Task>();
 4             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
 5             for (int i = 0; i <= 5; i++)
 6             {
 7                 //模擬分割單個文件的過程,花費500ms
 8                 Thread.Sleep(500);
 9                 string split_file = sourcefile + i;
10                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
11                 Task task = Task.Factory.StartNew(() =>
12                 {
13                     RecognizeProcess(split_file);
14                 });
15                 tasks.Add(task);
16             }
17             Task.WaitAll(tasks.ToArray());
18             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
19         }

View Code

  ……

 開始時間 2020-06-17 15:58:59 2591 結束時間 2020-06-17 15:59:28 9051 

整個過程耗費的時間約29秒,運行時間進一步縮短。 

然而,最後再思考一下,如果把多線程發揮到極致,理想狀態應該是多少秒執行完畢?

以源文件sourcefile=File00為例,

第一個分割子文件split_file=File000,ReadyTime 500ms,Ocr Completed Time 應該為5500ms

第二個分割子文件split_file=File001,ReadyTime 1000ms,Ocr Completed Time 應該為6000ms 

第三個分割子文件split_file=File002,ReadyTime 1500ms,Ocr Completed Time 應該為6500ms 

第四個分割子文件split_file=File003,ReadyTime 2000ms,Ocr Completed Time 應該為7000ms

第五個分割子文件split_file=File004,ReadyTime 2500ms,Ocr Completed Time 應該為7500ms

第六個分割子文件split_file=File005,ReadyTime 3000ms,Ocr Completed Time 應該為8000ms

File00 Split Completed!

每一個源文件(sourcefile)被逐個分割為6個拆分子文件(split_file)並識別完成,都需要8000ms時間,如果使用線程同步的話,那麼後續源文件也同步被分割並識別完成。 

所以,理想情況下,應該是8秒,而與29秒差距太大了,應該還有優化空間!

3.怎麼優化?向什麼方向優化?我們不妨不用Task,回歸到Thread本身來試試。

可是Thread運行時沒有Task.WaitAll()這樣的控制方法,因此,我們還要引入WaitHandle和ManualResetEvent來進行多線程管理。 

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("Enter Main" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 6             string strFilefolder = "";
 7             OcrProcess(strFilefolder);
 8             Console.WriteLine("Main Completed" + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
 9             Console.ReadKey();
10         }        
11 
12         static void OcrProcess(string strFilefolder)
13         {            
14             List<ManualResetEvent> split_waits = new List<ManualResetEvent>();
15             List<string> list_sourcefile = GetFileList(strFilefolder);
16             list_sourcefile.ForEach((sourcefile) =>
17             {
18                 Thread m_thread = new Thread(() =>
19                 {
20                     ManualResetEvent mre = new ManualResetEvent(false);
21                     split_waits.Add(mre);
22                     Console.WriteLine(sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
23                     //這裏對文件進行分割
24                     SplitProcess(sourcefile);
25                     mre.Set();
26                 });
27                 m_thread.Start();
28             });
29             WaitHandle.WaitAll(split_waits.ToArray());
30         }
31 
32         static void SplitProcess(string sourcefile)
33         {
34             Console.WriteLine("SplitFile Start:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
35             var ocr_waits = new List<EventWaitHandle>();
36             for (int i = 0; i <= 5; i++)
37             {
38                 //模擬分割單個文件的過程,花費500ms
39                 Thread.Sleep(500);
40                 string split_file = sourcefile + i;
41                 Console.WriteLine("file ready:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
42                 ManualResetEvent mre_child = new ManualResetEvent(false);
43                 ocr_waits.Add(mre_child);
44                 Thread m_child_thread = new Thread(() =>
45                 {
46                     Console.WriteLine("m_child_thread enter:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
47                     RecognizeProcess(split_file);                    
48                     mre_child.Set();
49                     Console.WriteLine("m_child_thread after set:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
50                 });
51                 m_child_thread.Start();
52             }
53             WaitHandle.WaitAll(ocr_waits.ToArray());
54             Console.WriteLine("SplitFile Completed:" + sourcefile + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));            
55         }
56 
57         static void RecognizeProcess(string split_file)
58         {
59             //模擬識別的過程,花費5000ms
60             Thread.Sleep(5000);
61             Console.WriteLine("ocrFile Completed:" + split_file + DateTime.Now.ToString(" yyyy-MM-dd HH:mm:ss ffff"));
62         }
63 
64         static List<string> GetFileList(string strFilefolder)
65         {
66             List<string> list_file = new List<string>();
67             for (int i = 0; i <= 2; i++)
68             {
69                 for (int j = 0; j <= 2; j++)
70                     list_file.Add("File" + i + j);
71             }
72             return list_file;
73         }
74     }

View Code

  

  ……

開始時間 2020-06-17 15:28:17 2397 結束時間 2020-06-17 16:28:27 9151 

整個過程耗費的時間約10秒,運行時間與理論的8秒已經十分接近(因為Thread創建以及運行時需要切換上下文,Console.WriteLine都有一定的耗時,PC性能好的話應該更接近8秒),可以說目標已經達成。 

Tips:

ManualResetEvent初始狀態為false表示不將線程信號量初始值置為signal,線程會自動往下執行,執行Set()方法時,將線程信號量置為signal。

WaitHandle.WaitAll(split_waithandle1,split_waithandle2); //一直等待,直到split_waithandle1,split_waithandle2信號量均被置為signal才會往下執行。

不足之處:

開啟Thread要受到系統的限制,所以本例線程數必須考慮操作系統線程最大值限制。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!