標籤: 電動車

  • 007.OpenShift管理應用部署

    007.OpenShift管理應用部署

    一 REPLICATION CONTROLLERS

    1.1 RC概述

    RC確保pod指定數量的副本一直運行。如果pod被殺死或被管理員顯式刪除,複製控制器將自動部署相應的pod。類似地,如果運行的pod數量超過所需的數量,它會根據需要刪除pod,以匹配指定的副本計數。
    RC的定義主要包括:

    • 所需的副本數量
    • 用於創建複製pod的pod定義
    • 用於標識後續管理操作的selector

    selector是一組label,RC管理的所有pod都必須匹配這些標籤。RC實例化的pod定義中必須包含相同的標籤集。RC使用這個selector來確定已經運行了多少pod實例,以便根據需要進行調整。
    提示:不執行自動縮放,因為它不跟蹤負載或流量。
    儘管Kubernetes通常直接管理RC,但OpenShift推薦的方法是管理根據需要創建或更改RC的DC。

    1.2 從DC創建RC

    在OpenShift中創建應用程序的最常見方法是使用oc new-app命令或web控制台。以這種方式創建的應用程序使用DeploymentConfig資源在運行時創建RC來創建應用程序pod。DeploymentConfig資源定義定義了要創建的pod的副本的數量,以及要創建的pod的模板。
    注意:不要將DeploymentConfig或ReplicationController資源中的template屬性誤認為OpenShift模板資源類型,OpenShift模板資源用於基於一些常用的語言運行時和框架構建應用程序。

    1.3 pod副本數控制

    DeploymentConfig或ReplicationController資源中的副本數量可以使用oc scale命令動態更改。
    $ oc get dc
    NAME REVISION DESIRED CURRENT TRIGGERED BY
    myapp 1 3 3 config,image(scaling:latest)
    $ oc scale –replicas=5 dc myapp
    DeploymentConfig資源將更改信息傳遞至ReplicationController,該控制器通過創建新的pod(副本)或刪除現有的pod來響應更改。
    雖然可以直接操作ReplicationController資源,但推薦的做法是操作DeploymentConfig資源。在觸發部署時,直接對ReplicationController資源所做的更改可能會丟失,例如,使用容器image的新版本重新創建pod。

    1.4 自動伸縮pod

    OpenShift可以通過HorizontalPodAutoscaler資源類型根據應用程序pod上的當前負載自動調整部署配置。
    HorizontalPodAutoscaler (HPA)資源使用OpenShift metrics子系統收集的性能指標,即如果沒有度量子系統(模塊),更確切地說是Heapster組件,自動縮放是不可能的。
    創建HorizontalPodAutoscaler資源的推薦方法是使用oc autoscale命令,例如:
    $ oc autoscale dc/myapp –min 1 –max 10 –cpu-percent=80
    該命令創建一個HorizontalPodAutoscaler資源,該資源更改myapp部署配置上的副本數量,以將其pod的CPU使用量控制在請求的總CPU使用量的80%以下。
    oc autoscale命令使用DC的名稱作為參數(在前面的示例中是myapp)創建一個HorizontalPodAutoscaler資源。
    HorizontalPodAutoscaler資源的最大值和最小值用於容納突發負載,並避免重載OpenShift集群。如果應用程序上的負載變化太快,建議保留一些備用的pod來處理突然出現的用戶請求。相反,過多的pod會耗盡所有集群容量,並影響共享相同OpenShift集群的其他應用程序。
    要獲取當前項目中關於HorizontalPodAutoscaler資源的信息,可使用oc get和oc describe命令。例如
    $ oc get hpa/frontend
    $ oc describe hpa/frontend
    注意:HorizontalPodAutoscaler資源只適用於為引用性能指標定義資源請求的pod。
    oc new-app命令創建的大多數pod沒有定義任何資源請求。因此,使用OpenShift autoscaler可能需要為應用程序創建定製的YAML或JSON資源文件,或者向項目添加資源範圍資源。

    二 擴展程序實驗

    2.1 前置準備

    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    2.2 創建應用

      1 [student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com
      2 [student@workstation ~]$ oc new-project scaling
      3 [student@workstation ~]$ oc new-app -o yaml -i php:7.0 \
      4 http://registry.lab.example.com/scaling > ~/scaling.yml		#將部署的yaml導出至本地
      5 [student@workstation ~]$ vi ~/scaling.yml
      6 ……
      7   spec:
      8     replicas: 3
      9     selector:
     10       app: scaling
     11       deploymentconfig: scaling				#修改副本數
     12 ……
     13 [student@workstation ~]$ oc create -f ~/scaling.yml	#以修改副本數后的yaml部署應用

     

    2.3 監視部署

      1 [student@workstation ~]$ watch -n 3 oc get builds
      2 Every 3.0s: oc get builds                                                                Mon Jul 22 11:12:02 2019
      3 
      4 NAME        TYPE      FROM          STATUS     STARTED              DURATION
      5 scaling-1   Source    Git@0bdae71   Complete   About a minute ago   1m0s
      6 [student@workstation ~]$ oc get pods
      7 NAME              READY     STATUS      RESTARTS   AGE
      8 scaling-1-build   0/1       Completed   0          2m
      9 scaling-1-ft249   1/1       Running     0          1m
     10 scaling-1-gjvkp   1/1       Running     0          1m
     11 scaling-1-mtrxr   1/1       Running     0          1m

     

    2.4 暴露服務

      1 [student@workstation ~]$ oc expose service scaling \
      2 --hostname=scaling.apps.lab.example.com

     

    2.5 web查看相關信息

    瀏覽器訪問https://master.lab.example.com,使用developer用戶和redhat密碼登陸。選擇scaling項目。
     

    2.6 測試負載均衡

      1 [student@workstation ~]$ for i in {1..5};do curl -s \http://scaling.apps.lab.example.com | grep IP;done	#多次請求
      2  <br/> Server IP: 10.128.0.17
      3  <br/> Server IP: 10.129.0.35
      4  <br/> Server IP: 10.129.0.36
      5  <br/> Server IP: 10.128.0.17
      6  <br/> Server IP: 10.129.0.35

     
    提示:瀏覽器可能無法嚴格檢查均衡性,因為OpenShift route存在會話關聯性(也稱為粘性會話)。即來自同一個web瀏覽器的所有請求都將轉到同一個pod。

    2.7 擴容應用

      1 [student@workstation ~]$ oc describe dc scaling | grep Replicas
      2 Replicas:       3
      3         Replicas:       3 current / 3 desired
      4 [student@workstation ~]$ oc scale --replicas=5 dc scaling

     

      1 [student@workstation ~]$ oc get pods -o wide

    2.8 測試負載均衡

      1 [student@workstation ~]$ for i in {1..5};do curl -s \http://scaling.apps.lab.example.com | grep IP;done	#多次請求
      2  <br/> Server IP: 10.128.0.17
      3  <br/> Server IP: 10.128.0.18
      4  <br/> Server IP: 10.129.0.35
      5  <br/> Server IP: 10.129.0.36
      6  <br/> Server IP: 10.129.0.37

     

    三 pod調度控制

    3.1 pod調度算法

    pod調度程序確定新pod在OpenShift集群中的節點上的位置。該調度算法被設計為可高度配置和適應不同集群。OCP 3.9附帶的默認配置通過使用node label、affinity rules,anti-affinity rules中的定義來支持zone和regions的調用。
    在OCP以前的版本中,安裝程序master節點標記為污點標記,表示不允許在master上部署pod。在新版的OCP 3.9中,在安裝和升級過程中,master會自動標記為可調度的。使得可以通過deploy調度pod至maste節點。而不僅僅是作為master的組件運行。
    默認節點selector是在安裝和升級期間默認設置的。它被設置為node-role.kubernetes.io/compute=true,除非使用osm_default_node_selector的Ansible變量覆蓋它。
    在安裝和升級期間,不管osm_default_node_selector配置如何,都會對庫存文件中定義的主機執行以下自動標記。
    compute節點配置non-master、non-dedicated的角色(默認情況下,具有region=infra標籤的節點),節點使用node-role.kubernetes.io/compute=true標記。
    master節點被標記為node-role.kubernetes.io/master=true,從而分配master節點角色。

    3.2 調度算法步驟

    • 過濾節點

    調度程序根據節點資源(如主機端口)的可用性篩選正在運行的節點列表,然後進一步根據節點selector和來自pod的資源請求篩選。最終的縮小是可運行pod的候選node列表。
    pod可以定義與集群節點中的標籤匹配的節點選擇器,標籤不匹配的節點視為不合格。
    pod還可以為計算資源(如CPU、內存和存儲)定義資源請求,沒有足夠的空閑計算機資源的節點視為不合格。

    • 對過濾后的節點列表進行優先級排序

    候選節點列表使用多個優先級標準進行評估,這些標準加起來就是權重,權重值較高的節點更適合運行pod。
    其中有affinity(親和規則)和anti-affinity(反親和規則),pod親和力較高的節點得分較高,而anti-affinity較高的節點權重低。
    affinity的一個常見用法是:出於性能原因,將相關的pod安排得彼此親和。例如,需要保持彼此同步的pod使用相同的網絡棧。
    anti-affinity的一個常見用法是:為了獲得高可用性,將相關的pod安排的盡量分散。例如,避免將所有pod從同一個應用程序調度到同一個節點。

    • 選擇最合適的節點。

    根據權重對候選列表進行排序,並選擇權重最高的節點來承載pod。如果多個節點得分相同,則隨機選擇一個節點。
    調度程序配置文件位於/etc/original/master/scheduler.json,其定義了一組predicates,用作過濾器或優先級函數。通過這種方式,可以將調度程序配置為支持不同的集群。

    3.3 調度拓撲

    對於大型數據中心,例如雲提供商,一個常見的拓撲結構是將主機組織成regions和zones:
    region:是一個地理區域內的一組主機,這保證了它們之間的內網高速連接;
    zone:也稱為可用區,是一組主機,它們可能一起失敗,因為它們共享公共的關鍵基礎設施組件,比如網絡、存儲或電源。
    OpenShift pod調度器可支持根據region和zone標籤在集群內調度,如:

      • 從相同的RC創建的或從相同的DC創建的pod副本調度至具有相同region標籤值的節點中運行。
      • 副本Pod調位至具有不同zone標籤的節點中運行。

    實例圖如下:

    要實現上圖中的樣例拓撲,可以使用集群管理員通過以下命令oc label:

      1 $ oc label node1 region=ZheJiang zone=Cloud1A --overwrite
      2 $ oc label node node2 region=ZheJiang zone=Cloud1A --overwrite
      3 $ oc label node node3 region=ZheJiang zone=Cloud2A --overwrite
      4 $ oc label node node4 region=ZheJiang zone=Cloud2A --overwrite
      5 $ oc label node node5 region=HuNan zone=Cloud1B --overwrite
      6 $ oc label node node6 region=HuNan zone=Cloud1B --overwrite
      7 $ oc label node node7 region=HuNan zone=Cloud2B --overwrite
      8 $ oc label node node8 region=HuNan zone=Cloud2B --overwrite

     
    提示:每個節點必須由其完全限定名(FQDN)標識,為了簡潔,如上命令使用了簡短的名稱。
    對區域標籤的更改需要–overwrite選項,因為OCP 3.9高級安裝方法默認情況下使用region=infra標籤配置節點。
    示例:要檢查分配給節點的標籤,可以使用oc get node命令和–show-labels選項。
    $ oc get node node1.lab.example.com –show-labels
    注意,一個節點可能有一些OpenShift分配的默認標籤,包含kubernetes.io後綴鍵值的標籤,此類標籤不應由集群管理員人為更改,因為它們由調度程序在內部使用。
    集群管理員還可以使用-L選項來確定單個標籤的值。
    示例:

      1 $ oc get node node1.lab.example.com -L region
      2 $ oc get node node1.lab.example.com -L region -L zone	#支持oc get跟多個-L選項

     

    3.4 UNSCHEDULABLE節點

    有時候,集群管理員需要關閉節點進行維護,如節點可能需要硬件升級或內核安全更新。要在對OpenShift集群用戶影響最小的情況下關閉節點,管理員應該遵循兩個步驟。
    將節點標記為不可調度,從而防止調度程序向節點分配新的pod。

      1 $ oc adm manage-node --schedulable=false node2.lab.example.com

    Drain節點,這將銷毀在pod中運行的所有pod,並假設這些pod將通過DC在其他可用節點中會重新創建。

      1 $ oc adm drain node2.lab.example.com

    維護操作完成后,使用oc adm management -node命令將節點標記為可調度的。

      1 $ oc adm manage-node --schedulable=true node2.lab.example.com

    3.5 控制pod位置

    有些應用程序可能需要在一組指定的node上運行。例如,某些節點為某些類型的工作負載提供硬件加速,或者集群管理員不希望將生產應用程序與開發應用程序混合使用。此類需求,都可以使用節點標籤和節點選擇器來實現。
    node selector是pod定義的一部分,但建議更改dc,而不是pod級別的定義。要添加節點選擇器,可使用oc edit命令或oc patch命令更改pod定義。
    示例:配置myapp的dc,使其pods只在擁有env=qa標籤的節點上運行。

      1 $ oc patch dc myapp --patch '{"spec":{"template":{"nodeSelector":{"env":"qa"}}}}'

    此更改將觸發一個新的部署,並根據新的節點選擇器調度新的pod。
    如果集群管理員不希望讓開發人員控制他們pod的節點選擇器,那麼應該在項目資源中配置一個默認的節點選擇器。

    3.5 管理默認項目

    生產環境一個常見實踐是指定一組節點來運行OCP的系統基礎Pod,比如route和內部倉庫。這些pod在默認項目中定義。
    通常可通過以下兩個步驟實現:

    1. 使用region=infra標籤標記專用節點;
    2. 為缺省名稱空間配置缺省節點選擇器。

    要配置項目的默認節點選擇器,可使用openshift.io/node-selector鍵值向名稱空間資源添加註釋。可以使用oc edit或oc annotate命令。

      1 $ oc annotate --overwrite namespace default \
      2 openshift.io/node-selector='region=infra'

     
    OCP 3.9 quick installer和advanced installer的Ansible playbook都支持Ansible變量,這些變量控制安裝過程中分配給節點的標籤,也控制分配給每個基礎設施pod的節點選擇器。
    安裝OCP子系統(如metrics子系統)的劇本還支持這些子系統節點選擇器的變量。

    四 控制Pod調度

    4.1 前置準備

    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    4.2 本練習準備

      1 [student@workstation ~]$ lab schedule-control setup
      2 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com

     

    4.3 查看region

      1 [student@workstation ~]$ oc get nodes -L region
      2 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      3 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      4 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
      5 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra

     

    4.4 創建project

      1 [student@workstation ~]$ oc new-project schedule-control

    4.5 創建應用

      1 [student@workstation ~]$ oc new-app --name=hello \
      2 --docker-image=registry.lab.example.com/openshift/hello-openshift

     

    4.6 擴展應用

      1 [student@workstation ~]$ oc scale dc hello --replicas=5
      2 deploymentconfig "hello" scaled
      3 [student@workstation ~]$ oc get pod -o wide
      4 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
      5 hello-1-c5z2n   1/1       Running   0          7s        10.128.0.21   node1.lab.example.com
      6 hello-1-hhvp7   1/1       Running   0          34s       10.129.0.38   node2.lab.example.com
      7 hello-1-jqrkb   1/1       Running   0          7s        10.128.0.20   node1.lab.example.com
      8 hello-1-tgmbr   1/1       Running   0          7s        10.129.0.39   node2.lab.example.com
      9 hello-1-z2bn7   1/1       Running   0          7s        10.128.0.22   node1.lab.example.com

     

    4.7 修改節點label

      1 [student@workstation ~]$ oc label node node2.lab.example.com region=apps --overwrite=true
      2 [student@workstation ~]$ oc get nodes -L region		#確認修改
      3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
      6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps

     

    4.8 導出dc

      1 [student@workstation ~]$ oc get dc hello -o yaml > dc.yaml

    4.9 修改node2調度策略

    添加dc.yaml中的調度策略,使pod調度至apps標籤的node。

      1 [student@workstation ~]$ vi dc.yaml
      2 ……
      3   template:
      4 ……
      5     spec:
      6       nodeSelector:		#添加節點選擇器
      7         region: apps
      8 ……

     

    4.10 應用更新

      1 [student@workstation ~]$ oc apply -f dc.yaml

    4.11 確認驗證

      1 [student@workstation ~]$ oc get pod -o wide
      2 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
      3 hello-2-4c2gv   1/1       Running   0          40s       10.129.0.42   node2.lab.example.com
      4 hello-2-6966b   1/1       Running   0          38s       10.129.0.43   node2.lab.example.com
      5 hello-2-dcqbr   1/1       Running   0          36s       10.129.0.44   node2.lab.example.com
      6 hello-2-dlf8k   1/1       Running   0          36s       10.129.0.45   node2.lab.example.com
      7 hello-2-rnk4w   1/1       Running   0          40s       10.129.0.41   node2.lab.example.com

     
    #驗證是否觸發了新的部署,並等待所有新的應用pod都準備好並運行。所有5個pod都應該調度至node2。

    4.12 修改node1調度策略

      1 [student@workstation ~]$ oc label node node1.lab.example.com region=apps --overwrite=true
      2 [student@workstation ~]$ oc get node -L region
      3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps
      6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps

     

    4.13 終止node2

      1 [student@workstation ~]$ oc adm manage-node --schedulable=false node2.lab.example.com
      2 NAME                    STATUS                     ROLES     AGE       VERSION
      3 node2.lab.example.com   Ready,SchedulingDisabled   compute   2d        v1.9.1+a0ce1bc657

     

    4.14 刪除pod

    刪除node2的pod,並使用node1創建的pod替換。

      1 [student@workstation ~]$ oc adm drain node2.lab.example.com --delete-local-data

    4.15 查看pod

      1 [student@workstation ~]$ oc get pods -o wide
      2 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
      3 hello-2-bjsj4   1/1       Running   0          51s       10.128.0.25   node1.lab.example.com
      4 hello-2-kmmmn   1/1       Running   0          50s       10.128.0.23   node1.lab.example.com
      5 hello-2-n6wvj   1/1       Running   0          51s       10.128.0.24   node1.lab.example.com
      6 hello-2-plr65   1/1       Running   0          50s       10.128.0.26   node1.lab.example.com
      7 hello-2-xsz68   1/1       Running   0          51s       10.128.0.27   node1.lab.example.com

     

    五 管理IS、image、Templates

    5.1 image介紹

    在OpenShift中,image是一個可部署的runtime模板,它包含運行單個容器的所有需求,還包括imag功能的元數據。image可以通過多種方式管理,如tag、import、pull和update。
    image可以跨多個主機部署在多個容器中。開發人員可以使用Docker構建image,也可以使用OpenShift構建工具。
    OpenShift實現了靈活的image管理機制。一個image名稱實際上可以引用同一image的許多不同版本。唯一的image由它的sha256哈希引用,Docker不使用版本號。相反,它使用tag來管理image,例如v1、v2或默認的latest tag。

    5.2 IS

    IS包括由tags標識的任意數量的容器images。它是相關image的統一虛擬視圖,類似於Docker image倉庫。開發人員有許多與image和IS交互的方法。例如,當添加或修改新image時,build和deployment可以接收通知,並通過運行新build或新deployment做出相應的動作。

    5.3 標記image

    OCP提供了oc tag命令,它類似於docker tag命令,但是,它是對IS而不是image進行操作。
    可以向image添加tag,以便更容易地確定它們包含什麼。tag是指定image版本的標識符。
    示例:將Apache web服務器2.4版本的映像,可將該image執行以下標記。
    apache: 2.4
    如果倉庫包含Apache web服務器的最新版本,他們可以使用latest標籤來表示這是倉庫中可用的最新image。
    apache:latest
    oc tag命令用於標籤image:
    [user@demo ~]$ oc tag source destination
    source:現有tag或圖像流中的圖像。
    destination:標籤在一個或多個IS中的最新image。
    示例:將ruby image的現有latest標記修改為當前版本v2.0標識,
    [user@demo ~]$ oc tag ruby:latest ruby:2.0

    5.4 刪除tag

    若要從image中刪除標記,可使用-d參數。
    [user@demo ~]$ oc tag -d ruby:latest
    可以使用不同類型的標籤,默認行為使用permanent tag,即源文件發生更改,該tag也會及時指向image,與目標tag無關。
    tracking tag指示在導入image期間導入目標tag的元數據。要確保目標tag在源tag更改時得到更新,需使用–alias=true標識。
    [user@demo ~]$ oc tag –alias=true source destination
    要重新導入tag,可使用–scheduled=true標識。
    [user@demo ~]$ oc tag –scheduled=true source destination
    要配置Docker始終從內部倉庫中獲取image,可使用–reference-policy=local標誌。默認情況下,image指向本地倉庫。從而實現在之後調用image的時候可以快速pull。
    [user@demo ~]$ oc tag –reference-policy=local source destination

    5.5 建議的tag形式

    在管理tag時,開發人員應該考慮映像的生命周期,參考下錶開發人員用來管理映像的可能的標記命名約定。

    描述 示例
    Revision myimage:v2.0.1
    Architecture myimage:v2.0-x86_64
    Base Image myimage:v1.2-rhel7
    Latest Image myimage:latest
    Latest Stable Image myimage:stable

    5.6 Templates介紹

    模板描述一組對象,其中包含處理後生成對象列表的參數。可以處理模板來創建開發人員有權在項目中創建的任何內容,例如service、build、configuration和dc。
    模板還可以定義一組標籤,應用於它定義的每個對象。開發人員可以使用命令行界面或web控制台從模板創建對象列表。

    5.7 Templates管理

    開發人員可以用JSON或YAML格式編寫模板,並使用命令行界面或web控制台導入它們。模板被保存到項目中,以供對該特定項目具有適當訪問權限的任何用戶重複使用。
    示例:導入模板。
    [user@demo ~]$ oc create -f filename
    還可以在導入模板時分配標籤,這意味着模板定義的所有對象都將被標記。
    [user@demo ~]$ oc create -f filename -l name=mylabel

    5.8 使用模板

    OCP提供了許多默認的instant app和QuickStart模板,允許開發人員為不同的語言快速創建新的應用程序。為Rails (Ruby)、Django (Python)、Node.js、CakePHP (PHP)和Dancer (Perl)提供了模板。
    要列出集群中的可用模板,請運行oc get templates命令。參數-n指定要使用的項目。
    [user@demo ~]$ oc get templates -n openshift
    開發人員還可以使用web控制台瀏覽模板,當您選擇模板時,可以調整可用的參數來自定義模板定義的資源。

    六 管理IS

    6.1 前置準備

    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    6.2 本練習準備

      1 [student@workstation ~]$ lab schedule-is setup

    6.3 創建項目

      1 [student@workstation ~]$ oc login -u developer -p redhat \
      2 https://master.lab.example.com
      3 [student@workstation ~]$ oc new-project schedule-is

     

    6.4 創建應用

      1 [student@workstation ~]$ oc new-app --name=phpmyadmin \
      2 --docker-image=registry.lab.example.com/phpmyadmin/phpmyadmin:4.7

     

    6.5 創建服務賬戶

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc project schedule-is
      3 [student@workstation ~]$ oc create serviceaccount phpmyadmin-account

     

    6.6 授權特權運行

      1 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid \
      2 -z phpmyadmin-account

     

    6.7 更新pod

      1 [student@workstation ~]$ oc login -u developer
      2 [student@workstation ~]$ oc patch dc/phpmyadmin --patch \
      3 '{"spec":{"template":{"spec":{"serviceAccountName": "phpmyadmin-account"}}}}'

     
    更新負責管理phpmyadmin部署的dc資源,以便使用新創建的服務帳戶。可以使用oc patch或oc edit命令。此命令可以從/home/student/DO280/labs/secure-review文件夾中的patch-dc.sh腳本中複製。

      1 [student@workstation ~]$ oc get pods		#確認驗證
      2 NAME                 READY     STATUS    RESTARTS   AGE
      3 phpmyadmin-2-vh29z   1/1       Running   0          3m

     
    提示:name后的2表示這個pod是第二次部署,即進行過迭代。

    6.8 更新內部倉庫image

      1 [student@workstation ~]$ cd /home/student/DO280/labs/schedule-is/
      2 [student@workstation schedule-is]$ ls
      3 phpmyadmin-latest.tar  trust_internal_registry.sh
      4 [student@workstation schedule-is]$ docker load -i phpmyadmin-latest.tar
      5 #使用docker load命令加載新的image。
      6 [student@workstation schedule-is]$ docker images
      7 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
      8 <none>              <none>              93d0d7db5ce2        13 months ago       166 MB

     

    6.9 tag鏡像

      1 [student@workstation schedule-is]$ docker tag 93d0d7db5ce2 \
      2 docker-registry-default.apps.lab.example.com/schedule-is/phpmyadmin:4.7
      3 #打完標記進行推送。

     

    6.10 登錄docker倉庫


    結論:docker倉庫會提示因為是自簽名證書,因此判定為不安全的方式。

    6.11 修改信任

    本環境使用/home/student/DO280/labs/secure-review文件夾中的trust_internal_registry.sh腳本,配置docker倉庫信任OpenShift內部倉庫。

      1 [student@workstation schedule-is]$ ./trust_internal_registry.sh

    6.12 推送image

      1 [student@workstation schedule-is]$ docker push \
      2 docker-registry-default.apps.lab.example.com/schedule-is/phpmyadmin:4.7

     

    6.13 確認更新

    驗證當源image更新后,是否能自動觸發OpenShift進行pod更新。

      1 [student@workstation schedule-is]$ oc get pods
      2 NAME                 READY     STATUS    RESTARTS   AGE
      3 phpmyadmin-3-hnfjk   1/1       Running   0          23s

     

    七 管理應用部署實驗

    7.1 前置準備

    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    7.2 本練習準備

      1 [student@workstation ~]$ lab manage-review setup

    7.3 確認region

      1 [student@workstation ~]$ oc login -uadmin -predhat https://master.lab.example.com
      2 [student@workstation ~]$ oc get nodes -L region
      3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
      6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra

     

    7.4 修改region

      1 [student@workstation ~]$ oc label node node1.lab.example.com region=services --overwrite=true
      2 [student@workstation ~]$ oc label node node2.lab.example.com region=applications --overwrite=true
      3 [student@workstation ~]$ oc get nodes -L region
      4 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      5 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      6 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   services
      7 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications

     

    7.5 創建項目

      1 [student@workstation ~]$ oc new-project manage-review

    7.6 創建應用

      1 [student@workstation ~]$ oc new-app -i php:7.0 \
      2 http://registry.lab.example.com/version

     

    7.7 擴展應用

      1 [student@workstation ~]$ oc scale dc version --replicas=3
      2 [student@workstation ~]$ oc get pods -o wide		#確認驗證
      3 NAME              READY     STATUS      RESTARTS   AGE       IP            NODE
      4 version-1-9626w   1/1       Running     0          40s       10.129.0.55   node2.lab.example.com
      5 version-1-build   0/1       Completed   0          1m        10.129.0.52   node2.lab.example.com
      6 version-1-f6vj2   1/1       Running     0          40s       10.129.0.56   node2.lab.example.com
      7 version-1-mrhk4   1/1       Running     0          45s       10.129.0.54   node2.lab.example.com

     
    結論:應用程序pod並沒有均分在兩個集群node節點之間,因為每個節點屬於不同的region,並且默認的OpenShift調度器配置打開了區域粘性。

    7.8 調度pod

      1 [student@workstation ~]$ oc export dc version -o yaml > version-dc.yml	#導出yaml
      2 spac
      3 ……
      4   template:
      5     metadata:
      6 ……
      7     spec:
      8       nodeSelector:		#添加節點選擇器
      9         region: applications
     10 ……

     

    7.9 迭代部署

      1 [student@workstation ~]$ oc replace -f version-dc.yml	#迭代

    7.10 確認驗證

      1 [student@workstation ~]$ oc get pod -o wide
      2 NAME              READY     STATUS      RESTARTS   AGE       IP            NODE
      3 version-1-build   0/1       Completed   0          15m       10.129.0.52   node2.lab.example.com
      4 version-2-2bmqq   1/1       Running     0          58s       10.129.0.60   node2.lab.example.com
      5 version-2-nz58r   1/1       Running     0          1m        10.129.0.59   node2.lab.example.com
      6 version-2-rlj2h   1/1       Running     0          1m        10.129.0.58   node2.lab.example.com

     
    驗證是否啟動了新的部署,並且在node2節點上運行了一組新的版本莢。等待所有三個新的應用程序莢都準備好並運行

    7.11 修改region

      1 [student@workstation ~]$ oc label node node1.lab.example.com region=applications --overwrite=true
      2 [student@workstation ~]$ oc get nodes -L region		#確認驗證
      3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications
      6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications

     

    7.12 終止node2

      1 [student@workstation ~]$ oc adm manage-node --schedulable=false node2.lab.example.com
      2 NAME                    STATUS                     ROLES     AGE       VERSION
      3 node2.lab.example.com   Ready,SchedulingDisabled   compute   2d        v1.9.1+a0ce1bc657

     

    7.13 刪除pod

    刪除node2的pod,並使用node1創建的pod替換。

      1 [student@workstation ~]$ oc adm drain node2.lab.example.com --delete-local-data

    7.14 查看pod

      1 [student@workstation ~]$ oc get pods -o wide
      2 NAME              READY     STATUS    RESTARTS   AGE       IP            NODE
      3 version-2-d9fhp   1/1       Running   0          3m        10.128.0.34   node1.lab.example.com
      4 version-2-jp5gr   1/1       Running   0          3m        10.128.0.35   node1.lab.example.com
      5 version-2-z5lv5   1/1       Running   0          3m        10.128.0.33   node1.lab.example.com

     

    7.15 暴露服務

      1 [student@workstation ~]$ oc expose service version --hostname=version.apps.lab.example.com
      2 [student@workstation ~]$ curl http://version.apps.lab.example.com	#確認測試
      3 <html>
      4  <head>
      5   <title>PHP Test</title>
      6  </head>
      7  <body>
      8  <p>Version v1</p>
      9  </body>
     10 </html>

     

    7.16 確認驗證

      1 [student@workstation ~]$ lab manage-review grade	#環境腳本判斷

    7.17 還原環境

      1 [student@workstation ~]$ oc adm manage-node --schedulable=true node2.lab.example.com
      2 [student@workstation ~]$ oc label node node1.lab.example.com region=infra --overwrite=true
      3 [student@workstation ~]$ oc label node node2.lab.example.com region=infra --overwrite=true
      4 [student@workstation ~]$ oc get node -L region
      5 NAME                     STATUS    ROLES     AGE       VERSION             REGION
      6 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
      7 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
      8 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
      9 [student@workstation ~]$ oc delete project manage-review

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

    【其他文章推薦】

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

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

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

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

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

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

  • 走出舒適圈的信念和勇氣——“Learning by doing!” 我的軟工2020春季教學總結

          看着大家陸續提交個人學期總結,我還不敢去翻看,怕思緒紛飛思維定式,變了自己寫總結的初心和思路。一篇總結,開頭起筆尤是最難,總是想各式各樣的開頭,翻來覆去,寫了念,念了刪,寫了念,念了刪。還是回到初心,回首一下為什麼上這門課。經過一個學期,我的態度和想法有沒有變化,我的收穫和驚喜是什麼。

           為什麼我想上這個班?
           過去沒有一次課,會像這次一樣,具有一點使命感。過去也沒有一次課,全程都是線上進行。也沒有一次課,如果放大忐忑的心情,也會挺忐忑的。
           作為一門臨時穿插安排的選修課,上課前,我聽到的關於系裡同學的水平和积極主動性,負向居多。如果課程要求高和嚴,還可能一翻兩瞪眼,學生投訴或紛紛消極放棄,那麼我大多數的時間精力可能都會用來勸說、解釋和雞湯。如果上的太差,也有可能搞砸後續我想進行的課程教學改革和質量提升。雖然考慮過這些困難,但相比於還沒有為系裡學生完整上過一門課,還沒有近距離感受不同學生的精彩作品和風格,還沒有為後續其他課程的改革做探索和調研,前面那些疑慮早已經拋到九霄雲外,只有許多期待和興奮感。

           你們給我的驚喜:

           你們從個人Github開始,也結束於團隊協作的GitHub,而且不少組做的很不錯,互幫互學,其團隊Github實踐能力,遠勝過以往我的班級;
           你們其中有的組分享的Android或小程序開發的經驗和教程,寫的用心,媲美以往我的班級;
           你們不少組用上了一些自動化測試工具,而且妥帖,用的好,遠勝過以往我的班級;
           更難能可貴的是,你們的不少作品,都離用戶很近,教務課程表、查寢點名、圖書館佔座、校園失物招領……等等,都將可以被用得上,希望你們不斷將作品成型,離之更近。
           不少同學的能力和潛質,都讓我覺得相見恨晚,也相識太短。匆匆你們也就要進入畢業年級。什麼是課程?就是拋卻那些具體的什麼理論和知識,回憶自己能留下的,就是這門課要教給你的。 如果問我,我們短暫的線上相聚,這門課,要交給你們什麼呢?是“Learing by doing”嗎? 這也許是之一。做中學,其實就是做自己所不會的。我們常有的觀念是,我不會,所以我做不了。而“Learning by doing”給我們的勇氣和信念是:做我所不會的,但又是對於自己發展非常重要的,甚至是關鍵路徑上的實現和突破。換句簡單的話來說,這門課想交(交,不是教,我沒有寫錯別字)給你們的是:走出舒適圈。其中的信念和勇氣就是,我可以“Learning by doing!”

          你們給了我更多在專業推行課程實踐改革的信心和動力。信心是:你們有這麼多潛質和能力,怎麼就不能做成項目,達成自己的能力提升呢? 動力是:在你們最好的時節,遇到你們,如果我們不能抓住這樣的機會,把握這樣的機會,做的更好,錯過了,將可惜許多未來的你們。成為系裡不少項目的開發者、成為課程核心助教、成為我們改革的初創者和開拓者,是這門課和這個學期,你們給我的最大收穫。

          就算是自賦的使命感吧,我想,當初來,並不是讓自己來掙課時費的。希望能了解現狀,立足現實,理解問題,分析原因,給出方法,執意推行,做出成果。前四點,無論老師或是學生,多多少少都有感慨和認知。不少知名的企業家,也常常在不同場合,對義務教育、高等教育提出屬於自己的真知灼見,大多數時是痛心疾首,哀其不幸,怒其不爭,覺得應該這樣改那樣改。為什麼問題顯而易見,現狀人人不滿(至少是不滿意),但改變卻牛步而行,各層次教育依然故我。人人都能對教育發表評論,因為重要,教育關係千家萬戶,關係國計民生,關係百年基業;也因為平凡,人人都受過教育,當過學生,也大多教育別人(比如養兒育女)。但這些其實是一種錯覺。企業也很重要啊,但少有人能夠對企業經營管理指摘或評論,很少其他人指導企業經營者應該怎麼做。我想,原因可能在距離。是否直接與學生互動,感受和方法,可能會真的不一樣。教學不是做菜,學生不是食材,互動勝過一切。這也是慕課為什麼知易行難,選課人數多,堅持人數微乎其微的原因。即使堅持了,效果也遠遠不如近距離教學的收穫和感受。評論或建議教育的人的錯覺,就在於希望自己的想法能被教育者一以貫之,卻常常忽略受教育者的感受、過程、反饋和互動。教學相長,如同沒有一次軟件開發項目是可以完全一樣完全照搬的,也沒有一次課程教學是可以完全一樣完全照搬的。更沒有什麼理論或建議,是可以醍醐灌頂,直接有效快速解決教育難題的。孔聖人之所以較其他名家更偉大一些,稱為至聖先師,除了有真知灼見,更在於自己帶領弟子三千,是真正戰鬥在教育一線的教育者。與學生的互動,一問一答,一段經歷,一個故事,乃至於對學生的點評,都成為後人傳頌的至理名言。將距離拉近,師生作為教學相長的團隊一體,才能相互促進,提升和改變。
         《構建之法》的作者,也正是將在清華、北航、微軟亞洲研究院的教學實踐的課程講義,凝結成書,並不斷在教學實踐中推陳出新,改版完善,才讓書具有了強大和茁壯的生命力,書中凝練的教學做法不斷推廣鋪開和可持續化,成為不少其他教學研討場合里都會提到的話題。回到根源,是經過實踐檢驗的“Learning by doing”才具有了這樣的生命力。如何繼往開來,我想,也要回到這樣的初心。不斷實踐,不斷改變。比如,線上教學,是可以發揮這樣的優勢的。作者對某些博客夜以繼日或苦心費思的點評,礙於單篇博客本身的閱讀量,點評被看到率不算高,回復率更讓人着急,也不一定能夠符合這個時代視頻影音影響力的特點。所以,正是線上教學,視頻直播,考慮到傳播力、影響力、互動性,作者可以考慮不斷前進,為不同開展的學校,做一次線上互動的軟工講座,分享軟件工程思維、案例、心得,和互動問答,這些,都將長久影響不少學生,也能逐步累積與一線學生互動的思考、感受和來自廣大讀者受眾的聲音。對作者來說,這一步,其實也是走出舒適圈,“Learning by doing!”

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

    【其他文章推薦】

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

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

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

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

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

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

  • Tensorflow2 自定義數據集圖片完成圖片分類任務

    Tensorflow2 自定義數據集圖片完成圖片分類任務

    對於自定義數據集的圖片任務,通用流程一般分為以下幾個步驟:

    • Load data

    • Train-Val-Test

    • Build model

    • Transfer Learning

    其中大部分精力會花在數據的準備和預處理上,本文用一種較為通用的數據處理手段,並通過手動構建,簡單模型, 層數較深的resnet網絡,和基於VGG19的遷移學習。

    你可以通過這個例子,快速搭建網絡,並訓練處一個較為滿意的結果。

    1. Load data

    數據集來自Pokemon的5分類數據, 每一種的圖片數量為200多張,是一個較小型的數據集。

    官方項目鏈接:

    Keras and Convolutional Neural Networks (CNNs)

    1.1 數據集介紹

    Pokemon文件夾中包含5個子文件,其中每個子文件夾名為對應的類別名。文件夾中包含有png, jpeg的圖片文件。

    1.2 解題思路

    • 由於文件夾中沒有劃分,訓練集和測試集,所以需要構建一個csv文件讀取所有的文件,及其類別

    • shuffle數據集以後,劃分Train_val_test

    • 對數據進行預處理, 數據標準化,數據增強, 可視化處理

    “””python
    # 創建数字編碼錶

      import os
      import glob
      import random
      import csv
      import tensorflow as tf
      from tensorflow import keras
      import matplotlib.pyplot as plt
      import time
      
      
      def load_csv(root, filename, name2label):
          """
          將分散在各文件夾中的圖片, 轉換為圖片和label對應的一個dataset文件, 格式為csv
          :param root: 文件路徑(每個子文件夾中的文件屬於一類)
          :param filename: 文件名
          :param name2label: 類名編碼錶  {'類名1':0, '類名2':1..}
          :return: images, labels
          """
          # 判斷是否csv文件已經生成
          if not os.path.exists(os.path.join(root, filename)):  # join-將路徑與文件名何為一個路徑並返回(沒有會生成新路徑)
              images = []  # 存的是文件路徑
              for name in name2label.keys():
                  # pokemon\pikachu\00000001.png
                  # glob.glob() 利用通配符檢索路徑內的文件,類似於正則表達式
                  images += glob.glob(os.path.join(root, name, '*'))  # png, jpg, jpeg
              print(name2label)
              print(len(images), images)
      
              random.shuffle(images)
      
              with open(os.path.join(root, filename), 'w', newline='') as f:
                  writer = csv.writer(f)
                  for img in images:
                      name = img.split(os.sep)[1]  # os.sep 表示分隔符 window-'\\' , linux-'/'
                      label = name2label[name]  # 0, 1, 2..
                      # 'pokemon\\bulbasaur\\00000000.png', 0
                      writer.writerow([img, label])  # 如果不設定newline='', 2個數據會分為2行寫
                  print('write into csv file:', filename)
      
          # 讀取現有文件
          images, labels = [], []
          with open(os.path.join(root, filename)) as f:
              reader = csv.reader(f)
              for row in reader:
                  # 'pokemon\\bulbasaur\\00000000.png', 0
                  img, label = row
                  label = int(label)  # str-> int
                  images.append(img)
                  labels.append(label)
      
          assert len(images) == len(labels)
      
          return images, labels
      
      
      def load_pokemon(root, mode='train'):
          """
          # 創建数字編碼錶
          :param root: root path
          :param mode: train, valid, test
          :return: images, labels, name2label
          """
      
          name2label = {}  # {'bulbasaur': 0, 'charmander': 1, 'mewtwo': 2, 'pikachu': 3, 'squirtle': 4}
          for name in sorted(os.listdir(os.path.join(root))):
              # sorted() 是為了復現結果的一致性
              # os.listdir - 返迴路徑下的所有文件(文件夾,文件)列表
              if not os.path.isdir(os.path.join(root, name)):  # 是否為文件夾且是否存在
                  continue
              # 每個類別編碼一個数字
              name2label[name] = len(name2label)
      
          # 讀取label
          images, labels = load_csv(root, 'images.csv', name2label)
      
          # 劃分數據集 [6:2:2]
          if mode == 'train':
              images = images[:int(0.6 * len(images))]
              labels = labels[:int(0.6 * len(labels))]  # len(images) == len(labels)
      
          elif mode == 'valid':
              images = images[int(0.6 * len(images)):int(0.8 * len(images))]
              labels = labels[int(0.6 * len(labels)):int(0.8 * len(labels))]
      
          else:
              images = images[int(0.8 * len(images)):]
              labels = labels[int(0.8 * len(labels)):]
      
          return images, labels, name2label
      
      
      # imagenet 數據集均值, 方差
      img_mean = tf.constant([0.485, 0.456, 0.406])  # 3 channel
      img_std = tf.constant([0.229, 0.224, 0.225])
      
      def normalization(x, mean=img_mean, std=img_std):
          # [224, 224, 3]
          x = (x - mean) / std
          return x
      
      def denormalization(x, mean=img_mean, std=img_std):
          x = x * std + mean
          return x
      
      
      def preprocess(x, y):
          # x: path, y: label
          x = tf.io.read_file(x)  # 2進制
          # x = tf.image.decode_image(x)
          x = tf.image.decode_jpeg(x, channels=3)  # RGBA
          x = tf.image.resize(x, [244, 244])
      
          # data augmentation
          # x = tf.image.random_flip_up_down(x)
          x = tf.image.random_flip_left_right(x)
          x = tf.image.random_crop(x, [224, 224, 3])  # 模型縮減比例不宜過大,否則會增大訓練難度
      
          x = tf.cast(x, dtype=tf.float32) / 255. # unit8 -> float32
          # U[0,1] -> N(0,1)  # 提高訓練準確度
          x = normalization(x)
      
          y = tf.convert_to_tensor(y)
      
          return x, y
      
      def main():
          images, labels, name2label = load_pokemon('pokemon', 'train')
          print('images:', len(images), images)
          print('labels:', len(labels), labels)
          # print(name2label)
      
          # .map()函數要位於.batch()之前, 否則 x=tf.io.read_file()會一次讀取一個batch的圖片,從而報錯
          db = tf.data.Dataset.from_tensor_slices((images, labels)).map(preprocess).shuffle(1000).batch(32)
      
          # tf.summary()
          # 提供了各類方法(支持各種多種格式)用於保存訓練過程中產生的數據(比如loss_value、accuracy、整個variable),
          # 這些數據以日誌文件的形式保存到指定的文件夾中。
      
          # 數據可視化:而tensorboard可以將tf.summary()
          # 記錄下來的日誌可視化,根據記錄的數據格式,生成折線圖、統計直方圖、圖片列表等多種圖。
          # tf.summary()
          # 通過遞增的方式更新日誌,這讓我們可以邊訓練邊使用tensorboard讀取日誌進行可視化,從而實時監控訓練過程。
          writer = tf.summary.create_file_writer('logs')
          for step, (x, y) in enumerate(db):
              with writer.as_default():
                  x = denormalization(x)
                  tf.summary.image('img', x, step=step, max_outputs=9)  # STEP:默認選項,指的是橫軸显示的是訓練迭代次數
      
                  time.sleep(5)
      
      
      
      if __name__ == '__main__':
          main()
    

    “””

    2. 構建模型進行訓練

    2.1 自定義小型網絡

    由於數據集數量較少,大型網絡的訓練中往往會出現過擬合情況,這裏就定義了一個2層卷積的小型網絡。
    引入early_stopping回調函數后,3個epoch沒有較大變化的情況下,模型訓練的準確率為0.8547

    “””
    # 1. 自定義小型網絡
    model = keras.Sequential([
    layers.Conv2D(16, 5, 3),
    layers.MaxPool2D(3, 3),
    layers.ReLU(),
    layers.Conv2D(64, 5, 3),
    layers.MaxPool2D(2, 2),
    layers.ReLU(),
    layers.Flatten(),
    layers.Dense(64),
    layers.ReLU(),
    layers.Dense(5)
    ])

      model.build(input_shape=(None, 224, 224, 3))  
      model.summary()
      
      early_stopping = EarlyStopping(
          monitor='val_loss',
          patience=3,
          min_delta=0.001
      )
      
      
      model.compile(optimizer=optimizers.Adam(lr=1e-3),
                     loss=losses.CategoricalCrossentropy(from_logits=True),
                     metrics=['accuracy'])
      model.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
                 callbacks=[early_stopping])
      model.evaluate(db_test)
    

    “””

    2.2 自定義的Resnet網絡

    resnet 網絡對於層次較深的網絡的可訓練型提升很大,主要是通過一個identity layer保證了深層次網絡的訓練效果不會弱於淺層網絡。
    其他文章中有詳細介紹resnet的搭建,這裏就不做贅述, 這裏構建了一個resnet18網絡, 準確率0.7607。

    “””
    import os

      import numpy as np
      import tensorflow as tf
      from tensorflow import keras
      from tensorflow.keras import layers
      
      tf.random.set_seed(22)
      np.random.seed(22)
      os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
      assert tf.__version__.startswith('2.')
      
      
      class ResnetBlock(keras.Model):
      
          def __init__(self, channels, strides=1):
              super(ResnetBlock, self).__init__()
      
              self.channels = channels
              self.strides = strides
      
              self.conv1 = layers.Conv2D(channels, 3, strides=strides,
                                         padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
              self.bn1 = keras.layers.BatchNormalization()
              self.conv2 = layers.Conv2D(channels, 3, strides=1,
                                         padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
              self.bn2 = keras.layers.BatchNormalization()
      
              if strides != 1:
                  self.down_conv = layers.Conv2D(channels, 1, strides=strides, padding='valid')
                  self.down_bn = tf.keras.layers.BatchNormalization()
      
          def call(self, inputs, training=None):
              residual = inputs
      
              x = self.conv1(inputs)
              x = tf.nn.relu(x)
              x = self.bn1(x, training=training)
              x = self.conv2(x)
              x = tf.nn.relu(x)
              x = self.bn2(x, training=training)
      
              # 殘差連接
              if self.strides != 1:
                  residual = self.down_conv(inputs)
                  residual = tf.nn.relu(residual)
                  residual = self.down_bn(residual, training=training)
      
              x = x + residual
              x = tf.nn.relu(x)
              return x
      
      
      class ResNet(keras.Model):
      
          def __init__(self, num_classes, initial_filters=16, **kwargs):
              super(ResNet, self).__init__(**kwargs)
      
              self.stem = layers.Conv2D(initial_filters, 3, strides=3, padding='valid')
      
              self.blocks = keras.models.Sequential([
                  ResnetBlock(initial_filters * 2, strides=3),
                  ResnetBlock(initial_filters * 2, strides=1),
                  # layers.Dropout(rate=0.5),
      
                  ResnetBlock(initial_filters * 4, strides=3),
                  ResnetBlock(initial_filters * 4, strides=1),
      
                  ResnetBlock(initial_filters * 8, strides=2),
                  ResnetBlock(initial_filters * 8, strides=1),
      
                  ResnetBlock(initial_filters * 16, strides=2),
                  ResnetBlock(initial_filters * 16, strides=1),
              ])
      
              self.final_bn = layers.BatchNormalization()
              self.avg_pool = layers.GlobalMaxPool2D()
              self.fc = layers.Dense(num_classes)
      
          def call(self, inputs, training=None):
              # print('x:',inputs.shape)
              out = self.stem(inputs, training = training)
              out = tf.nn.relu(out)
      
              # print('stem:',out.shape)
      
              out = self.blocks(out, training=training)
              # print('res:',out.shape)
      
              out = self.final_bn(out, training=training)
              # out = tf.nn.relu(out)
      
              out = self.avg_pool(out)
      
              # print('avg_pool:',out.shape)
              out = self.fc(out)
      
              # print('out:',out.shape)
      
              return out
      
      
      def main():
          num_classes = 5
      
          resnet18 = ResNet(5)
          resnet18.build(input_shape=(None, 224, 224, 3))
          resnet18.summary()
      
      
      if __name__ == '__main__':
          main()
    

    “””

    “””
    # 2.resnet18訓練, 圖片數量較小,訓練結果不是特別好
    # resnet = ResNet(5) # 0.7607
    # resnet.build(input_shape=(None, 224, 224, 3))
    # resnet.summary()
    “””

    2.3 VGG19遷移學習

    遷移學習利用了數據集之間的相似性,對於數據集數量較少的時候,訓練效果會遠優於其他。
    在訓練過程中,使用include_top=False, 去掉最後分類的基層Dense, 重新構建並訓練就可以了。準確率0.9316

    “””
    # 3. VGG19遷移學習,遷移學習利用數據集之間的相似性, 結果遠好於其他2種
    # 為了方便,這裏仍然使用resnet命名
    net = tf.keras.applications.VGG19(weights=’imagenet’, include_top=False, pooling=’max’ )
    net.trainable = False
    resnet = keras.Sequential([
    net,
    layers.Dense(5)
    ])
    resnet.build(input_shape=(None, 224, 224, 3)) # 0.9316
    resnet.summary()

      early_stopping = EarlyStopping(
          monitor='val_loss',
          patience=3,
          min_delta=0.001
      )
      
      
      resnet.compile(optimizer=optimizers.Adam(lr=1e-3),
                     loss=losses.CategoricalCrossentropy(from_logits=True),
                     metrics=['accuracy'])
      resnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
                 callbacks=[early_stopping])
      resnet.evaluate(db_test)
    

    “””

    附錄:

    train_scratch.py 代碼

    “””

    import os
    
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    
    import tensorflow as tf
    import numpy as np
    from tensorflow import keras
    from tensorflow.keras import layers, optimizers, losses
    from tensorflow.keras.callbacks import EarlyStopping
    
    tf.random.set_seed(22)
    np.random.seed(22)
    assert tf.__version__.startswith('2.')
    
    # 設置GPU顯存按需分配
    # gpus = tf.config.experimental.list_physical_devices('GPU')
    # if gpus:
    #     try:
    #         # Currently, memory growth needs to be the same across GPUs
    #         for gpu in gpus:
    #             tf.config.experimental.set_memory_growth(gpu, True)
    #         logical_gpus = tf.config.experimental.list_logical_devices('GPU')
    #         print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
    #     except RuntimeError as e:
    #         # Memory growth must be set before GPUs have been initialized
    #         print(e)
    
    from pokemon import load_pokemon, normalization
    from resnet import ResNet
    
    
    def preprocess(x, y):
        # x: 圖片的路徑,y:圖片的数字編碼
        x = tf.io.read_file(x)
        x = tf.image.decode_jpeg(x, channels=3)  # RGBA
        # 圖片縮放
        # x = tf.image.resize(x, [244, 244])
        # 圖片旋轉
        # x = tf.image.rot90(x,2)
        # 隨機水平翻轉
        x = tf.image.random_flip_left_right(x)
        # 隨機豎直翻轉
        # x = tf.image.random_flip_up_down(x)
    
        # 圖片先縮放到稍大尺寸
        x = tf.image.resize(x, [244, 244])
        # 再隨機裁剪到合適尺寸
        x = tf.image.random_crop(x, [224, 224, 3])
    
        # x: [0,255]=> -1~1
        x = tf.cast(x, dtype=tf.float32) / 255.
        x = normalization(x)
        y = tf.convert_to_tensor(y)
        y = tf.one_hot(y, depth=5)
    
        return x, y
    
    
    batchsz = 32
    
    # create train db
    images1, labels1, table = load_pokemon('pokemon', 'train')
    db_train = tf.data.Dataset.from_tensor_slices((images1, labels1))
    db_train = db_train.shuffle(1000).map(preprocess).batch(batchsz)
    # create validation db
    images2, labels2, table = load_pokemon('pokemon', 'valid')
    db_val = tf.data.Dataset.from_tensor_slices((images2, labels2))
    db_val = db_val.map(preprocess).batch(batchsz)
    # create test db
    images3, labels3, table = load_pokemon('pokemon', mode='test')
    db_test = tf.data.Dataset.from_tensor_slices((images3, labels3))
    db_test = db_test.map(preprocess).batch(batchsz)
    
    
    # 1. 自定義小型網絡
    # resnet = keras.Sequential([
    #     layers.Conv2D(16, 5, 3),
    #     layers.MaxPool2D(3, 3),
    #     layers.ReLU(),
    #     layers.Conv2D(64, 5, 3),
    #     layers.MaxPool2D(2, 2),
    #     layers.ReLU(),
    #     layers.Flatten(),
    #     layers.Dense(64),
    #     layers.ReLU(),
    #     layers.Dense(5)
    # ])  # 0.8547
    
    
    # 2.resnet18訓練, 圖片數量較小,訓練結果不是特別好
    # resnet = ResNet(5)  # 0.7607
    # resnet.build(input_shape=(None, 224, 224, 3))
    # resnet.summary()
    
    
    # 3. VGG19遷移學習,遷移學習利用數據集之間的相似性, 結果遠好於其他2種
    net = tf.keras.applications.VGG19(weights='imagenet', include_top=False, pooling='max' )
    net.trainable = False
    resnet = keras.Sequential([
        net,
        layers.Dense(5)
    ])
    resnet.build(input_shape=(None, 224, 224, 3))   # 0.9316
    resnet.summary()
    
    early_stopping = EarlyStopping(
        monitor='val_loss',
        patience=3,
        min_delta=0.001
    )
    
    
    resnet.compile(optimizer=optimizers.Adam(lr=1e-3),
                   loss=losses.CategoricalCrossentropy(from_logits=True),
                   metrics=['accuracy'])
    resnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
               callbacks=[early_stopping])
    resnet.evaluate(db_test)
    

    “””

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

    【其他文章推薦】

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

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

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

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

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

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

  • 三文搞懂學會Docker容器技術(上)

    三文搞懂學會Docker容器技術(上)

    1,Docker簡介

      1.1 Docker是什麼?

    Docker官網: https://www.docker.com/

    Docker 是一個開源的應用容器引擎,基於 Go 語言 並遵從Apache2.0協議開源。
    Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後發布到任何流行的 Linux 機器上,也可以實現虛擬化。
    容器是完全使用沙箱機制,相互之間不會有任何接口(類似 iPhone 的 app),更重要的是容器性能開銷極低。
    Docker 從 17.03 版本之後分為 CE(Community Edition: 社區版) 和 EE(Enterprise Edition: 企業版),我們用社區版就可以了。

      1.2 Docker架構原理?

     

    Docker三要素,鏡像,容器,倉庫

    1.鏡像

    Docker 鏡像(Image)就是一個只讀的模板,它可以是一個可運行軟件(tomcat,mysql),也可以是一個系統(centos)。鏡像可以用來創建 Docker 容器,一個鏡像可以創建很多容器。

    2.容器

    Docker 利用容器(Container)獨立運行的一個或一組應用。容器是用鏡像創建的運行實例。它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的、保證安全的平台。可以把容器看做是一個簡易版的 Linux 環境(包括root用戶權限、進程空間、用戶空間和網絡空間等)和運行在其中的應用程序。容器的定義和鏡像幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

    3.倉庫

    倉庫(Repository)是集中存放鏡像文件的場所,類似GitHub存放項目代碼一樣,只不過Docker Hub是由來存鏡像(image)的。倉庫(Repository)和倉庫註冊服務器(Registry)是有區別的。倉庫註冊服務器上往往存放着多個倉庫,每個倉庫中又包含了多個鏡像,每個鏡像有不同的標籤(tag,類似版本號)。

    倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

    最大的公開倉庫是 Docker Hub(https://hub.docker.com/),存放了數量龐大的鏡像供用戶下載。國內的公開倉庫包括阿里雲 、網易雲 等。

     

    容器與鏡像的關係類似於面向對象編程中的對象與類。

    Docker 面向對象
    容器 對象
    鏡像

      1.3 Docker有什麼用?

        1,簡化環境搭建,提高開發生命周期效率;

        2,大大簡化運維工作量;

        3,微服務利器;

      1.4 Docker容器與虛擬機區別?

    Docker是一種輕量級的虛擬化技術,比傳統的虛擬機性能更好。

    下圖是虛擬機的體繫結構:

     

    • server – 表示真實電腦。
    • Host OS – 真實電腦的操作系統,例如:Windows,Linux
    • Hypervisor – 虛擬機平台,模擬硬件,如VMWare,VirtualBox
    • Guest OS – 虛擬機平台上安裝的操作系統,例如CentOS Linux
    • App – 虛擬機操作系統上的應用,例如nginx

     

    下圖是Docker的體繫結構:

    • server – 表示真實電腦。
    • Host OS – 真實電腦的操作系統,例如:Windows,Linux
    • Docker Engine – 新一代虛擬化技術,不需要包含單獨的操作系統。
    • App – 所有的應用程序現在都作為Docker容器運行。

     

    這種體繫結構的明顯優勢是,不需要為虛擬機操作系統提供硬件模擬。所有應用程序都作為Docker容器工作,性能更好。

      Docker容器 虛擬機(VM)
    操作系統 與宿主機共享OS 宿主機OS上運行宿主機OS
    存儲大小 鏡像小,便於存儲與傳輸 鏡像龐大(vmdk等)
    運行性能 幾乎無額外性能損失 操作系統額外的cpu、內存消耗
    移植性 輕便、靈活、適用於Linux 笨重、與虛擬化技術耦合度高
    硬件親和性  面向軟件開發者 面向硬件運維者

     

    Docker優點:輕量級,速度快,運行應用隔離,方便維護…

    2,Docker安裝

      2.1 Docker版本介紹

    Docker從1.13版本之後採用時間線的方式作為版本號,分為社區版CE和企業版EE。

    社區版是免費提供給個人開發者和小型團體使用的,企業版會提供額外的收費服務,比如經過官方測試認證過的基礎設施、容器、插件等。

    社區版按照stable和edge兩種方式發布,每個季度更新stable版本,如17.06,17.09;每個月份更新edge版本,如17.09,17.10。

    我們平時用社區版就足夠了。所以我們安裝社區版;

      2.2 Docker安裝官方文檔

    我們主要參考:https://docs.docker.com/install/linux/docker-ce/centos/  來安裝;

      2.3 工具準備

    前置課程:Centos課程  http://www.java1234.com/javaxuexiluxiantu.html

    打包下載: http://pan.baidu.com/s/1i55jJAt

    虛擬機 VMware

    centos7安裝下虛擬機VM上;

    連接工具 才用 FinalShell  官方地址:http://www.hostbuf.com/

      2.4 Docker安裝步驟

    我們切換到root用戶

    1、Docker 要求 CentOS 系統的內核版本高於 3.10 ,查看本頁面的前提條件來驗證你的CentOS 版本是否支持 Docker 。

    通過 uname -r 命令查看你當前的內核版本

     $ uname -r

    2、使用 root 權限登錄 Centos。確保 yum 包更新到最新。

    $ yum update

    3、卸載舊版本(如果安裝過舊版本的話)

    $ yum remove docker  docker-common docker-selinux docker-engine

    4、安裝需要的軟件包, yum-util 提供yum-config-manager功能,另外兩個是devicemapper驅動依賴的

    $ yum install -y yum-utils device-mapper-persistent-data lvm2

    5、設置yum源

    $ yum-config-manager –add-repo https://download.docker.com/linux/centos/docker-ce.repo

    6,安裝最新版本的Docker

    $ yum install docker-ce docker-ce-cli containerd.io

    7,啟動Docker並設置開機啟動

    $ systemctl start docker

    $ systemctl enable docker

    8,驗證Docker

    $ docker version

     

    說明安裝OK;

    9,Docker HelloWorld測試;

    $ docker run hello-world

     

    因為本地沒有這個鏡像,所以從遠程官方倉庫去拉取,下載;

    然後我們再執行一次;

     

    OK了

      2.5 Docker配置阿里雲鏡像倉庫

    Docker默認遠程倉庫是 https://hub.docker.com/

    比如我們下載一個大點的東西,龜速

     

    由於是國外主機,類似Maven倉庫,慢得一腿,經常延遲,破損;

    所以我們一般都是配置國內鏡像,比如阿里雲,網易雲等;推薦阿里雲,穩定點;

    配置步驟如下:

    1,登錄進入阿里雲鏡像服務中心,獲取鏡像地址

    進入阿里雲容器鏡像服務地址:點這裏快速進入

    使用你的淘寶賬號密碼登錄

     

    這裏我們獲取鏡像地址;

    2,在/etc/docker目錄下找到在daemon.json文件(沒有就新建),將下面內容寫入

    {

     “registry-mirrors”: [“https://xxxxxxx.mirror.aliyuncs.com”]

    }

    3,重啟daemon

    systemctl daemon-reload

    4,重啟docker服務

    systemctl restart docker

    5,測試

    由於速度太快,截圖都難;

     

    3,HelloWorld運行原理

    運行  docker run hello-world

    本地倉庫未能找到該鏡像,然後去遠程倉庫尋找以及下載該鏡像;

    然後我們再執行該命令:

    出來了 Hellowold。我們具體來分析下 執行原理和過程;

    從左到右 client客戶端,Docker運行主機,遠程倉庫;

    docker build ,pull,run分別是 構建,拉取,運行命令,後面再細講;

    中間Docker主機里有 Docker daemon主運行線程,以及Containers容器,容器里可以運行很多實例,(實例是從右側Images鏡像實例化出來的)Images是存儲再本地的鏡像文件,比如 Redis,Tomat這些鏡像文件;

    右側是Registry鏡像倉庫,默認遠程鏡像倉庫 https://hub.docker.com/  不過是國外主機,下載很慢,不穩定,所以我們後面要配置成阿里雲倉庫鏡像地址,穩定快捷;

    執行 docker run hello-world的過程看如下圖例:

     

     

     

    4,Docker基本命令

       4.1 啟動Docker

               systemctl start docker

      4.2 停止Docker

             systemctl stop docker

      4.3 重啟Docker

           systemctl restart docker

      4.4 開機啟動Docker

         systemctl enable docker

      4.5 查看Docker概要信息

       docker info

      4.6 查看Docker幫助文檔

       docker –help

      4.7 查看Docker版本信息

         docker version

    5,Docker鏡像

      5.1 docker images 列出本機所有鏡像

     

    REPOSITORY 鏡像的倉庫源
    TAG 鏡像的標籤(版本)同一個倉庫有多個TAG的鏡像,多個版本;我們用REPOSITORY:TAG來定義不同的鏡像;
    IMAGE ID 鏡像ID,鏡像的唯一標識
    CREATE 鏡像創建時間
    SIZE 鏡像大小

    OPTIONS 可選參數:

    -a 显示所有鏡像(包括中間層)
    q 只显示鏡像ID
    -qa 可以組合
    –digests 显示鏡像的摘要信息
    –no-trunc 显示完整的鏡像信息 

     

      5.2 docker search 搜索鏡像

    和 https://hub.docker.com/ 這裏的搜索效果一樣;

    OPTIONS可選參數:

    –no-trunc 显示完整的鏡像描述
    -s 列出收藏數不小於指定值的鏡像
    –automated 只列出Docker Hub自動構建類型的鏡像

     

     

     

      5.3 docker pull 下載鏡像

    docker pull 鏡像名稱:[TAG]

    注意:不加TAG,默認下載最新版本latest

      5.4 docker rmi 刪除鏡像

    1,刪除單個:docker rmi 鏡像名稱:[TAG]

    如果不寫TAG,默認刪除最新版本latest

    有鏡像生成的容器再運行時候,會報錯,刪除失敗;

    我們需要加 -f 強制刪除

    2,刪除多個:docker rmi -f 鏡像名稱1:[TAG] 鏡像名稱2:[TAG]

    中間空格隔開

    3,刪除全部:docker rmi -f $(docker images -qa)

     

     

    ——————————————————————————————————————————

    作者: java1234_小鋒

    出處:https://www.cnblogs.com/java688/p/13132444.html

    版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

    ——————————————————————————————————————————

     

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

    【其他文章推薦】

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

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

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

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

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

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

  • 熱浪席捲 澳洲全國均溫40.9度創新高

    摘錄自2019年12月18日中央社報導

    澳17日出現有紀錄以來最熱的一天,氣象局測到全國平均氣溫高達攝氏40.9度(華氏105.6度)。先前澳洲高溫紀錄為2013年1月時創下的攝氏40.3度。

    在近日野火肆虐澳洲東岸,又有熱浪席捲各地,氣候狀況更形惡劣的情況下,高溫紀錄預料很快就會再寫新高。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 福島核污染物遭沖走外洩 韓國要求日本提供資料

    摘錄自2019年10月21日自由時報報導

    哈吉貝颱風侵襲日本,造成許多地區出現慘重水患,放置福島核電事故輻射污染物的臨時貯存場也遭大水淹沒,部分裝有輻射污染廢棄物的袋子沖入河中,輻射廢棄物不翼而飛,韓國核電安全委員會要求日本提供該事件相關資料。

    據《韓聯社》報導,韓核電安全委員會委員長嚴在植21日透露,針對福島輻射污染物垃圾袋被沖走一事,已要求日本駐韓大使館提供相關資料,日前韓奧委會也在與國際奧會(IOC)主席巴赫(Thomas Bach)會談時,向巴赫提出福島輻射污染物洩漏事件,巴赫表示會計畫確認情況。

    日本環境省表示,目前在河道的輻射量並沒有變化,認為這些流出的放射性物質濃度較低,對環境不會有影響。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 亞洲訂單能見度佳 台廠第四季有望淡季不淡

    雙反最終以定價定量收場,雖然結局讓台廠短期內並未受惠中國廠商的轉單效益。但法人表示,台廠在中歐貿易戰平息後,企業策略布局已進行新一波調整,重心除轉回到中國大陸內需市場,也著重於需求大增的日本及其他亞洲市場。

    其中碩禾8月正銀的出貨量已正式突破3噸,且顧客需求活絡、訂單能見度佳,出貨量有望逐月放大。加上銀價隨著美國量化寬鬆政策訊息鈍化與出現反彈,正銀收益更加穩定。另外,碩禾子公司禾迅投資的日本永和電力,在日本簽訂約50億日圓電站業務合約,將在福島建置近17MW的電站,估計明年完工後,每年可貢獻售電收入約8億日圓的收益。

    昱晶日前指出雙反時因應客戶拉貨的庫存已消化完畢,目前看來客戶對第四季需求沒有出現雜音,10月接單狀況佳,11、12月份的訂單應不會出現太大問題,整體表現可望與第三季相當。

    中美晶在八月營收創新高後,也宣布著重綠能題材,積極以新技術搶攻油電混合車和純電動車市場,由於油電混合車和純電動車對於晶片的需求較傳統車用量高,隨著油價攀高、環保意識抬頭,也將推升未來公司的成長力道。

    新日光在宣布合併旺能後,領域囊括太陽能電池、模組到終端的系統安裝,產能亦同步抬升。而新日光轉投資的系統安裝廠永旺日前爭取到國發基金補助,以永旺今年度在國內、外的安裝目標來看,可望帶來製造端之外的穩定成長動能。

    隨著太陽能產業景氣回升,國內太陽能電池廠的接單能見度開始至10月向後延伸,一線大廠產能利用率皆仍能維持在高檔,第四季有望呈現淡季不淡的局面。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 在運行時生成C# .NET類

    在運行時生成C# .NET類

    ​本文譯自​:​Generating C# .NET Classes at Runtime
    作者:WedPort

    在我的C#職業生涯中,有幾次我不得不在運行時生成新的類型。希望把它寫下來能幫助有相同應用需求的人。這也意味着我以後不必在查找相同問題的StackOverflow文章了。我最初是在.NET 4.6.2中這樣做的,但我已經更新到為.NET Core 3.0提供了示例。所有代碼都可以在我的GitHub上面找到。
    GitHub:https://github.com/cheungt6/public/tree/master/ReflectionEmitClassGeneration

    為什麼我需要在運行時生成類?

    在運行時生產新類型的需求通常是由於運行時才知道類屬性,滿足性能要求以及需要在新類型中添加功能。當你嘗試這樣做的時候,你應該考慮的第一件事是:這是否真的是一個明智的解決方案。在深入思考之前,還有很多其他事情可以嘗試,問你自己這樣的問題:

    1. 我可以使用普通的類嗎
    2. 我可以使用Dictionary、Tuple或者對象數組(Array)?
    3. 我是否可以使用擴展對象
    4. 我確定我不能使用一個普通的類嗎?

    如果你認為這仍然是必要的,請繼續閱讀下面的內容。

    示例用例

    作為一名開發人員,我將大量數據綁定到各種WPF Grids中。大多數時候屬性是固定的,我可以使用預定義的類。有時候,我不得不動態的構建網格,並且能夠在應用程序運行時更改數據。採取以下显示ID和一些財務數據的類(FTSE和CAC是指數,其屬性代表指數價格):

    public class PriceHolderViewModel : ViewModelBase
    {
        public long Id { get; set; }
        public decimal FTSE100 { get; set; }
        public decimal CAC40 { get; set; }
    }
    

    如果我們僅對其中的屬性感興趣,該類定義的非常棒。但是,如果要使用更多屬性擴展此類,則需要在代碼中添加它,重新編譯並在新版本中進行部署。

    相反的,我們可以做的是跟蹤對象所需的屬性,並在運行時構建類。這將允許我們在需要是不斷的添加和刪除屬性,並使用反射來更新它們的值。

    // Keep track of my properties
    var _properties = new Dictionary<string, Type>(new[]{
       new KeyValuePair<string, Type>( "FTSE100", typeof(Decimal) ),
       new KeyValuePair<string, Type>( "CAC40", typeof(Decimal) ) });
    

    創建你的類型

    下面的示例向您展示了如何在運行時構建新類型。你需要使用**System.Reflection.Emit**庫來構造一個新的動態程序集,您的類將在其中創建,然後是模塊和類型。與舊的** .NET Framework**框架不同,在舊的版本中,你需要在當前程序的AppDomain中創建程序集 ,而在** .NET Core** 中,AppDomain不再可用。你將看到我使用GUID創建了一個新類型名稱,以便於跟蹤類型的版本。在以前,你不能創建具有相同名稱的兩個類型,但是現在似乎不是這樣了。

    public Type GeneratedType { private set; get; }
    
    private void Initialise()
    {
        var newTypeName = Guid.NewGuid().ToString();
        var assemblyName = new AssemblyName(newTypeName);
        var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var dynamicModule = dynamicAssembly.DefineDynamicModule("Main");
        var dynamicType = dynamicModule.DefineType(newTypeName,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                typeof(T));     // This is the type of class to derive from. Use null if there isn't one
        dynamicType.DefineDefaultConstructor(MethodAttributes.Public |
                                            MethodAttributes.SpecialName |
                                            MethodAttributes.RTSpecialName);
        foreach (var property in Properties)
            AddProperty(dynamicType, property.Key, property.Value);
    
        GeneratedType = dynamicType.CreateType();
    }
    

    在定義類型時,你可以提供一種類型,從中派生新的類型。如果你的基類具有要包含在新類型中的某些功能或屬性,這將非常有用。之前,我曾使用它在運行時擴展ViewModelSerializable類型。

    在你創建了TypeBuilder后,你可以使用下面提供的代碼開始添加屬性。它創建了支持字段和所需的中間語言,以便通過GetterSetter訪問它們。為每個屬性完成此操作后,可以使用CreateType()創建類型的實例。

    private static void AddProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
    {
        var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
        var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        
        var getMethod = typeBuilder.DefineMethod("get_" + propertyName,
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        var getMethodIL = getMethod.GetILGenerator();
        getMethodIL.Emit(OpCodes.Ldarg_0);
        getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder);
        getMethodIL.Emit(OpCodes.Ret);
    
        var setMethod = typeBuilder.DefineMethod("set_" + propertyName,
              MethodAttributes.Public |
              MethodAttributes.SpecialName |
              MethodAttributes.HideBySig,
              null, new[] { propertyType });
        var setMethodIL = setMethod.GetILGenerator();
        Label modifyProperty = setMethodIL.DefineLabel();
        Label exitSet = setMethodIL.DefineLabel();
    
        setMethodIL.MarkLabel(modifyProperty);
        setMethodIL.Emit(OpCodes.Ldarg_0);
        setMethodIL.Emit(OpCodes.Ldarg_1);
        setMethodIL.Emit(OpCodes.Stfld, fieldBuilder);
        setMethodIL.Emit(OpCodes.Nop);
        setMethodIL.MarkLabel(exitSet);
        setMethodIL.Emit(OpCodes.Ret);
    
        propertyBuilder.SetGetMethod(getMethod);
        propertyBuilder.SetSetMethod(setMethod);
    }
    

    有了類型后,就很容易通過使用Activator.CreateInstance()來創建它的實例。但是,你希望能夠更改已創建的屬性的值,為了做到這一點,你可以再次使用反射來獲取propertyInfos並提取Set方法。一旦有了這些屬性,電影它們類設置屬性值就相對簡單了。

    foreach (var property in Properties)
    {
        var propertyInfo = GeneratedType.GetProperty(property.Key);
        var setMethod = propertyInfo.GetSetMethod();
        setMethod.Invoke(objectInstance, new[] { propertyValue });
    }
    

    現在,您可以在運行時使用自定義屬性來創建自己的類型,並具有更新其值的功能,一切就緒。 我發現的唯一障礙是創建一個可以存儲新類型實例的列表。 WPF中的DataGrid傾向於只讀取List的常規參數類型的屬性。 這意味着即使您使用新屬性擴展了基類,使用AutoGenerateProperties也只能看到基類中的屬性。 解決方案是使用生成的類型顯式創建一個新的List。 我在下面提供了如何執行此操作的示例:

    var listGenericType = typeof(List<>);
    var list = listGenericType.MakeGenericType(GeneratedType);
    var constructor = list.GetConstructor(new Type[] { });
    var newList = (IList)constructor.Invoke(new object[] { });
    foreach (var value in values)
        newList.Add(value);
    

    結論

    我已經在GitHub中創建了一個示例應用程序。它包含一個UI來幫助您調試和理解運行時新類型的創建,以及如何更新值。如果您有任何問題或意見,請隨時與我們聯繫。

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

    【其他文章推薦】

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

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

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

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

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

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

  • Java多線程之volatile詳解

    Java多線程之volatile詳解

    本文目錄

    • 從多線程交替打印A和B開始
    • Java 內存模型中的可見性、原子性和有序性
    • Volatile原理
      • volatile的特性
      • volatile happens-before規則
      • volatile 內存語義
      • volatile 內存語義的實現
    • CPU對於Volatile的支持
      • 緩存一致性協議
    • 工作內存(本地內存)並不存在
    • 總結
    • 參考資料

    從多線程交替打印A和B開始

    面試中經常會有一道多線程交替打印A和B的問題,可以通過使用Lock和一個共享變量來完成這一操作,代碼如下,其中使用num來決定當前線程是否打印

    public class ABTread {
    
        private static int num=0;
        private static Lock lock=new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==0){
                            System.out.println("A");
                            num=1;
                        }
                        lock.unlock();
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==1){
                            System.out.println("B");
                            num=0;
                        }
                        lock.unlock();
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    這一過程使用了一個可重入鎖,在以前可重入鎖的獲取流程中有分析到,當鎖被一個線程持有時,後繼的線程想要再獲取鎖就需要進入同步隊列還有可能會被阻塞。
    現在假設當A線程獲取了鎖,B線程再來獲取鎖且B線程獲取失敗則會調用LockSupport.park()導致線程B阻塞,線程A釋放鎖時再還行線程B
    是否會經常存在阻塞線程和還行線程的操作呢,阻塞和喚醒的操作是比較費時間的。是否存在一個線程剛釋放鎖之後這一個線程又再一次獲取鎖,由於共享變量的存在,
    則獲取鎖的線程一直在做着毫無意義的事情。

    可以使用volatile關鍵字來修飾共享變量來解決,代碼如下:

    public class ABTread {
    
        private static volatile  int num=0;
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==0){        //讀取num過程記作1
                            System.out.println("A");
                            num=1;          //寫入num記位2
                        }
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==1){        //讀取num過程記作3
                            System.out.println("B");
                            num=0;          ////寫入num記位4
                        }
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    Lock可以通過阻止同時訪問來完成對共享變量的同時訪問和修改,必要的時候阻塞其他嘗試獲取鎖的線程,那麼volatile關鍵字又是如何工作,
    在這個例子中,是否效果會優於Lock呢。

    Java 內存模型中的可見性、原子性和有序性

    • 可見性:指線程之間的可見性,一個線程對於狀態的修改對另一個線程是可見的,也就是說一個線程修改的結果對於其他線程是實時可見的。
      可見性是一個複雜的屬性,因為可見性中的錯誤總是會違背我們的直覺(JMM決定),通常情況下,我們無法保證執行讀操作的線程能實時的看到其他線程的寫入的值。
      為了保證線程的可見性必須使用同步機制。退一步說,最少應該保證當一個線程修改某個狀態時,而這個修改時程序員希望能被其他線程實時可見的,
      那麼應該保證這個狀態實時可見,而不需要保證所有狀態的可見。在 Javavolatilesynchronizedfinal 實現可見性。

    • 原子性:如果一個操作是不可以再被分割的,那麼我們說這個操作是一個原子操作,即具有原子性。但是例如i++實際上是i=i+1這個操作是可分割的,他不是一個原子操作。
      非原子操作在多線程的情況下會存在線程安全性問題,需要是我們使用同步技術將其變為一個原子操作。javaconcurrent包下提供了一些原子類,
      我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicIntegerAtomicLongAtomicReference等。在 Javasynchronized 和在 lockunlock 中操作保證原子性

    • 有序性:一系列操作是按照規定的順序發生的。如果在本線程之內觀察,所有的操作都是有序的,如果在其他線程觀察,所有的操作都是無序的;前半句指“線程內表現為串行語義”後半句指“指令重排序”和“工作內存和主存同步延遲”
      Java 語言提供了 volatilesynchronized 兩個關鍵字來保證線程之間操作的有序性。volatile 是因為其本身包含“禁止指令重排序”的語義,
      synchronized 是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規則獲得的,此規則決定了持有同一個對象鎖的兩個同步塊只能串行執行。

    Volatile原理

    volatile定義:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應該通過獲取排他鎖單獨獲取這個變量;
    java提供了volatile關鍵字在某些情況下比鎖更好用。

    • Java語言提供了volatile了關鍵字來提供一種稍弱的同步機制,他能保證操作的可見性和有序性。當把變量聲明為volatile類型后,
      編譯器與運行時都會注意到這個變量是一個共享變量,並且這個變量的操作禁止與其他的變量的操作重排序。

    • 訪問volatile變量時不會執行加鎖操作。因此也不會存在阻塞競爭的線程,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。

    volatile的特性

    volatile具有以下特性:

    • 可見性:對於一個volatile的讀總能看到最後一次對於這個volatile變量的寫
    • 原子性:對任意單個volatile變量的讀/寫具有原子性,但對於類似於i++這種複合操作不具有原子性。
    • 有序性:

    volatile happens-before規則

    根據JMM要求,共享變量存儲在共享內存當中,工作內存存儲一個共享變量的副本,
    線程對於共享變量的修改其實是對於工作內存中變量的修改,如下圖所示:

    從多線程交替打印A和B開始章節中使用volatile關鍵字的實現為例來研究volatile關鍵字實現了什麼:
    假設線程A在執行num=1之後B線程讀取num指,則存在以下happens-before關係

    1)  1 happens-before 2,3 happens-before 4
    2)  根據volatile規則有:2 happens-before 3
    3)  根據heppens-before傳遞規則有: 1 happens-before 4
    

    至此線程的執行順序是符合我們的期望的,那麼volatile是如何保證一個線程對於共享變量的修改對於其他線程可見的呢?

    volatile 內存語義

    根據JMM要求,對於一個變量的獨寫存在8個原子操作。對於一個共享變量的獨寫過程如下圖所示:

    對於一個沒有進行同步的共享變量,對其的使用過程分為readloaduseassign以及不確定的storewrite過程。
    整個過程的語言描述如下:

    - 第一步:從共享內存中讀取變量放入工作內存中(`read`、`load`)
    - 第二步:當執行引擎需要使用這個共享變量時從本地內存中加載至**CPU**中(`use`)
    - 第三步:值被更改后使用(`assign`)寫回工作內存。
    - 第四步:若之後執行引擎還需要這個值,那麼就會直接從工作內存中讀取這個值,不會再去共享內存讀取,除非工作內存中的值出於某些原因丟失。
    - 第五步:在不確定的某個時間使用`store`、`write`將工作內存中的值回寫至共享內存。
    

    由於沒有使用鎖操作,兩個線程可能同時讀取或者向共享內存中寫入同一個變量。或者在一個線程使用這個變量的過程中另一個線程讀取或者寫入變量。
    上圖中1和6兩個操作可能會同時執行,或者在線程1使用num過程中6過程執行,那麼就會有很嚴重的線程安全問題,
    一個線程可能會讀取到一個並不是我們期望的值。

    那麼如果希望一個線程的修改對後續線程的讀立刻可見,那麼只需要將修改后存儲在本地內存中的值回寫到共享內存
    並且在另一個線程讀的時候從共享內存重新讀取而不是從本地內存中直接讀取即可;事實上
    當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存;
    而當讀取一個volatile變量時,JMM會從主存中讀取共享變量
    ,這也就是volatile的寫-讀內存語義。

    volatile的寫-讀內存語義:

    • volatile寫的內存語義:當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存
    • volatile讀的內存語義:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效,線程接下來將從主內存中讀取共享變量。

    如果將這兩個步驟綜合起來,那麼線程3讀取一個volatile變量后,寫線程1在寫這個volatile變量之前所有可見的共享變量的值都將樂客變得對線程3可見。

    volatile變量的讀寫過程如下圖:

    需要注意的是:在各個線程的工作內存中是存在volatile變量的值不一致的情況的,只是每次使用都會從共享內存讀取並刷新,執行引擎看不到不一致的情況,
    所以認為volatile變量在本地內存中不存在不一致問題。

    volatile 內存語義的實現

    在前文Java內存模型中有提到重排序。為了實現volatile的內存語義,JMM會限制重排序的行為,具體限制如下錶:

    是否可以重排序 第二個操作 第二個操作 第二個操作
    第一個操作 普通讀/寫 volatile volatile
    普通讀/寫 NO
    volatile NO NO NO
    volatile NO NO

    說明:

    - 若第一個操作時普通變量的讀寫,第二個操作時volatile變量的寫操作,則編譯器不能重排序這兩個操作
    - 若第一個操作是volatile變量的讀操作,不論第二個變量是什麼操作不餓能重排序這兩個操作
    - 若第一個操作時volatile變量的寫操作,除非第二個操作是普通變量的獨寫,否則不能重排序這兩個操作
    

    為了實現volatile變量的內存語義,編譯器生成字節碼文件時會在指令序列中插入內存屏障來禁止特定類型的處理器排序。
    為了實現volatile變量的內存語義,插入了以下內存屏障,並且在實際執行過程中,只要不改變volatile的內存語義,
    編譯器可以根據實際情況省略部分不必要的內存屏障

    - 在每個volatile寫操作前面插入StoreStore屏障
    - 在每個volatile寫操作後面插入StoreLoad屏障
    - 在每個volatile讀操作後面插入LoadLoad屏障
    - 在每個volatile讀操作後面插入LoadStore屏障
    

    插入內存屏障后volatile寫操作過程如下圖:

    插入內存屏障后volatile讀操作過程如下圖:

    至此在共享內存和工作內存中的volatile的寫-讀的工作過程全部完成

    但是現在的CPU中存在一個緩存,CPU讀取或者修改數據的時候是從緩存中獲取並修改數據,那麼如何保證CPU緩存中的數據與共享內存中的一致,並且修改后寫回共享內存呢?

    CPU對於Volatile的支持

    緩存行:cpu緩存存儲數據的基本單位,cpu不能使數據失效,但是可以使緩存行失效。

    對於CPU來說,CPU直接操作的內存時高速緩存,而每一個CPU都有自己L1、L2以及共享的L3級緩存,如下圖:

    那麼當CPU修改自身緩存中的被volatile修飾的共享變量時,如何保證對其他CPU的可見性。

    緩存一致性協議

    在多處理器的情況下,每個處理器總是嗅探總線上傳播的數據來檢查自己的緩存是否過期,當處理器發現自己對應的緩存對應的地址被修改,
    就會將當前處理器的緩存行設置為無效狀態,當處理器對這個數據進行操作的時候,會重新從系統中把數據督導處理器的緩存里。這個協議被稱之為緩存一致性協議。

    緩存一致性協議的實現又MEIMESIMOSI等等。

    MESI協議緩存狀態

    狀態 描述
    M(modified)修改 該緩存指被緩存在該CPU的緩存中並且是被修改過的,即與主存中的數據不一致,該緩存行中的數據需要在未來的某個時間點寫回主存,當寫回註冊年之後,該緩存行的狀態會變成E(獨享)
    E(exclusive)獨享 該緩存行只被緩存在該CPU的緩存中,他是未被修改過的,與主存中數據一致,該狀態可以在任何時候,當其他的CPU讀取該內存時編程共享狀態,同樣的,當CPU修改該緩存行中的內容時,該狀態可以變為M(修改)
    S(share)共享 該狀態意味着該緩存行可能被多個CPU緩存,並且各個緩存中的數據與主存中的數據一致,當有一個CPU修改自身對應的緩存的數據,其它CPU中該數據對應的緩存行被作廢
    I(Invalid)無效 該緩存行無效

    MESI協議可以防止緩存不一致的情況,但是當一個CPU修改了緩存中的數據,但是沒有寫入主存,也會存在問題,那麼如何保證CPU修改共享被volatile修飾的共享變量后立刻寫回主存呢。

    在有volatile修飾的共享變量進行寫操作的時候會多出一條帶有lock前綴的彙編代碼,而這個lock操作會做兩件事:

    1. 將當前處理器的緩存行的數據協會到系統內存。lock信號確保聲言該信號期間CPU可以獨佔共享內存。在之前通過鎖總線的方式,現在採用鎖緩存的方式。
    2. 這個寫回操作會使其他處理器的緩存中緩存了該地址的緩存行無效。在下一次這些CPU需要使用這些地址的值時,強制要求去共享內存中讀取。

    如果對聲明了volatile的共享變量進行寫,JVM會向CPU發送一條lock指令,使得將這個變量所在的緩存行緩存的數據寫回到內存中。而其他CPU通過嗅探總線上傳播的數據,
    使得自身緩存行失效,下一次使用時會從主存中獲取對應的變量。

    工作內存(本地內存)並不存在

    根據JAVA內存模型描述,各個線程使用自身的工作內存來保存共享變量,那麼是不是每個CPU緩存的數據就是從工作內存中獲取的。這樣的話,在CPU緩存寫回主存時,
    協會的是自己的工作內存地址,而各個線程的工作內存地址並不一樣。CPU嗅探總線時就嗅探不到自身的緩存中緩存有對應的共享變量,從而導致錯誤?

    事實上,工作內存並不真實存在,只是JMM為了便於理解抽象出來的概念,它涵蓋了緩存,寫緩衝區、寄存器及其他的硬件編譯器優化。所以緩存是直接和共享內存交互的。
    每個CPU緩存的共享數據的地址是一致的。

    總結

    • volatile提供了一種輕量級同步機制來完成同步,它可以保操作的可見性、有序性以及對於單個volatile變量的讀/寫具有原子性,對於符合操作等非原子操作不具有原子性。

    • volatile通過添加內存屏障及緩存一致性協議來完成對可見性的保證。

    最後Lock#lock()是如何保證可見性的呢??

    Lock#lock()使用了AQSstate來標識鎖狀態,而statevolatile標記的,由於對於volatile的獨寫操作時添加了內存屏障的,所以在修改鎖狀態之前,
    一定會將之前的修改寫回共享內存。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 聯合國示警:氣候變遷加劇阿拉伯地區衝突局勢

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

    【其他文章推薦】

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

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

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

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

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

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