日本大鯢數量急減 雜交問題嚴重 水族館推大鯢布偶提升國民關注

文:宋瑞文

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

如何監控 Linux 服務器狀態?

Linux 服務器我們天天打交道,特別是 Linux 工程師更是如此。為了保證服務器的安全與性能,我們經常需要監控服務器的一些狀態,以保證工作能順利開展。

本文介紹的幾個命令,不僅僅適用於服務器監控,也適用於我們日常情況下的開發。

1. watch

watch 命令我們的使用頻率很高,它的基本作用是,按照指定頻率重複執行某一條指令。使用這個命令,我們可以重複調用一些命令來達到監控服務器的作用。

默認情況下,watch 命令的執行周期是 2 秒,但我們可以使用 -n 選項來指定運行頻率,比如我們想要每隔 5 秒執行 date 命令,可以這麼執行:

$ watch -n 5 date

一台服務器肯定有多人在用,特別是本部門的小夥伴。對於這些小夥伴有沒渾水摸魚,我們可以使用一些命令來監控他們。

我們可以每隔 10 秒執行 who 命令,來看看都有誰在使用服務器。

$ watch -n 10 who
Every 10.0s: who                             butterfly: Tue Jan 23 16:02:03 2019

shs      :0           2019-01-23 09:45 (:0)
dory     pts/0        2019-01-23 15:50 (192.168.0.5)
alvin     pts/1        2019-01-23 16:01 (192.168.0.15)
shark    pts/3        2019-01-23 11:11 (192.168.0.27)

如果發現系統運行很慢,我們可以調用 uptime 命令來查看系統平均負載情況。

$ watch uptime
Every 2.0s: uptime                           butterfly: Tue Jan 23 16:25:48 2019

 16:25:48 up 22 days,  4:38,  3 users,  load average: 1.15, 0.89, 1.02

一些關鍵的進程肯定不能掛,否則可能會影響到業務開展,所以我們可以重複統計服務器中的所有進程數量。

$ watch -n 5 'ps -ef | wc -l'
Every 5.0s: ps -ef | wc -l                   butterfly: Tue Jan 23 16:11:54 2019

245

想動態知道服務器內存使用情況,可以重複執行 free 命令。

$ watch -n 5 free -m
Every 5.0s: free -m                          butterfly: Tue Jan 23 16:34:09 2019

              total        used        free      shared  buff/cache   available
Mem:           5959         776        3276          12        1906        4878
Swap:          2047           0        2047

當然不僅僅是這些,我們還可以重複調用很多命令來對服務器一些關鍵參數進行監控,

2. top

使用 top 命令我們可以知道系統的很多關鍵參數,而且是動態更新的。默認情況下,top 監控的是系統的整體狀態,如果我們只想知道某個人的使用情況,可以使用 -u 選項來指定這個人。

$ top -u alvin
top - 16:14:33 up 2 days,  4:27,  3 users,  load average: 0.00, 0.01, 0.02
Tasks: 199 total,   1 running, 198 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.2 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   5959.4 total,   3277.3 free,    776.4 used,   1905.8 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   4878.4 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
23026 alvin      20   0   46340   7820   6504 S   0.0   0.1   0:00.05 systemd
23033 alvin      20   0  149660   3140     72 S   0.0   0.1   0:00.00 (sd-pam)
23125 alvin      20   0   63396   5100   4092 S   0.0   0.1   0:00.00 sshd
23128 alvin      20   0   16836   5636   4284 S   0.0   0.1   0:00.03 zsh

在這個結果里,你不僅僅可以看到 alvin 這個用戶運行的所有的進程數,也可以看到每個進程所消耗的系統資源(CPU,內存),同時依然可以看到整個系統的關鍵參數。

3. ac

如果你想知道每個用戶登錄服務器所使用的時間,你可以使用 ac 命令。這個命令需要你安裝 acct 包(Debian)或 psacct 包(RHEL,Centos)。

如果我們想知道所有用戶登陸服務器所使用的時間之和,我們可以直接運行 ac 命令,無需任何參數。

$ ac
        total     1261.72

如果我們想知道各個用戶所使用時間,可以加上 -p 選項。

$ ac -p
        shark                                5.24
        alvin                                5.52
        shs                               1251.00
        total     1261.76

我們還可以通過加上 -d 選項來查看具體每一天用戶使用服務器時間之和。

$ ac -d | tail -10
Jan 11  total        0.05
Jan 12  total        1.36
Jan 13  total       16.39
Jan 15  total       55.33
Jan 16  total       38.02
Jan 17  total       28.51
Jan 19  total       48.66
Jan 20  total        1.37
Jan 22  total       23.48
Today   total        9.83

小結

我們可以使用很多命令來監控系統的運行狀態,本文主要介紹了三個:watch 命令可以讓你重複執行某一條命令來監控一些參數的變化,top 命令可以查看某個用戶運行的進程數以及消耗的資源,而 ac 命令則可以查看每個用戶使用服務器時間。你經常使用哪個命令呢?歡迎留言討論!

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

帶你學夠浪:Go語言基礎系列 – 8分鐘學控制流語句

文章每周持續更新,原創不易,「三連」讓更多人看到是對我最大的肯定。可以微信搜索公眾號「 後端技術學堂 」第一時間閱讀(一般比博客早更新一到兩篇)

對於一般的語言使用者來說 ,20% 的語言特性就能夠滿足 80% 的使用需求,剩下在使用中掌握。基於這一理論,Go 基礎系列的文章不會刻意追求面面俱到,但該有知識點都會覆蓋,目的是帶你快跑趕上 Golang 這趟新車。

Hurry up , Let’s go !

控制語句是程序的靈魂,有了它們程序才能完成各種邏輯,今天我們就來學習 Go 中的各種控制語句。

通過本文的學習你將掌握以下知識:

  • if 條件語句
  • for 循環語句
  • switch 語句
  • defer 延遲調用

if 條件語句

與大多數編程語言一樣,if 用於條件判斷,當條件表達式 exprtrue 執行 {} 包裹的消息體語句,否則不執行。

語法是這樣的:

if expr {
    // some code
}

**注意:**語法上和 c 語言不同的是不用在條件表達式 expr 外帶括號,和 python 的語法類似。

當然,如果想在條件不滿足的時候做點啥,就可以 if 后帶 else 語句。語法:

if expr {
    // some code
} else {
    // another code
}

不僅僅是 if

除了可以在 if 中做條件判斷之外,在 Golang 中你甚至可以在 if 的條件表達式前執行一個簡單的語句。

舉個例子:

if x2 := 1; x2 > 10 { 
    fmt.Println("x2 great than 10")
} else {
    fmt.Println("x2 less than 10", x2)
}

上面的例子在 if 語句中先聲明並賦值了 x2,之後對 x2 做條件判斷。

注意:此處在 if 內聲明的變量 x2 作用域僅限於 if 和else 語句。

for循環語句

當需要重複執行的時候需要用到循環語句,Go 中只有 for 這一種循環語句。

標準的for循環語法:

for 初始化語句; 條件表達式; 後置語句 {
    // some code
}

這種語法形式和 C 語言中 for 循環寫法還是很像的,不同的是不用把這三個部分用 () 括起來。循環執行邏輯:

  • 初始化語句:初始循環時執行一次,做一些初始化工作,一般是循環變量的聲明和賦值。
  • 條件表達式:在每次循環前對條件表達式求值操作,若求值結果是
    true 則執行循環體內語句,否則不執行。
  • 後置語句:在每次循環的結尾執行,一般是做循環變量的自增操作。

舉個例子:

sum := 0
for i := 0; i < 10; i++ {
    sum += i // i作用域只在for語句內
    fmt.Println(i, sum)
}

注意:循環變量i 的作用域只在 for 語句內,超出這個範圍就不能使用了。

while循環怎麼寫?

前面說了,Golang 中只有 for 這一種循環語法,那有沒有類似 C 語言中 while 循環的寫法呢?答案是有的:把 for 語句的前後兩部分省略,只留中間的「條件表達式」的 for 語句等價於 while 循環。

像下面這樣:

sum1 := 0
for ;sum1 < 10; { // 可以省略初始化語句和後置語句
    sum1++
    fmt.Println(sum1)
}

上面的示例沒有初始化語句和後置語句,會循環執行 10 次後退出。

當然你要是覺得前後的分號也不想寫了,也可以省略不寫,上面的代碼和下面是等效的:

sum1 := 0
for sum1 < 10 { // 可以省略初始化語句和後置語句,分號也能省略
    sum1++
    fmt.Println(sum1)
}

在 Golang 中死循環可以這樣寫,相當於 C 語言中的 while(true)

 for { // 死循環
  // your code
 }

switch 語句

switch 語句可以簡化多個 if-else 條件判斷寫法,避免代碼看起來雜亂。

可以先定義變量,然後在 switch 中使用這個變量。

 a := 1
 switch a {
 case 1: 
  fmt.Println("case 1") // 不用寫break 執行到這自動跳出
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }
輸出:case 1

從 C 語言過來的朋友一定有這樣的經歷:經常會在 case 語句中漏掉 break 導致程序繼續往下執行,從而產生奇奇怪怪的 bug ,這種問題在 Golang 中不復存在了。

Golang 在每個 case 後面隱式提供 break 語句。 除非以 fallthrough 語句結束,否則分支會自動終止。

 switch a := 1; a { //這裡有分號
 case 1: // case 無需為常量,且取值不必為整數。
  fmt.Println("case 1") // 不用寫break 執行到自動跳出 除非以 fallthrough 語句結束
  fallthrough
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }
輸出:
case 1
case 2

還可以直接在 switch 中定義變量后使用,但是要注意變量定義之後又分號,比如下面這樣:

 switch b :=1; b { //注意這裡有分號
 case 1: 
  fmt.Println("case 1") 
 case 2:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }

沒有條件的switch

沒有條件的 switch 同 switch true 一樣,只有當 case 中的表達式值為「真」時才執行,這種形式能簡化複雜的 if-else-if else 語法。

下面是用 if 來寫多重條件判斷,這裏寫的比較簡單若是再多幾個 else if 代碼結構看起來會更糟糕。

    a := 1
    if a > 0 {
        fmt.Println("case 1") 
    } else if a < 0 {
        fmt.Println("case 2")   
    } else {
        fmt.Printf("unexpect case")   
    }

如果用上不帶條件的 switch 語句,寫出來就會簡潔很多,像下面這樣。

 a := 1
 switch {    // 相當於switch true
 case a > 0: // 若表達式為「真」則執行 
  fmt.Println("case 1") 
 case a < 0:
  fmt.Println("case 2")
 default:
  fmt.Printf("unexpect case")
 }

defer 語句

defer 語句有延遲調用的效果。具體來說defer後面的函數調用會被壓入堆棧,當外層函數返回才會對壓棧的函數按後進先出順序調用。說起來有點抽象,舉個例子:

package main

import "fmt"

func main() {
 fmt.Println("entry main")
 for i := 0; i < 6; i++ {
  defer fmt.Println(i)
 }
 fmt.Println("exit main")
}

fmt.Println(i) 不會每次立即執行,而是在 main 函數返回之後才依次調用,編譯運行上述程序的輸出:

entry main
exit main  //外層函數返回
5
4
3
2
1
0

上面是簡單的使用示例,實際使用中defer 通常用來釋放函數內部變量,因為它可以在外層函數 return 之後繼續執行一些清理動作。

這在文件類操作異常處理中非常實用,比如用於釋放文件描述符,我們以後會講解這塊應用,總之先記住 defer 延遲調用的特點。

總結

通過本文的學習,我們掌握了 Golang 中基本的控制流語句,利用這些控制語句加上一節介紹的變量等基礎知識,可以構成豐富的程序邏輯,就能用 Golang 來做一些有意思的事情了。

感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習.

今天的技術分享就到這裏,我們下期再見。

創作不易,白票不是好習慣,如果在我這有收穫,動動手指「點贊」「關注」是對我持續創作的最大支持。

微信搜索公眾號「 後端技術學堂 」回復「資料」「1024」有我給你準備的各種編程學習資料。文章每周持續更新,我們下期見!

本文使用 mdnice 排版

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

異步函數async await在wpf都做了什麼?

首先我們來看一段控制台應用代碼:

 class Program
 {
     static async Task Main(string[] args)
     {
        System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
        var result = await ExampleTask(2);
        System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
        System.Console.WriteLine(result);
        Console.WriteLine("Async Completed");
     }

     private static async Task<string> ExampleTask(int Second)
     {
        await Task.Delay(TimeSpan.FromSeconds(Second));
        return $"It's Async Completed in {Second} seconds";
     }
 }

輸出結果

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed

如果這段代碼在WPF運行,猜猜會輸出啥?

      private async void Async_Click(object sender, RoutedEventArgs e)
      {
          Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
          var result= await ExampleTask(2);
          Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
          Debug.WriteLine(result);
          Debug.WriteLine("Async Completed");   
      }

      private async Task<string> ExampleTask(int Second)
      {
          await Task.Delay(TimeSpan.FromSeconds(Second));
          return $"It's Async Completed in {Second} seconds";
      }

輸出結果:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed

這時候你肯定是想說,小朋友,你是否有很多問號????,我們接下看下去

一.SynchronizationContext(同步上下文)

首先我們知道async await 異步函數本質是狀態機,我們通過反編譯工具dnspy,看看反編譯的兩段代碼是否有不同之處:

控制台應用:

internal class Program
{
    [DebuggerStepThrough]
	private static Task Main(string[] args)
	{
		Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
		<Main>d__.args = args;
		<Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
		<Main>d__.<>1__state = -1;
		<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
		return <Main>d__.<>t__builder.Task;
	}
    
	[DebuggerStepThrough]
	private static Task<string> ExampleTask(int Second)
	{
		Program.<ExampleTask>d__1 <ExampleTask>d__ = new Program.<ExampleTask>d__1();
		<ExampleTask>d__.Second = Second;
		<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
		<ExampleTask>d__.<>1__state = -1;
		<ExampleTask>d__.<>t__builder.Start<Program.<ExampleTask>d__1>(ref <ExampleTask>d__);
		return <ExampleTask>d__.<>t__builder.Task;
	}

	[DebuggerStepThrough]
	private static void <Main>(string[] args)
	{
	        Program.Main(args).GetAwaiter().GetResult();
	}
}

WPF:

public class MainWindow : Window, IComponentConnector
{

	public MainWindow()
	{
	       this.InitializeComponent();
	}

	[DebuggerStepThrough]
	private void Async_Click(object sender, RoutedEventArgs e)
	{
		MainWindow.<Async_Click>d__1 <Async_Click>d__ = new MainWindow.<Async_Click>d__1();
		<Async_Click>d__.<>4__this = this;
		<Async_Click>d__.sender = sender;
		<Async_Click>d__.e = e;
		<Async_Click>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
		<Async_Click>d__.<>1__state = -1;
		<Async_Click>d__.<>t__builder.Start<MainWindow.<Async_Click>d__1>(ref <Async_Click>d__);
	}

	[DebuggerStepThrough]
	private Task<string> ExampleTask(int Second)
	{
	        MainWindow.<ExampleTask>d__3 <ExampleTask>d__ = new MainWindow.<ExampleTask>d__3();
		<ExampleTask>d__.<>4__this = this;
		<ExampleTask>d__.Second = Second;
		<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
		<ExampleTask>d__.<>1__state = -1;
		<ExampleTask>d__.<>t__builder.Start<MainWindow.<ExampleTask>d__3>(ref <ExampleTask>d__);
		return <ExampleTask>d__.<>t__builder.Task;
	}

	[DebuggerNonUserCode]
	[GeneratedCode("PresentationBuildTasks", "4.8.1.0")]
	public void InitializeComponent()
	{
		bool contentLoaded = this._contentLoaded;
		if (!contentLoaded)
		{
		     this._contentLoaded = true;
		     Uri resourceLocater = new Uri("/WpfApp1;component/mainwindow.xaml", UriKind.Relative);
		     Application.LoadComponent(this, resourceLocater);
		}
	}
	private bool _contentLoaded;
}

我們可以看到完全是一致的,沒有任何區別,為什麼編譯器生成的代碼是一致的,卻會產生不一樣的結果,我們看看創建和啟動狀態機代碼部分的實現:

public static AsyncVoidMethodBuilder Create()
{
	SynchronizationContext synchronizationContext = SynchronizationContext.Current;
	if (synchronizationContext != null)
	{
		synchronizationContext.OperationStarted();
	}
	return new AsyncVoidMethodBuilder
	{
		_synchronizationContext = synchronizationContext
	};
}

[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	AsyncMethodBuilderCore.Start<TStateMachine>(ref stateMachine);
}

[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	if (stateMachine == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
	}
	Thread currentThread = Thread.CurrentThread;
	Thread thread = currentThread;
	ExecutionContext executionContext = currentThread._executionContext;
	ExecutionContext executionContext2 = executionContext;
	SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
	try
	{
	     stateMachine.MoveNext();//狀態機執行代碼
	}
	finally
	{
	     SynchronizationContext synchronizationContext2 = synchronizationContext;
	     Thread thread2 = thread;
	     if (synchronizationContext2 != thread2._synchronizationContext)
	     {
		  thread2._synchronizationContext = synchronizationContext2;
	     }
	     ExecutionContext executionContext3 = executionContext2;
	     ExecutionContext executionContext4 = thread2._executionContext;
	     if (executionContext3 != executionContext4)
	     {
		 ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
	     }
	}
}

在這裏總結下:

  • 創建狀態機的Create函數通過SynchronizationContext.Current獲取到當前同步執行上下文
  • 啟動狀態機的Start函數之後通過MoveNext函數執行我們的異步方法
  • 這裏還有一個小提示,不管async函數裏面有沒有await,都會生成狀態機,只是MoveNext函數執行同步方法,因此沒await的情況下避免將函數標記為async,會損耗性能

同樣的這裏貌似沒能獲取到原因,但是有個很關鍵的地方,就是Create函數為啥要獲取當前同步執行上下文,之後我從MSDN找到關於SynchronizationContext
的介紹,有興趣的朋友可以去閱讀以下,以下是各個.NET框架使用的SynchronizationContext:

SynchronizationContext 默認
WindowsFormsSynchronizationContext WindowsForm
DispatcherSynchronizationContext WPF/Silverlight
AspNetSynchronizationContext ASP.NET

我們貌似已經一步步接近真相了,接下來我們來看看DispatcherSynchronizationContext

二.DispatcherSynchronizationContext

首先來看看DispatcherSynchronizationContext類的比較關鍵的幾個函數實現:

public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
     if (dispatcher == null)
     {
         throw new ArgumentNullException("dispatcher");
     }
     Dispatcher.ValidatePriority(priority, "priority");
     _dispatcher = dispatcher;
     _priority = priority;
     SetWaitNotificationRequired();
 }

//同步執行
public override void Send(SendOrPostCallback d, object state)
{
     if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
     {
         _dispatcher.Invoke(DispatcherPriority.Send, d, state);
     }
     else
     {
          _dispatcher.Invoke(_priority, d, state);
     }
}

//異步執行
public override void Post(SendOrPostCallback d, object state)
{
     _dispatcher.BeginInvoke(_priority, d, state);
}

我們貌似看到了熟悉的東西了,Send函數調用Dispatcher的Invoke函數,Post函數調用Dispatcher的BeginInvoke函數,那麼是否WPF執行異步函數之後會調用這裏的函數嗎?我用dnspy進行了調試:

我通過調試之後發現,當等待執行完整個狀態機的之後,也就是兩秒后跳轉到該Post函數,那麼,我們可以將之前的WPF那段代碼大概可以改寫成如此:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    //async生成狀態機的Create函數。獲取到UI主線程的同步執行上下文
    DispatcherSynchronizationContext synchronizationContext = (DispatcherSynchronizationContext)SynchronizationContext.Current;
    
    //UI主線程執行
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    
    //開始在狀態機的MoveNext執行該異步操作
    var result= await ExampleTask(2);
    
    //等待兩秒,異步執行完成,再在同步上下文異步執行
    synchronizationContext.Post((state) =>
    {
         //模仿_dispatcher.BeginInvoke
         Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
         Debug.WriteLine(result);
         Debug.WriteLine("Async Completed");  
     },"Post");           
 }

輸出結果:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed

也就是asyn負責生成狀態機和執行狀態機,await將代碼分為兩部分,一部分是異步執行狀態機部分,一部分是異步執行完之後,通過之前拿到的DispatcherSynchronizationContext,再去異步執行接下來的部分。我們可以通過dnspy調試DispatcherSynchronizationContext的 _dispatcher字段的Thread屬性,知道Thread為UI主線程,而同步界面UI控件的時候,也就是通過Dispatcher的BeginInvoke函數去執行同步的

三.Task.ConfigureAwait

Task有個ConfigureAwait方法,是可以設置是否對Task的awaiter的延續任務執行原始上下文,也就是為true時,是以一開始那個UI主線程的DispatcherSynchronizationContext執行Post方法,而為false,則以await那個Task裏面的DispatcherSynchronizationContext執行Post方法,我們來驗證下:

我們將代碼改為以下:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    var result= await ExampleTask(2).ConfigureAwait(false);
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    Debug.WriteLine(result);
    Debug.WriteLine($"Async Completed");
}

輸出:

Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed

結果和控制台輸出的一模一樣,且通過dnspy斷點調試依舊進入到DispatcherSynchronizationContext的Post方法,因此我們也可以證明我們上面的猜想,而且默認ConfigureAwait的參數是為true的,我們還可以將異步結果賦值給UI界面的Text block:

private async void Async_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    var result= await ExampleTask(2).ConfigureAwait(false);
    Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
    this.txt.Text = result;//修改部分
    Debug.WriteLine($"Async Completed");
}

拋出異常:

調用線程無法訪問此對象,因為另一個線程擁有該對象

補充
推薦林大佬的一篇文章,也講的也簡潔透徹C# dotnet 自己實現一個線程同步上下文

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

自己動手實現深度學習框架-7 RNN層–GRU, LSTM

目標

        這個階段會給cute-dl添加循環層,使之能夠支持RNN–循環神經網絡. 具體目標包括:

  1. 添加激活函數sigmoid, tanh.
  2. 添加GRU(Gate Recurrent Unit)實現.
  3. 添加LSTM(Long Short-term Memory)實現.
  4. 使用基於GRU和LSTM的RNN模型擬合一個正餘弦疊加函數.

RNN原理

原始的RNN

        RNN模型用來捕捉序列數據的特徵. 給定一個長度為T的輸入系列\(X=(x_1, x_2, .., X_T)\), RNN層輸出一個長度為T的序列\(H=(h_1, h_2, …, H_T)\), 對於任意時間步t, 可以表示為:

\[H_t = δ(X_tW_x + H_{t-1}W_h + b), \quad t = 2, 3, .., T \]

        函數δ是sigmoid函數:

\[δ = \frac{1}{1 + e^{-x}} \]

        \(H_t\)包含了前面第1到t-1步的所有信息。 和CNN層類似, CNN層在空間上共享參數, RNN層在時間步上共享參數\(W_x, W_h, b\).

        RNN層中隱藏層的數量為T-2, 如果T較大(超過10), 反向傳播是很容易出現梯度爆炸. GRU和LSTM就是為了解決這個問題而誕生, 這兩種模型,可以讓RNN能夠支持長度超過1000的輸入序列。

GRU

        GRU使用了不同功能的門控單元, 分別捕捉序列上不同時間跨度的的依賴關係。每個門控單元都會都有獨立的參數, 這些參數在時間步上共享。

        GRU的門控單元有:

        \(R_t = δ(X_tW^r_x + H_{t-1}W^r_h + b^r)\), 重置門用於捕捉短期依賴關係.

        \(U_t = δ(X_tW^u_x + H_{t-1}W^u_h + b^u)\), 更新門用於捕捉長期依賴關係

        \(\bar{H}_t = tanh(X_t\bar{W}_x + (R_t * H_{t-1})\bar{W}_h + \bar{b})\)

        除此之外, 還有一個輸出單元:

        \(H_t = U_t * H_{t-1} + (1-U_t)*\bar{H}_t\)

LSTM

        LSTM的設計思路和GRU類似, 同樣使用了多個門控單元:

        \(I_t = δ(X_tW^i_x + H_{t-1}W^i_h + b^i)\), 輸入門,過濾記憶門的輸出.

        \(F_t = δ(X_tW^f_x + H_{t-1}W^f_h + b^f)\), 遺忘門, 過濾前面時間步的記憶.

        \(O_t = δ(X_tW^o_x + H_{t-1}W^o_h + b^o)\), 輸出門, 過濾當前時間步的記憶.

        \(M_t = tanh(X_tW^m_x + H_{t-1}W^m_h + b^m)\), 記憶門.

        它還有自己獨有的記憶單元和輸出單元:

        \(\bar{M}_t = F_t * \bar{M}_{t-1} + I_t * M_t\)

        \(H_t = O_t * tanh(\bar{M}_t)\)

RNN實現

        設計要求:

  1. RNN層中的隱藏層的數量是基於序列長度的,輸入序列有多長, RNN層應生成對應數量的隱藏層。
  2. RNN層在時間步上共享參數, 從前面的描述可以看出, 只有門控單元有參數,因此門控單元應獨立實現。
  3. 任意一個時間步上的層都依賴上一個時間步的輸出,在正向傳播和反向傳播過程中都需要上一個時間步的輸出, 每個門控單元都使用棧保存上一個時間步的輸出.
  4. 默認情況下RNN層輸出所有時間步的輸出。但有時只需要最後一個時間步的輸出, 這種情況下使用過濾層, 只向下一層傳播最後一個時間步的輸出。
  5. 使用門控單元實現GRU和LSTM

RNN基礎類的實現

RNN類

        文件: cutedl/rnn_layers.py, 類名: RNN

        這個類是RNN層基類, 它主要功能是控制向前傳播和向後傳播的主流程.

        初始化參數:

  '''
  out_units 輸出單元數
  in_units 輸入單元數
  stateful 保留當前批次的最後一個時間步的狀態作為下一個批次的輸入狀態, 默認False不保留

  RNN 的輸入形狀是(m, t, in_units)
  m: batch_size
  t: 輸入系列的長度
  in_units: 輸入單元數頁是輸入向量的維數

  輸出形狀是(m, t, out_units)
  '''
  def __init__(self, out_units, in_units=None, stateful=False, activation='linear'):

        向前傳播

def forward(self, in_batch, training):
    m, T, n = in_batch.shape
    out_units = self.__out_units
    #所有時間步的輸出
    hstatus = np.zeros((m, T, out_units))
    #上一步的輸出
    pre_hs = self.__pre_hs
    if pre_hs is None:
        pre_hs = np.zeros((m, out_units))

    #隱藏層循環過程, 沿時間步執行
    for t in range(T):
        hstatus[:, t, :] = self.hiden_forward(in_batch[:,t,:], pre_hs, training)
        pre_hs = hstatus[:, t, :]

    self.__pre_hs = pre_hs
    #pdb.set_trace()
    if not self.stateful:
        self.__pre_hs = None

    return hstatus

        反向傳播

def backward(self, gradient):
      m, T, n = gradient.shape

      in_units = self.__in_units
      grad_x = np.zeros((m, T, in_units))
      #pdb.set_trace()
      #從最後一個梯度開始反向執行.
      for t in range(T-1, -1, -1):
          grad_x[:,t,:], grad_hs = self.hiden_backward(gradient[:,t,:])
          #pdb.set_trace()
          if t - 1 >= 0:
              gradient[:,t-1,:] = gradient[:,t-1,:] + grad_hs

      #pdb.set_trace()
      return grad_x

sigmoid和tanh激活函數

sigmoid及其導數

\[sigmoid = \frac{1}{1+e^{-x}} \]

\[\frac{d}{dx}sigmoid = sigmoid(1-sigmoid) \]

tanh及其導數

\[tanh = \frac{e^x – e^{-x}}{e^x + e^{-x}} \]

\[\frac{d}{dx}tanh = 1 – tanh^2 \]

門控單元實現

        文件: cutedl/rnn_layers.py, 類名: GateUint

        門控單元是RNN層基礎的參數單元. 和Dense層類似,它是Layer的子類,負責學習和使用參數。但在學習和使用參數的方式上有很大的不同:

  • Dense有兩個參數矩陣, GateUnit有3個參數矩陣.
  • Dense在一次反向傳播過程中只使用當前的梯度學習參數,而GateUnit會累積每個時間步的梯度。

        下面我們會主要看一下GateUnit特別之處的代碼.

        在__ init__方法中定義參數和棧:

    #3個參數
    self.__W = None #當前時間步in_batch權重參數
    self.__Wh = None #上一步輸出的權重參數
    self.__b = None #偏置量參數

    #輸入棧
    self.__hs = []  #上一步輸出
    self.__in_batchs = [] #當前時間步的in_batch

        正向傳播:

  def forward(self, in_batch, hs, training):
      W = self.__W.value
      b = self.__b.value
      Wh = self.__Wh.value

      out = in_batch @ W + hs @ Wh + b

      if training:
          #向前傳播訓練時把上一個時間步的輸出和當前時間步的in_batch壓棧
          self.__hs.append(hs)
          self.__in_batchs.append(in_batch)

          #確保反向傳播開始時參數的梯度為空
          self.__W.gradient = None
          self.__Wh.gradient = None
          self.__b.gradient = None

      return self.activation(out)

        反向傳播:

def backward(self, gradient):
    grad = self.activation.grad(gradient)

    W = self.__W.value
    Wh = self.__Wh.value
    pre_hs = self.__hs.pop()
    in_batch = self.__in_batchs.pop()

    grad_in_batch = grad @ W.T
    grad_W = in_batch.T @ grad
    grad_hs = grad @ Wh.T
    grad_Wh = pre_hs.T @ grad
    grad_b = grad.sum(axis=0)

    #反向傳播計算
    if self.__W.gradient is None:
        #當前批次第一次
        self.__W.gradient = grad_W
    else:
        #累積當前批次的所有梯度
        self.__W.gradient = self.__W.gradient + grad_W

    if self.__Wh.gradient is None:
        self.__Wh.gradient = grad_Wh
    else:
        self.__Wh.gradient = self.__Wh.gradient +  grad_Wh

    if self.__b.gradient is None:
        self.__b.gradient = grad_b
    else:
        self.__b.gradient = self.__b.gradient + grad_b

    return grad_in_batch, grad_hs

GRU實現

        文件: cutedl/rnn_layers.py, 類名: GRU

        隱藏單初始化:

def set_parent(self, parent):
    super().set_parent(parent)

    out_units = self.out_units
    in_units = self.in_units

    #pdb.set_trace()
    #重置門
    self.__g_reset = GateUnit(out_units, in_units)
    #更新門
    self.__g_update = GateUnit(out_units, in_units)
    #候選輸出門
    self.__g_cddout = GateUnit(out_units, in_units, activation='tanh')

    self.__g_reset.set_parent(self)
    self.__g_update.set_parent(self)
    self.__g_cddout.set_parent(self)

    #重置門乘法單元
    self.__u_gr = MultiplyUnit()
    #輸出單元
    self.__u_out = GRUOutUnit()

        向前傳播:

  def hiden_forward(self, in_batch, pre_hs, training):
      gr = self.__g_reset.forward(in_batch, pre_hs, training)
      gu = self.__g_update.forward(in_batch, pre_hs, training)
      ugr = self.__u_gr.forward(gr, pre_hs, training)
      cddo = self.__g_cddout.forward(in_batch, ugr, training)

      hs = self.__u_out.forward(gu, pre_hs, cddo, training)

      return hs

        反向傳播:

def hiden_backward(self, gradient):

    grad_gu, grad_pre_hs, grad_cddo = self.__u_out.backward(gradient)
    #pdb.set_trace()
    grad_in_batch, grad_ugr = self.__g_cddout.backward(grad_cddo)

    #計算梯度的過程中需要累積上一層輸出的梯度
    grad_gr, g_pre_hs = self.__u_gr.backward(grad_ugr)
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_update.backward(grad_gu)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_reset.backward(grad_gr)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    #pdb.set_trace()
    return grad_in_batch, grad_pre_hs    

LSTM實現

        文件: cutedl/rnn_layers.py, 類名: LSTM

        隱藏單元初始化:

def set_parent(self, layer):
    super().set_parent(layer)

    in_units = self.in_units
    out_units = self.out_units

    #輸入門
    self.__g_in = GateUnit(out_units, in_units)
    #遺忘門
    self.__g_forget = GateUnit(out_units, in_units)
    #輸出門
    self.__g_out = GateUnit(out_units, in_units)
    #記憶門
    self.__g_memory = GateUnit(out_units, in_units, activation='tanh')

    self.__g_in.set_parent(self)
    self.__g_forget.set_parent(self)
    self.__g_out.set_parent(self)
    self.__g_memory.set_parent(self)

    #記憶單元
    self.__memory_unit =LSTMMemoryUnit()
    #輸出單元
    self.__out_unit = LSTMOutUnit()

        向前傳播:

def hiden_forward(self, in_batch, hs, training):
    g_in = self.__g_in.forward(in_batch, hs, training)
    #pdb.set_trace()
    g_forget = self.__g_forget.forward(in_batch, hs, training)
    g_out = self.__g_out.forward(in_batch, hs, training)
    g_memory = self.__g_memory.forward(in_batch, hs, training)

    memory = self.__memory_unit.forward(g_forget, g_in, g_memory, training)
    cur_hs = self.__out_unit.forward(g_out, memory, training)

    return cur_hs

        反向傳播:

def hiden_backward(self, gradient):
    #pdb.set_trace()
    grad_out, grad_memory = self.__out_unit.backward(gradient)
    grad_forget, grad_in, grad_gm = self.__memory_unit.backward(grad_memory)

    grad_in_batch, grad_hs = self.__g_memory.backward(grad_gm)
    tmp1, tmp2 = self.__g_out.backward(grad_out)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_forget.backward(grad_forget)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_in.backward(grad_in)
    grad_in_batch += tmp1
    grad_hs += tmp2

    return grad_in_batch, grad_hs

驗證

        接下來, 驗證示例將會構建一個簡單的RNN模型, 使用該模型擬合一個正餘弦疊加函數:

#採樣函數
def sample_function(x):
    y = 3*np.sin(2 * x * np.pi) + np.cos(x * np.pi) + np.random.uniform(-0.05,0.05,len(x))
    return y

        訓練數據集和測試數據集在這個函數的不同定義域區間內樣. 訓練數據集的採樣區間為[1, 200.01), 測試數據集的採樣區間為[200.02, 240.002). 模型任務是預測這個函數值的序列.

        示例代碼在examples/rnn/fit_function.py文件中.

使用GRU構建的模型

def fit_gru():
    model = Model([
                rnn.GRU(32, 1),
                nn.Filter(),
                nn.Dense(32),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('gru', model)

訓練報告:

使用LSTM構建的模型

def fit_lstm():
    model = Model([
                rnn.LSTM(32, 1),
                nn.Filter(),
                nn.Dense(2),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('lstm', model)

訓練報告:

總結

        這個階段,框架新增了RNN的兩個最常見的實現:GRU和LSTM, 相應地增加了它需要的激活函數. cute-dl已經具備了構建最基礎RNN模型的能力。通過驗證發現, GRU模型和LSTM模型在簡單任務上都表現出了很好的性能。會添加嵌入層,使框架能夠構建文本分類任務的模型,然後在imdb-review(電影評價)數據集上進行驗證.

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

博斯普魯斯海峽變色 出現正港土耳其藍

摘錄自2020年5月27日中央社報導

「每日晨報」(Daily Sabah)報導,博斯普魯斯海峽(Bosporus)水色自26日起轉變成「土耳其藍」。伊斯坦堡科技大學(Istanbul Technical University)教授托羅斯(Huseyin Toros)認為,東北風是導致「海水變色」主要原因。

托羅斯指出:「單細胞生物被東北風曳引進入博斯普魯斯海峽,海水表面經過折射,轉變成土耳其藍色。在此一大氣環境下的氣流、海平面下的活動、不同微生物、白天陽光變化等因素也可能導致海水顏色產生變化。」他表示,海水將於幾天內恢復「本色」。

美國國家航空暨太空總署(NASA)的衛星於當年5月29日首度補捉到黑海浮游生物激增的圖像。漁夫們相信,海中出現大量浮游生物意味當年鯷魚產量將會大增。但是浮游生物也會消耗水中大量氧氣,從而對其他海洋生物造成傷害。

土地水文
土地利用
國際新聞
土耳其
海水
港口
浮游生物

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

掌握SpringBoot-2.3的容器探針:基礎篇

歡迎訪問我的GitHub

  • 地址:https://github.com/zq2599/blog_demos
  • 內容:原創文章分類匯總,及配套源碼,涉及Java、Docker、K8S、DevOPS等

關於《SpringBoot-2.3容器化技術》系列

《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲計算時代依舊緊跟主流,保持競爭力;
全系列文章分為主題和輔助兩部分,主題部分如下:

  1. 《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  2. 《詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
  3. 《掌握SpringBoot-2.3的容器探針:基礎篇》;
  4. 《掌握SpringBoot-2.3的容器探針:深入篇》;
  5. 《掌握SpringBoot-2.3的容器探針:實戰篇》;
  • 輔助部分是一些參考資料和備忘總結,如下:
  1. 《SpringBoot-2.3鏡像方案為什麼要做多個layer》;
  2. 《設置非root賬號不用sudo直接執行docker命令》;
  3. 《開發階段,將SpringBoot應用快速部署到K8S》;

SpringBoot容器探針系列文章簡介

為了讓應用更適應容器化環境,SpringBoot2.3版本推出了新的探針技術,《掌握SpringBoot-2.3的容器探針》系列旨在與您一起學習和實踐這些新技術,分為三個階段:

  1. 基礎篇:即本文,對容器探針的相關知識點進行梳理和學習;
  2. 深入篇:繼續深入學習探針相關的知識點;
  3. 實戰篇:將springboot應用部署在kubernetes環境,並使用最新的探針技術;

探針特性的官方信息

  • 如下圖紅框所示,2.3版本的容器探針特性早在預覽版(v2.3.0.M4)就已經發布:

  • 如今v2.3.0.RELEASE已發布,可以放心的學習和使用該特性了,首先把基礎知識點列出來,確保準備工作OK;

知識點整理

下面是掌握探針技術所需的基礎知識,也是本文的主要內容:

  1. kubernetes的存活探針livenessProbe
  2. kubernetes的就緒探針readinessProbe
  3. SpringBoot的actuator

接下來逐個學習,有了這些知識積累,我們才能更好的閱讀官方資料,開發適合自己業務場景的探針;

kubernetes的存活探針livenessProbe

  1. kubernetes的探針涉及的內容是很多的,這裏只提和SpringBoot相關的部分;
  2. kubelet 使用存活探針livenessProbe來知道什麼時候要重啟容器;
  3. 下圖是kubernetes官網的存活探針示例,幾個關鍵參數已經做了詳細說明:
  1. 可見如果我們的SpringBoot應用發布到kubernetes環境,只要應用還健康,livenessProbe對應的地址就要能響應200-400的返回碼;

kubernetes的就緒探針readinessProbe

  1. 有時候,應用程序會暫時性的不能提供通信服務。例如,應用程序在啟動時可能需要加載很大的數據或配置文件,或是啟動后要依賴等待外部服務。在這種情況下,既不想殺死應用程序,也不想給它發送請求。Kubernetes 提供了就緒探測器來發現並緩解這些情況。容器所在 Pod 上報還未就緒的信息,並且不接受通過 Kubernetes Service 的流量。
  2. 就緒探測器的配置和存活探測器的配置相似,唯一區別就是要使用 readinessProbe字段,而不是 livenessProbe 字段;
  3. 簡單的說,就緒探針正常的容器,k8s就認為是可以對外提供服務的,相應的請求也會被調度到該容器上來;

SpringBoot的actuator

  1. 簡單來說,actuator是用來幫助用戶監控和操作SprinBoot應用的,這些監控和操作都可以通過http請求實現,如下圖,http://localhost:8080/actuator/health 地址返回的是應用的健康狀態:
  1. 下面是常用的actuator地址,訪問不同的地址可以得到不同的信息:
  1. 在SpringBoot-2.3版本中,actuator新增了兩個地址:/actuator/health/liveness/actuator/health/readiness,前者用作kubernetes的存活探針,後者用作kubernetes的就緒探針

畫外音:SpringBoot的探針技術就這點東西?

  1. 文章看到這裏,您可能覺得索然無味:所謂的容器探針特性如此簡單,新增兩個actuator地址留給kubernetes的存活和就緒探針用,只要這兩個地址響應正常,kubernetes就判定該容器正常;
  2. 大多數時候,上述結論並無不妥,SpringBoot官方給出的推薦配置如下圖,我們只要照搬即可:
  1. 冷靜下來仔細思考,有三個問題似乎沒有解決:
  • 首先,SpringBoot為kubernetes提供了兩個actuator項,但是那些並未部署在kubernetes的SringBoot應用呢?用不上這兩項也要對外暴露這兩個服務地址嗎?

  • 其次,就緒探針是什麼時候開始返回200返回碼的?應用啟動階段,業務服務可能需要一段時間才能正常工作,就緒探針要是提前返回了200,那k8s就認為容器可以正常工作了,這時候把外部請求調度過來是無法正常響應的,所以搞清楚就緒探針的狀態變化邏輯很重要;

  • 最後,也是最重要的一點:有的場景下,例如外部依賴服務異常、本地全局異常等情況下,業務不想對外提供服務,等到問題解決后業務又可以對外提供服務了,如果此時我們能自己寫代碼控制就緒探針的返回碼,那就做到了控制kubernetes是否將外部請求調度到此容器上,這可是個很實用的功能!

還需要繼續深入

面對上述三個問題您是否會感慨:看似簡單的容器探針技術,想要用好還需掌握更多知識,接下來的文章中咱們一起努力吧,從知識覆蓋到實戰操練,終究會掌握這門實用技術;

歡迎關注我的公眾號:程序員欣宸

https://github.com/zq2599/blog_demos

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

同叫X7的自主SUV,誰才是更強的後起之秀

動力總成兩者都是1。5L自吸發動機+5擋手動的動力組合。漢騰X7最大可輸出149匹馬力和215牛米,在2000轉才爆發最大扭矩到4000轉就開始衰減。而斯威X7最大可輸出156馬力和230牛米,在1750轉便爆發最大扭矩,可持續到4500轉。從賬面來看,斯威X7的動力要優於漢騰X7,出力也更早,同時還是直噴發動機,能更好地減少燃油損失。

前言

自主品牌有不少車型都喜歡用X7來命名,所以一說起X7總能提起一堆車型。最近上市了兩款X7,分別屬於漢騰和斯威的,這兩個牌子也是新近建立的汽車廠商,兩款車型的對碰到底會擦出什麼火花,讓我們拭目以待。

為了公平起見,我會拿漢騰X7的手動豪華型與斯威X7的手動舒適型來對比,兩者價差為1100元。

漢騰汽車-漢騰X7

2016款 1.5T 手動豪華型

指導價:8.98萬

華晨鑫源-斯威X7

2016款 1.5T 手動舒適型

指導價:9.09萬

外觀設計

漢騰X7的前臉看起來比較扁平,整個設計拉得比較寬,給人一種大車感。尾部則更為簡約,雙邊兩出的排氣口顯得較為大體。

斯威X7從前面看,會覺得有一點點像寶馬X3,特別是那兩組圓形大燈,看起來與寶馬的天使之瞳更為相像。大面積的鍍鉻格柵也很符合國人的口味。側面看,斯威X7像一台升高版的MpV。尾部設計則比較緊湊,最具亮點是那個M標識,像極了寶馬M系的標識,只是那幾條斜線的顏色有所不同。

內飾設計

漢騰X7的中控會稍向駕駛員一側傾斜,木紋裝飾板也顯得很高級,用料方面對得起這個價格。只是那塊中控屏幕有點汽配城的feel,拖累整個內飾觀感。

斯威X7的中控設計則看起來先進許多,與榮威RX5的類似,也是一整塊觸控屏,乾淨整潔,會比較耐看。

動力總成

兩者都是1.5L自吸發動機+5擋手動的動力組合。漢騰X7最大可輸出149匹馬力和215牛米,在2000轉才爆發最大扭矩到4000轉就開始衰減。而斯威X7最大可輸出156馬力和230牛米,在1750轉便爆發最大扭矩,可持續到4500轉。從賬面來看,斯威X7的動力要優於漢騰X7,出力也更早,同時還是直噴發動機,能更好地減少燃油損失。

底盤表現

漢騰X7的后懸架用的是多連桿獨立懸架,濾震動作會比較富有韌性,迎接衝擊時,有點點硬。不過整體還是偏軟,過彎時的側傾較大。

斯威X7的后懸架用的是扭力梁,不過調校得還是很不錯。在過一些減速帶時,對車子的小彈跳抑製得很到位。

空間表現

↑漢騰X7的後座↑

↑漢騰X7的後排中央↑

↑斯威X7的後排↑

兩者的空間都屬於中規中矩那種,但是漢騰X7的後排中央會有一點凸起,而斯威X7則幾乎全平。關鍵是,斯威X7是一款7座車型,第三排短途坐一下乘客是完全沒有問題的。不過斯威X7的第二排座椅不可前後調節,即便如此空間也很可觀。

配置對比

從配置的對比上來看,兩者各有千秋,不過漢騰X7上到豪華型也沒有車身穩定系統,這點很說不過去。具體的對比,可以看下圖。

編者總結

兩輛X7的對比上,斯威X7會更為佔優,具體表現在動力、空間表現與配置都要優於漢騰X7。作為兩個後起之秀,這樣的定價確實也是足夠親民。雖然許多方面都不佔優勢,但是漢騰X7較為傳統的內飾設計,也許可以俘獲不少大叔的芳心。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

1.5T+CVT還是DCT?這款自主高品質轎車將配新的動力系統

但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。艾瑞澤7的售價區間為7。99-9。59萬元,1。5T車型的起售價也是為7。99萬元,預測同等配置的1。

最近,我們得到了一些關於奇瑞艾瑞澤7的1.5T自動擋車型的路試照片,新車將會在2017年上市銷售。現款的艾瑞澤7的1.5T車型只配備了5擋手動變速箱,這對於那些既想要大動力又想要自動擋的消費者就感到比較痛苦了。

選擇1.6L車型吧,動力較弱。選擇1.5T車型吧!動力是足夠了,但是就是手動擋開着會比較累。所以奇瑞也意識到了這個問題,抓緊給艾瑞澤7的1.5T車型配備自動擋,豐富自己的產品線,以滿足消費者日趨多樣化的選擇。

從外觀上來看,這次曝光的1.5T自動擋車型和現款車型基本一致,畢竟只是增加了一款車型,又不是改款或者換代,所以外觀是不會有很大變化的。

前臉依舊是“X”造型的設計,前大燈靠內側部分採用了熏黑的處理,以增加運動感。現款車型已經是標配了日間行車燈了,作為自動檔車型自然不可缺少。尾部的“TCI”說明了它是1.5T車型的身份。

內飾也是和現款基本一致,看起來也比較時尚,目測做工應該不錯。主要的變化是由手動擋換車了自動擋。同時配置也比較豐富,ESp、上坡輔助、胎壓監測、倒車雷達、真皮方向盤、GpS、中控大屏幕、藍牙、手機映射等都會配備。

至於動力系統,新車肯定搭載1.5L渦輪增壓發動機,但是變速箱現在還沒有確定的消息,有可能是CVT,也有可能是和瑞虎7一樣的6速雙離合。不過,編者推測雙離合的概率會更大一點。

其實編者一直覺得艾瑞澤7是一款質感非常不錯的車子,四輪獨立懸挂,1.5T發動機動力十足,配置也很厚道,做工也不錯,行駛質感在同級別處於領先地位,操控較好。但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。

艾瑞澤7的售價區間為7.99-9.59萬元,1.5T車型的起售價也是為7.99萬元,預測同等配置的1.5T自動擋車型將會比手動擋車型最低貴8000元,所以自動擋的售價將會接近9萬元。對於這個價格,你能接受么?

競爭對手

吉利帝豪GL

指導價:7.88-11.38萬

帝豪GL的1.3T自動擋車型的起售價為9.78萬,這價格要比艾瑞澤7稍微貴一點,但是帝豪GL的熱度要比艾瑞澤7大很多。作為目前比較熱門的一款車,帝豪GL將是艾瑞澤7的強大對手。

長安逸動

指導價:8.09-24.99萬

逸動的自動擋車型的起步價為8.99萬元,搭配4AT變速箱,目前還有着七八千的優惠,雖然價格可能比艾瑞澤7便宜,但是逸動的發動機為1.6L 125馬力。

一汽-大眾-寶來

指導價:10.78-15.38萬

寶來自動擋的起售價為11.98萬,目前也有着一兩萬的優惠,是加一些錢買寶來還是直接買艾瑞澤7?這是一個值得思考的問題。

所以說艾瑞澤7上市之後會面臨很多強大的對手,但是編者相信只要價格足夠接地氣,就不用發愁銷量問題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

※回頭車貨運收費標準

Pytest單元測試框架——Pytest+Allure+Jenkins的應用

一、簡介

  pytest+allure+jenkins進行接口測試、生成測試報告、結合jenkins進行集成。

  pytest是python的一種單元測試框架,與python自帶的unittest測試框架類似,但是比unittest框架使用起來更簡潔,效率更高

  allure-pytest是python的一個第三方庫。用於連接pytest和allure,使它們可以配合在一起使用。

  allure-pytest基於pytest的原始執行結果生成適用於allure的json格式結果。該json格式結果可以用於後續適用allure生成html結果。

二、安裝  

  1、安裝pytest,命令行或終端中輸入

1 pip install pytest

  2、安裝allure-pytest,安裝成功

1 pip install allure-pytest

  allure-pytest安裝成功后截圖如下。

  3、下載安裝JDK

  官方下載:https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

  安裝與配置不作闡述請諒解  

  4、下載安裝Jenkins

  官方下載:https://www.jenkins.io/

  安裝與配置不作闡述請諒解

三、下載Allure並配置

  下載allure並配置

  1、allure官網下載:https://github.com/allure-framework/allure2/releases

  如下圖所示

  2、allure2下載下來是一個zip的壓縮包,我們要解壓至自己的文件目錄下(可解壓放至項目的測試用例下或python安裝目錄下),自己可找到文件即可。

  3、打開allure2目錄,找到bin目錄,複製bin文件目錄, 然後進行環境變量的配置,設置環境變量的目的就是讓系統無論在哪個目錄下都可以運行allure2。

  4、環境變量設置:(桌面——我的電腦——右鍵屬性——高級系統配置——環境變量——系統變量——Path——編輯環境變量——把我們上面複製的目錄路徑新增至環境變量中即可)

  設置環境變量,如下圖所示。

 

  5、配置好后,打開cmd終端,輸入allure,出現以下幫助文檔,就說明配置成功了。

 四、Allure裝飾器描述

  Allure裝飾器

 五、Pytest+Allure的應用

  上述我們講了一些理論的知識,下面我們就來實戰練習一下吧。進一步理解Pytest+allure如何結合應用的。

  1、新建testcase文件夾,用來存放測試用例,新建test_Demo.py文件,作為pytest的具體測試用例文件。在test_Demo.py文件中輸入以下代碼。

 1 # test_Demo.py
 2 # Creator:wuwei
 3 # Date:2020-06-09
 4 
 5 import pytest
 6 import requests
 7 import allure
 8 import sys
 9 sys.dont_write_bytecode = True
10 
11 @allure.epic('測試描述'.center(30, '*'))
12 @allure.feature('測試模塊')
13 @allure.suite('測試套件')
14 class TestPytestOne():
15     @allure.story('用戶故事描述:用例一')
16     @allure.title('測試標題:用例一')
17     @allure.description('測試用例描述:用例一')
18     @allure.testcase('測試用例地址:https://www.baidu.com/')
19     @allure.tag('測試用例標籤:用例一')
20     def test_one(self):
21         print('執行第一個用例')
22         assert 1 == 1
23 
24     @allure.story('用戶故事描述:用例二')
25     @allure.title('測試標題:用例二')
26     @allure.description('測試用例描述:用例二')
27     @allure.testcase('測試用例地址:https://www.sogou.com/')
28     @allure.tag('測試用例標籤:用例二')
29     def test_two(self,action):
30         print('執行第二個用例')
31         assert True == True
32 
33 # pytest運行
34 if __name__ == "__main__":
35     pytest.main(['-s', '-v', 'test_Demo.py', '-q', '--alluredir', '../reports'])

  2、我們再來創建一個conftest.py,conftest用來共享數據及不同層次之間共享使用的文件,測試用例的前置和後置中一般都可以用到的。

 1 # conftest.py
 2 # Creator:wuwei
 3 # Date:2020-06-09
 4 
 5 import pytest
 6 import sys
 7 sys.dont_write_bytecode = True
 8 
 9 @pytest.fixture()
10 def action():
11     print("測試用例開始".center(30, '*'))
12     yield
13     print("測試用例結束".center(30, '*'))

  3、運行test_Demo.py文件,test_Demo文件中已經pytest+allure的結合,可查看allure的運行結果,可看出在根目錄中生成了一個reports文件夾,其中生成了測試報告的json文件,這裏面的json文件可通過allure生成html的測試報告。
  運行test_Demo.py,終端显示如下圖所示。

   生成的Json格式的測試報告,如下圖所示。

   4、使用allure將json文件生成html的測試報告,定位至項目文件根目錄下,運行以下命令,會在項目根目錄下生成一個名為allure_reports的文件夾,用來存放html測試報告。命令下如所示。

1 allure generate reports -o allure_reports/

  成功運行allure,結果如下圖所示。

  項目根目錄下的allure_reports文件,存放的是allure生成的測試報告。可看出文件下有一個HTML文件,可通過Python的編輯器Pycharm來打開該HTML文件(測試報告),或可通過allure命令來打開該HTML,展示HTML測試報告。如下所示。

  測試報告文件,HTML測試報告如下。

  allure命令打開HTML測試報告。命令如下所示。

1 allure open allure_reports/

  如下圖所示。

   打開生成的HTML測試報告如下圖所示。

 六、Pytest+Allure+Jenkins的應用

  1、Jenkins插件網站上下載allure插件最新版本:

    http://mirrors.jenkins-ci.org/plugins/allure-jenkins-plugin/

  2、Jenkins的安裝我已經在Postman+Newman+Git+Jenkins的篇章中講過了,沒看小夥伴可以看一下那篇文章。確認Jenkins服務是否開啟。確認開啟后,在瀏覽器中輸入:http://localhost:8080/,進入Jenkins配置頁面。

  3、http://localhost:8080/,登錄Jenkins的頁面,在管理Jenkins——插件管理——高級中找到上傳插件。將(1)步驟中下載的.hpi的文件上傳至jenkins上。

  上傳安裝好的allure-jenkins-plugin的插件,安裝完成並成功,是藍色圓點显示,因我已經安裝過一次,會提示已經安裝,重啟Jenkins即可生效。(注意:不是關閉瀏覽器重新打開,而是重啟Jenkins服務

  4、全局變量中配置allure路徑與JDK的路徑,

  配置JDK安裝的路徑,如下圖所示。

  配置allure安裝的路徑,如下圖所示。

  5、新建Item,配置構建后的allure測試報告生成。這裏配置Pytest執行完成之後,生成的allure文件所在的目錄位置。

  項目中生成allure的json測試報告的位置。需與下面構建后操作中的Results的Path文件一致。

  構建后操作的allure生成測試報告的配置,如下圖所示

  6、配置構建命令。就是上述在cmd中運行項目時的命令。如下圖所示。

注意:運行后發現有報錯。“Build step ‘Execute Windows batch command’ marked build as failure”,解決方案,在運行項目的命令后添加exit 0。如下圖所示。

  7、修改運行命令后我們再來運行一下。我們可發現運行后,allure裏面沒任務數據。因為我們還沒設置運行的項目路徑。設置工作空間,打開工作空間目錄,將我們的項目複製到jenkins的工作目錄中。

  我們可將代碼傳至GitHub上,在Jenkins中設置相關Github項目的配置,也可進行Jenkins部署。我在Postman+Newman+Git+Jenkins這篇博客里就應用到了。有興趣的可參考看看這篇Jenkins如何Git項目。在這裏我們使用本地項目來部署。

  測試報告無數據因為工作空間裏面沒有項目配置。

  複製項目至Jenkins工作空間的目錄中。

  8、添加項目后,我們再運行一下,藍點則為運行成功,可看到後面已經生成了allure的測試報告了。可直接點擊後面的alluree圖標跳轉至HTML的測試報告。如下圖所示。

  allure生成的HTML測試報告

八、總結

  上述我們聊了下pytest+allure+jenkins如何結合集成一起使用的,本地啟動jenkins,運行項目,調用allure生成測試報告。也簡單的做了一個小Demo。後期我將結合Requests接口測試和seleniumWeb測試應用至具體項目中。

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準