用費曼技巧學編程,香不香?

引子

 有一本講諾貝爾獎獲得者,物理學家費曼的書,叫做《發現的樂趣》,書中寫到一個費曼小時候的故事:

 “我們家有《大不列顛百科全書》,我還是小孩子的時候,父親就常常讓我坐在他腿上,給我讀些《大不列顛百科全書》。比如說,我們讀關於恐龍的部分,書上可能講雷龍或其他什麼龍,書上會說:“這傢伙有 25 英尺高,腦袋寬 6 英尺。” 

這時父親就停下來,說:“我們來看看這句話什麼意思。這句話的意思是:假如它站在我們家的前院里,它是那麼高,高到足以把頭從窗戶伸進來。不過呢,它也可能遇到點麻煩,因為它的腦袋比窗戶稍微寬了些,要是它伸進頭來,會擠破窗戶。 

費曼說:凡是我們讀到的東西,我們都盡量把它轉化成某種現實,從這裏我學到一個本領——凡我所讀的內容,我總設法通過某種轉換,弄明白它究竟什麼意思,它到底在說什麼。

 

費曼技巧

 

費曼技巧,或者說費曼學習法是一種以教促學的方法,一共有四步(已經知道的可以無視,直接跳過): 

(1) 選擇新概念/新知識, 自己先去學習它。 

(2) 假裝當一個老師,去教授別人 

想象你面對一群小白,怎麼把這個概念講給他們聽,讓他們理解呢? 

把你講解的思路也寫到紙上,如果實在不想寫,可以說出來。 

非常重要!!!不要讓你的思路停留在大腦中,因為大腦中對於知識點之間的關聯會有些想當然的、錯誤的假設,說出來或者寫出來能找到這些“盲點”!!

 

(3) 如果你在教授的過程中遇到了麻煩,卡了殼,返回去學習。 

重新去看書,搜相關資料,問別人,倒逼自己把這個概念搞清楚, 然後回到第二步,繼續給小白講授。

 

(4) 簡化你的語言。 

目標是用你自己的語言,非專業的詞彙去解釋這個概念。盡量做到簡單直白,或者找到比喻來表達。 

非常簡單的過程,對吧? 

 

實戰演練

我們來用個例子來演練一下,有請碼農翻身頭號主人公張大胖出場。 

張大胖正在學習Java,這一天他遇到了一個新的概念:“動態代理”  (注意是學習這個概念,不是具體實現), 非常抽象,在日常編程中幾乎不會直接使用,理解起來有難度。

 

第一步,自學

 張大胖看了動態代理的介紹,書上列舉出一堆煩人的代碼來展示這個東西是怎麼使用的,比如有個接口(IHelloWorld)及其實現類(HelloWorld), 然後有個InvocationHandler的實現,最後用Proxy.newProxyInstance(….)創建一個新的類出來,這些都是什麼鬼?啰里啰唆的。

 

第二步,張大胖嘗試教一下小白(當然這裏的小白至少得懂點兒Java)

 

張大胖:動態代理嘛,很簡單,就是給定一個接口和實現類,再加上一個InvocationHandler , 動態代理這個技術可以在運行時創建一個新的代理類出來。 

小白:張老師, 新的代理類有什麼用? 

張大胖:舉個例子,有個叫IHelloWorld接口及其實現類HelloWorld,它有一個叫sayHello()的方法。可以在sayHello()之前和之後,額外加一些日誌的輸出。 

(在講解一個概念的時候,舉例和類比很重要,人類習慣於通過例子來學習,從具體走向抽象) 

小白:那我直接寫一個新的類,比如HelloWorldEx,把日誌輸出添加到其中不就行了,為什麼還要用Proxy.newProxyInstance(……)這麼麻煩的方法?

public class HelloWorldEx implements IHelloWorld{
    IHelloWorld hw;
    public HelloWorldEx(IHelloWorld hw){
        this.hw = hw;
    }    
    public void sayHello(){        
        Logger.startLog();
        hw.sayHello();
        Logger.endLog();
    }
}

  

張大胖無法回答這個問題,卡殼了! 

第三步,回過頭去看書,學習。

書中也沒有解釋,唉! 

仔細想一想,手動寫一個類HelloWorldEx和用Proxy.newProxyInstance來創建,區別到底是什麼? 

實現的功能是相同的,但是HelloWorldEx需要事先寫好,編譯后不能改了,相當於寫死了!如果我想對Order類,Employee類,Department類,也想加點兒日誌,還得寫個OrderEx,EmployeeEx,DepartmentEx的類,太麻煩了! 

而Proxy.newProxyInstance這種方法,可以在程序運行的時候為任意類動態地創建增強的類。 

事先寫死的叫做靜態代理,Proxy.newProxyInstance這種方式叫做動態代理,更加靈活。 

張大胖覺得這麼解釋就通了。 

小白:為什麼要創建新的代理類,那個Proxy.newProxyInstance不能直接修改老的HelloWorld類嗎? 

張大胖再度卡殼,上網搜索,找到了答案,和Python,Ruby等方法不同,Java本質是一個靜態類型的語言,class一旦被裝入JVM,是不能修改,添加,刪除方法的,既然老的class不能修改,只能通過代理的方式來創建新的類了。 

小白:懂了,這個技術主要用在什麼地方啊? 難道只是加個日誌? 

張大胖第三次卡殼,只好再次搜索。 

原來動態代理使用得最多的是AOP,AOP中經常會以聲明的方式提出這樣的要求: 

某個包下所有add開頭的方法,在執行之前都要調用Logger.startLog()方法,在執行之後都要調用Logger.endLog()方法。 

或者對於所有以Service結尾的類,所有的方法執行之前都要調用tx.begin(),執行之後都要調用tx.commit(), 如果拋出異常的話調用tx.rollback()。

 

到此為止,張大胖可以這樣來給小白講述了: 

你不是用過Spring AOP嗎?AOP中經常有這樣的需求……  ,Spring想添加這些日誌和事務的功能,但是卻沒有辦法去修改用戶的類,它是框架啊,一是不知道用戶類的源碼,二是Java不允許再修改裝載入JVM的class。 

沒辦法,Spring只好在運行時找到用戶的類,然後操作字節碼動態創建一個新類,新類會對原有的類進行增強,添加日誌,事務這些功能,注意啊,這些都是在內存中動態創建的。 

這個技術就是Java的動態代理,不過它有個前提要求,就是用戶的類需要實現接口才行。我用一個簡單的例子給你說下,你就明白細節了……

 

第四步,簡化,比喻

上面的講解從文字上來說還是非常啰嗦的,用了很大篇幅來講解“為什麼”,因為理解了why ,剩下的就是細節了。  

如果你徹底理解了以後,動態代理的技術細節會在大腦中會建立這麼一幅圖景:

 

$HelloWorld100就是那個代理類,它和HelloWorld都實現了IHelloWorld這個接口。 

如果一定要用個比喻來說,它們倆就是“兄弟關係”,CgLib提供了另外一種對現有類增強的辦法,動態生成的類繼承了現有的類,兩者是“父子關係”。

  

小結

 怎麼樣?用這種(假裝)教授別人,層層遞進、自我逼問的方法是不是很有效果?收益很大?  

用這種辦法,實際上就是逼着你把大腦中的盲點和一些想當然的假設給暴露出來,效果要比單純地閱讀和記憶好得多,趕緊在學習中試一下吧!

  

更多精彩文章,盡在碼農翻身

 

我是一個線程

TCP/IP之大明郵差

一個故事講完Https

CPU 阿甘

Javascript: 一個屌絲的逆襲

微服務把我坑了

如何降低程序員的工資?

程序員,你得選准跑路的時間!

兩年,我學會了所有的編程語言!

一直CRUD,一直996,我煩透了,我要轉型

字節碼萬歲!

上帝託夢給我說:一切皆文件

Node.js :我只需要一個店小二

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

【其他文章推薦】

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

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

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

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

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

Python流程控制語句詳解

1.程序結構

計算機在解決問題時,分別是順序執行所有語句、選擇執行部分語句、循環執行部分語句,分別是:順序結構、選擇結構、循環結構。如下圖:

2.選擇語句

  2.1最簡單的if語句

  Python使用保留字if來組成選擇語句,其格式如下:

if 表達式:
   代碼塊

  表達式可以是一個單純的布爾值或者變量,也可以是比較表達式或者邏輯表達式,如果表達式值為真,則執行“代碼塊”;如果值為假,就跳過“代碼塊”,執行後面語句,如圖

  

   注:1.在Python中,當表達式的值為非零的數或者非空的字符串時,if語句也認為是條件成立(即為真值)。

··     2.使用if語句時,如果只有一條語句,那麼語句塊可以直接寫到冒號“:”的右側。但為了代碼可讀性不建議這麼做。

     3.常見錯誤: 

1.if語句後面未加冒號
number = 3
if number == 3   #後面未加冒號,正確的是結尾處添加英文半角的冒號:if number == 3:
    print(number)
2.使用if語句時,如果在符合條件時,需要執行多個語句,一定要記得按照邏輯順序進行代碼縮進,否則程序的本意會有變化,但程序不會報錯,且bug不容易發現。
代碼1:
number = 3
if number == 3:
  print(number)
print('這個是5')
代碼2:
number = 3
if number == 3:
  print(number)
  print('這個是5'

  2.2.if  …… else 語句

  Python中提供 if …else 語句來解決兩個選擇問題,其格式如下:

if 表達式:
    語句塊1
else:
    語句塊2

  使用 if …else 語句時,表達式可以是一個單純的布爾值或者變量,也可以是比較表達式或者邏輯表達式,如果表達式值為真,則執行“代碼塊”;如果值為假,執行else後面的代碼塊。如圖所示:

  

  技巧:

if…else額語句可以使用條件表達式進行簡化,如下:
a = 5
if a > 0:
    b = a
else:
    b = -a
print(b)

簡化:
a = 5
b = a if a > 0 else -a
print(b)

  注:1. 在使用else 語句時,else一定不可以單獨使用,它必須和保留字if一起搭配使用。

    2.程序中使用if…else 語句時,如果出現多個if 語句多餘else語句的情況,那麼該else語句將會根據確定該else 語句屬於哪個if語句。

  2.3.if…elif…else語句

  在開發程序時遇到多選一的情況,則可以使用if …elif…else語句,具體情況如下:

if 表達式1:
    語句塊1
elif 表達式2:
    語句塊2
elif 表達式3:
    語句塊3
…
else:
    語句塊n

  使用 if …elif…else 語句時,表達式可以是一個單純的布爾值或者變量,也可以是比較表達式或者邏輯表達式,如果表達式值為真,則執行語句;如果值為假,則跳過該執行語句,進行下一個elif判斷,只有表達式全部為假的情況下,執行else後面的代碼塊。如圖所示:

   注:1. if 和 elif 都需要判斷表達式的真假,而 else 則不需要判斷;另外 elif 和 elif 都需要跟 if 一起使用,不能單獨使用。

     2. 使用if語句時盡量避免遵循以下原則:

(1).當使用布爾類型的變量作為判斷條件時,假設布爾類型變量為flag,較為規範格式;
if flag:   #表示為真
if not flag #表示為假
不符合規範格式:
if flag == True:
if flag == False:
(2).使用 " if 1 == a: " 這樣的書寫格式可以防止錯寫成 " if  a = 1: "這種形式,從而避免出錯 

  2.4 if 語句的嵌套

  前面已經介紹了3種形式的 if 語句,這三種都可以進行相互嵌套:

  (1) . 在最簡單的if語句中嵌套 if……else語句,形式如下:

if 表達式1: if 表達式2: 語句塊1 else: 語句塊2

  (2). 在if……else中嵌套if……else語句,形式如下:

if 表達式1:
    if 表達式2:
        語句塊1
    else:
        語句塊2
else:
    if 表達式3:
        語句塊3
    else:
        語句塊4

  注:if 選擇語句可以有多種嵌套方式,開發時可以可以根據自身的需要進行選擇合適的嵌套方式,但一定要嚴格控制好不同級別代碼的縮進量。

3.條件表達式

  在程序開發過程中,經常會根據表達式的結果,有條件的進行賦值,例如返回最大值:

a = 6
b = 3
if a > b:
    c = a
else:
    c = b

  針對以上代碼,使用條件表達式進行簡化,如下:

a = 6
b = 3
c = a if a > b else b

4.循環語句

  4.1 while 循環

  while循環是通過一個條件來控制是否要繼續反覆執行循環體(循環體是指一組被重複執行的語句)中的語句。

while 條件表達式:
    循環體

  當條件表達式的返回值為真時,則執行循環體中的語句,執行完畢后,重新判斷條件表達式的返回值,直到表達式返回的結果為假是退出循環體。

  

   注:在使用while循環語句時,一定不要忘記添加將循環條件改變為Flase的代碼,否則,將產生死循環。但開發中也離不開死循環,可根據情況進行編寫。

   4.2. for循環

  for 循環是一個依次重複執行的循環,通常適用於枚舉、遍歷序列和對象中的元素。語法如下:

for 迭代變量 in 可迭代對象:
    循環體

  迭代變量用於保存讀除的值,對象為遍歷或迭代的對象,該對象可以是任何有序的序列對象,如字符串,列表,元組等,循環體為一組被重複執行的語句。

  

  for循環語句可以最基本的應用就是進行數值循環和遍歷字符串。還可以進行遍歷列表、元組、集合和字典。

  4.3. 循環嵌套

  在Python中,是允許在一個循環體中嵌套另一個循環。

  (1). 在while循環中嵌套while循環

while 條件表達式1:
    while 條件表達式2:
        循環體2
    循環體1

  (2). 在for 循環中嵌套 for 循環

for 迭代變量1 in 對象1:
    for 迭代變量2 in 對象2:
        循環體2
    循環體1

  (3). 在while 循環中嵌套 for 循環

while 條件表達式:
    for 迭代變量 in 對象:
        循環體2
    循環體1

  (4). 在 for 循環中嵌套 while 循環

for 迭代變量 in 對象:
    while 條件表達式:
        循環體2
    循環體1

  特殊案例:九九乘法表

for i in range(0,10):
    for j in range(1,i+1):
        print(str(j) + "*"  + str(i) + "=" + str(i * j) +"\t" ,end = " "
    print("")

5.跳轉語句

  當循環滿足一定條件時,程序會一直執行下去,如果需要在中間離開循環,也就是for循環結束重複之前,或者while循環找到結束條件之前,即break語句和continue語句。

  5.1 break語句

  break可以終止當前循環,包括for循環和while循環在內的所有控制語句。

  在while中使用break語句:

 

while 條件表達式1:
    執行語句
    if 條件表達式2:
        break

  在for 中使用break語句

for 迭代變量 in 對象:
    if 條件表達式:
        break

  

           while語句使用break                                                             for語句中使用break

  5.2.continue語句

  continue語句的作用沒有break語句強大,他只能終止本次循環而提前進入下次循環中。

  在while中使用continue語句

while 條件表達式1:
    執行代碼
    if 條件表達式2:
        continue

  在for 中使用continue語句

for 迭代變量 in 對象:
    if 條件表達式:
        continue

         

            while語句使用continue                                                     for語句中使用continue

  注:break與continue的區別

    break語句一般會結合if 語句進行搭配使用,表示在某種條件下,跳出循環。如果使用嵌套循環,break語句將跳出最內層循環。

    continue語句一般也會結合if語句進行搭配使用,表示在某種條件下,跳出當前循環的剩下語句,繼續進行下一輪循環,如果使用嵌套循環,continue語句將只跳過最內層循環中剩餘語句。

6.pass語句

  在Python中pass語句表示空語句,它不做任何事情,一般起到站位作用,常用在代碼調試等。

#例:
for i in range(1,10):    #輸出1~10的數不包含10
    if i % 2 == 0:         #判斷是否是偶數   
        print(i,end="")     #在同一行打印偶數 
    else:                      #不是偶數
        pass                    #佔位符,不做任何事情,直接跳過

#輸出結果為:
2 4 6 8

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

ThreadLocal的使用場景分析

目錄

一.ThreadLocal介紹

二.使用場景1——數據庫事務問題

  2.1 問題背景

  2.2 方案1-修改接口傳參

  2.3 方案2-使用ThreadLocal

三.使用場景2——日誌追蹤問題

四.其他使用場景

 

 

一.ThreadLocal介紹

  我們知道,變量從作用域範圍進行分類,可以分為“全局變量”、“局部變量”兩種:

  1.全局變量(global variable),比如類的靜態屬性(加static關鍵字),在類的整個生命周期都有效;

  2.局部變量(local variable),比如在一個方法中定義的變量,作用域只是在當前方法內,方法執行完畢后,變量就銷毀(釋放)了;

  使用全局變量,當多個線程同時修改靜態屬性,就容易出現併發問題,導致臟數據;而局部變量一般來說不會出現併發問題(在方法中開啟多線程併發修改局部變量,仍可能引起併發問題);

  再看ThreadLocal,從名稱上就能知道,它可以用來保存局部變量,只不過這個“局部”是指“線程”作用域,也就是說,該變量在該線程的整個生命周期中有效。

 

二.使用場景1——數據庫事務問題

2.1問題背景

  下面介紹示例,UserService調用UserDao刪除用戶信息,涉及到兩張表的操作,所以用到了數據庫事務:

  數據庫封裝類DbUtils

public class DbUtils {

    // 使用C3P0連接池
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");

    public static Connection getConnectionFromPool() throws SQLException {
        return dataSource.getConnection();
    }

    // 省略其他方法.....
}

  UserService代碼如下:  

public class UserService {

    private UserDao userDao;

    public void deleteUserInfo(Integer id, String operator) {
        Connection connection = null;
        try {
            // 從連接池中獲取一個連接
            connection = DbUtils.getConnectionFromPool();
            // 因為涉及事務操作,所以需要關閉自動提交
            connection.setAutoCommit(false);

            // 事務涉及兩步操作,刪除用戶表,增加操作日誌
            userDao.deleteUserById(id);
            userDao.addOperateLog(id, operator);

            connection.commit();
        } catch (SQLException e) {
            // 回滾操作
            try {
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException ex) {
            }
        } finally {
            DbUtils.freeConnection(connection);
        }
    }
}

  下面是UserDao,省略了部分代碼:

package cn.ganlixin.dao;
import cn.ganlixin.util.DbUtils;
import java.sql.Connection;

/**
 * @author ganlixin
 * @create 2020-06-12
 */
public class UserDao {

    public void deleteUserById(Integer id) {
        // 從連接池中獲取一個數據連接
        Connection connection = DbUtils.getConnectionFromPool();

        // 利用獲取的數據庫連接,執行sql...........刪除用戶表的一條數據

        // 歸還連接給連接池
        DbUtils.freeConnection(connection);
    }

    public void addOperateLog(Integer id, String operator) {
        // 從連接池中獲取一個數據連接
        Connection connection = DbUtils.getConnectionFromPool();

        // 利用獲取的數據庫連接,執行sql...........插入一條記錄到操作日誌表

        // 歸還連接給連接池
        DbUtils.freeConnection(connection);
    }
}

  上面的代碼乍一看,好像沒啥問題,但是仔細看,其實是存在問題的!!問題出在哪兒呢?就出在從數據庫連接池獲取連接哪個位置。

  1.UserService會從數據庫連接池獲取一個連接,關閉該連接的自動提交;

  2.UserService然後調用UserDao的兩個接口進行數據庫操作;

  3.UserDao的兩個接口,都會從數據庫連接池獲取一個連接,然後執行sql;

  注意,第1步和第3步獲得的連接不一定是同一個!!!!這才是關鍵。

  如果UserService和UserDao獲取的數據庫連接不是同一個,那麼UserService中關閉自動提交的數據庫連接,並不是UserDao接口中執行sql的數據庫連接,當userService中捕獲異常,即使執行rollback,userDao中的sql已經執行完了,並不會回滾,所以數據已經出現不一致!!!

 

2.2方案1-修改接口傳參

  上面的例子中,因為UserService和UserDao獲取的連接不是同一個,所以並不能保證事務原子性;那麼只要能夠解決這個問題,就可以保證了

  可以修改userDao中的代碼,不要每次在UserDao中從數據庫連接池獲取連接,而是增加一個參數,該參數就是數據庫連接,有UserService傳入,這樣就能保證UserService和UserDao使用同一個數據庫連接了

public class UserDao {

    public void deleteUserById(Connection connection, Integer id) {
        // 利用傳入的數據庫連接,執行sql...........刪除用戶表的一條數據
    }

    public void addOperateLog(Connection connection, Integer id, String operator) {
        // 利用傳入的數據庫連接,執行sql...........插入一條記錄到操作日誌表
    }
}

  UserService調用接口時,傳入數據庫連接,修改代碼后如下:

// 事務涉及兩步操作,刪除用戶表,增加操作日誌
// 新增參數傳入數據庫連接,保證UserService和UserDao使用同一個連接
userDao.deleteUserById(connection, id);
userDao.addOperateLog(connection, id, operator);

  這樣做,的確是能解決數據庫事務的問題,但是並不推薦這樣做,耦合度太高,不利於維護,修改起來也不方便;

 

2.3使用ThreadLocal解決

  ThreadLocal可以保存當前線程有效的變量,正好適合解決這個問題,而且改動的點也特別小,只需要在DbUtils獲取連接的時候,將獲取到的連接存到ThreadLocal中即可:

public class DbUtils {

    // 使用C3P0連接池
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");

    // 創建threadLocal對象,保存每個線程的數據庫連接對象
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static Connection getConnectionFromPool() throws SQLException {
        if (threadLocal.get() == null) {
            threadLocal.set(dataSource.getConnection());
        }

        return threadLocal.get();
    }

    // 省略其他方法.....
}

  然後UserService和UserDao中,恢復最初的版本,UserService和UserDao中都調用DbUtils獲取數據庫連接,此時他們獲取到的連接則是同一個Connection對象,就可以解決數據庫事務問題了。

 

三.使用場景2——日誌追蹤問題

  如果理解了場景1的數據庫事務問題,那麼對於本小節的日誌追蹤,光看標題就知道是怎麼回事了;

  開發過程時,會在項目中打很多的日誌,一般來說,查看日誌的時候,都是通過關鍵字去找日誌,這就需要我們在打日誌的時候明確的寫入某些標識,比如用戶ID、訂單號、流水號…

  如果業務比較複雜,那麼一個請求的處理流程就會比較長,如果將這麼一長串的流程給串起來,也可以通過前面說的用戶ID、訂單號、流水號來串,但有個問題,某些接口沒有用戶ID或者訂單號作為參數!!!!這個時候,雖然可以像場景1中給接口增加用戶ID或者訂單號作為參數,但是並不推薦這麼做。

  此時可以就可以使用ThreadLocal,封裝一個工具類,提供唯一標識(可以是用戶ID、訂單號、或者是分佈式全局ID),示例如下:

package cn.ganlixin.util;

/**
 * 描述:
 * 日誌追蹤工具類,設置和獲取traceId,
 * 此處的traceId使用snowFlake雪花數算法,詳情可以參考:https://www.cnblogs.com/-beyond/p/12452632.html
 *
 * @author ganlixin
 * @create 2020-06-12
 */
public class TraceUtils {
    // 創建ThreadLocal靜態屬性,存Long類型的uuid
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    // 全局id生成器(雪花數算法)
    private static final SnowFlakeIdGenerator generator = new SnowFlakeIdGenerator(1, 1);

    public static void setUuid(String uuid) {
        // 雪花數算法
        threadLocal.set(generator.nextId());
    }

    public static Long getUuid() {
        if (threadLocal.get() == null) {
            threadLocal.set(generator.nextId());
        }
        return threadLocal.get();
    }
}

  

  使用示例:

@Slf4j
public class UserService {

    private UserDao userDao;

    public void deleteUserInfo(Integer id, String operator) {
        log.info("traceId:{}, id:{}, operator:{}", TraceUtils.getUuid(), id, operator);
        
        //.....
    }
}

@Slf4j
public class UserDao {

    public void deleteUserById(Connection connection, Integer id) {
        log.info("traceId:{}, id:{}", TraceUtils.getUuid(), id);
    }
}

  

 四.其他場景

  其他場景,其實就是利用ThreadLocal“線程私有且線程間互不影響”特性,除了上面的兩個場景,常見的還有用來記錄用戶的登錄狀態(當然也可以用session或者cookie實現)。

 

  原文地址:https://www.cnblogs.com/-beyond/p/13111015.html 

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

【其他文章推薦】

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

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

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

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

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

餐桌上的減碳計畫:智庫研究改善糧食系統 可實現2050年減碳目標20%

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

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

美西野火更加猖狂 一周燒掉兩年份的土地

摘錄自2020年9月13日聯合報報導

從美國加州、俄勒岡州到華盛頓州的太平洋沿岸,近日山林大火不斷延燒,規模空前。科學家警告,氣候變遷將帶來天然災害,目前狀況只是前奏。BBC報導,雖然是強風等自然因素使美國西岸野火蔓延,不過,是人為活動導致氣候暖化,才使野火火勢變大且破壞性更強。

世界有紀錄以來最熱10年,有9年落在2005年之後。聯合國上周警告,2016年到2020年這5年,很可能成為有紀錄以來最熱的5年。俄勒岡州與加州的均溫從1900年來上升超過攝氏1度。

加州有紀錄以來規模最大的20場野火中,有6場發生在今年。在俄勒岡州,野火上周焚燒的面積,接近野火平均每年燒毀面積的2倍。

賓州州立大學地球系統科學中心主任曼恩說,今後的天災會有多嚴重,取決於人類如何降低溫室氣體排放,危險的氣候變遷顯然已經到來,「關鍵在於我們要讓問題惡化到多嚴重」。

氣候變遷
國際新聞
美國
森林野火

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

【其他文章推薦】

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

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

※想知道最厲害的網頁設計公司"嚨底家"!

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

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

車價太高等等就能便宜?你可能想得有點多了!

另外,對於是買你上的車型來說,大多數都是3年一改款、5年一換代的節奏,所以正處於這兩個階段的在售車型在終端優惠也是最大的,而值得一說的是,這類車型無論再怎麼等,在最終的售價方面也不會出現太大差距,所以此時的建議是早買早享受。

你認為最有效的砍價套路是什麼?

在看來最佳的砍價技巧是以退為進!這一個砍價方式也被應用在我們的日常生活當中,譬如看上了某樣商品,在跟商家扯皮條的時候報出心理價位,然後假裝不買走人。據不完全統計,這種砍價方式的成功率高達80%。

雖然以上是我們的慣用手法,但值得一說的是,使用這一種方式購車的成功率會大大下降,畢竟買車並不是買一顆白菜這麼簡單!有不少消費者在購車時遇到的問題便是車價過高超過了原本的預算。

於是也出現了最普遍的做法:嫌差價太高,決定先回去考慮,等上一段時間,等優惠更大的時候再入手。

然而問題來了!

價格太高等一等

會不會有更大的優惠呢?

答案是:會!但是並非所有車型都通用,首先你必須明白,汽車在原則上來說都算是一件商品而他們同樣存在着一個生命周期,分別是:市場導入期、成長期、成熟期、衰退期。而根據這幾個階段,我們也可以總結出來:

除非是廠家在新款上市時主動放出來的優惠政策以外,正常的新款車型基本上都是沒有任何優惠的、甚至乎有部分車型還必須加價!譬如…

辣評:第十代思域的到來可以說是讓東本打了一仗漂亮的翻身仗,不僅如此,作為加價的鼻祖,旗下的車型CR-V依然賣得火熱,所以想等優惠?至少短期內並沒有。當然,如果你有充足的預算,原價購買也沒有任何問題。不過對於市面上大多數新車來說,上市后等上幾個月,這個時候車型的終端優惠也會相繼放出,此時購買的性價比會更高。

另外,對於是買你上的車型來說,大多數都是3年一改款、5年一換代的節奏,所以正處於這兩個階段的在售車型在終端優惠也是最大的,而值得一說的是,這類車型無論再怎麼等,在最終的售價方面也不會出現太大差距,所以此時的建議是早買早享受。

當然這是對於市面上90%的車型來說,有部分車型你想等它有優惠的活,還是建議你放棄,譬如…

辣評:雖然已經上市了很長一段時間,但受到代號為8AR-FTS的2.0T發動機產能的限制,漢蘭達目前依然處於一個供不應求的狀態,總結下來便是我明擺着要加價提車,不要拉倒!

購車時“拖”並不是一件好事!

車型優惠大意味着該款車型已經進入到了成熟期或者衰退期,而且價格越高的車型,優惠幅度也會越大!如果你選擇一等再等,你有可能會碰到這幾個問題:

1、老款換新款了!於是原有的優惠價格也會隨之上調,最後得不償失。

2、等上數月價格有所鬆動,但經不住國家的政策,譬如…

目前已經進入到了10月份,簡單來說還有2個月,我們即將迎來2018年!而值得一說的是,近幾年在購車政策上變動實在有點大,譬如像上一年購置稅還是5折,而2017年則改為7.5折,未來在2018年裡,購置稅是否恢復原價了?只能說有可能。

因小失大也是我們最常碰到的事情,所以在購車錢首先要搞清楚4S店的砍價方式:

購車時注意事項:

1、你能不能用心理價位買到心儀之車,決定權在4S店手上

2、銷售知識跟你談價格,而最終決定價格的是上一層的領導

3、砍價時需要具備必買之心,才能擁有資本與銷售談判

4、制定車型的心理價位,詳情可參考各大汽車門戶的車型落地價

砍價技巧:

1、不要相信銷售們的首次報價,坐下來慢慢詳談

2、在這家4S店砍完價再到另外一家4S店砍價(損招)

3、關係戶,起碼認識到經理級別以上的朋友,這樣價格才會低出新高度。

(找熟人購車需先注意):建議自己先砍好好價格,再找熟人看能不能獲取更大的優惠幅度,否則不僅欠了人情還買了高價車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

月銷4萬輛還優惠1多萬元?暗訪4款熱門SUV優惠的真實情況

從上圖中我們可以看到除了一些常規的保險費用以外,多了一項綜合服務費的收費項目。經過我們向銷售諮詢,該項服務費主要包括:pDI檢測費、洗車費等一些雜費。聽完銷售跟我們詳細的講解后,內心是這樣的。所以說4S店的套路永遠都是非常多的,不過只要你詳細問下去,4S店的銷售也並不能說得清楚這筆費用究竟是做什麼的。

無論是買手機還是買電腦,作為消費者的我們肯定非常關注這款手機或者電腦的銷量情況,越多人購買的產品一定是我們需要購買到的好產品。

不同的是,在國內買車是家庭中一件非常大消費的購物行為,所以也有不少精明的消費者參考銷量榜進行買車,再參考各大汽車網站推送出來的優惠幅度,興奮地跑去當地的4S店諮詢,結果是這樣的……

談起買車經歷,很多人都覺得自己被4S店的銷售小賺了一筆,然後羡慕別人拿到一個比自己更低的購車價格,卻不知道應該從哪一項費用上跟銷售談優惠,所以我們以暗訪的形式針對廣州本地的4S店一探究竟。

不愁銷量,價格堅挺!

看完小標題的讀者,肯定能基本猜測出這是哪一款國產SUV,它就是在銷量榜排在第一位的哈弗H6。每個月都能夠獲得不錯的銷量,以至於我們在進入哈弗4S店的時候,被一群哈弗H6圍住,可想而知4S店的老闆笑得多開心。

全新的哈弗H6與老款車型對比,最大的亮點就是十分霸氣的外觀,讓人感覺這款SUV大氣、穩重。不過今天我們主要來調侃銷售,看看如此熱門的一款國產SUV究竟能夠拿到多少優惠。

在諮詢哈弗H6的優惠前,我們先詢問銷售哪一款車型賣得比較好,哪款車身顏色比較熱門,店裡庫存的現車多不多?

在談論價格優惠之前,我們便在網上了解了哈弗H6的價格優惠幅度,根據網上不同的汽車網站显示優惠幅度在1.4萬。不過我們在諮詢銷售的時候,優惠1.4萬的車型並不是全新的哈弗H6,而是舊款車型,否則賣不出去。

由於這款車型屬於2.0T排量車型,所以無法享受購置稅的優惠,廠家為此給這款車型優惠三千。除此以外,並沒有其他現金上的優惠,看來在價格方面還是比較堅挺的,不過我們表示急需用車的時候,銷售突然為我們推薦一個到店活動,優惠當天公布,我們預測現金優惠應該在1-2千左右+大禮包之類。

對於在城市裡面打拚的消費者來說,買車需要家裡人的支持,所以我們這次要求銷售對一次性和貸款兩種方式進行說明,我們可以為讀者提供最划算的買車方式。

不管是全款購車還是分期購車,銷售都是以優惠完3千購置稅的價錢進行接下來的計算。我們先以保險費用一樣的價格進行計算,如果選擇全款購車情況,僅僅在上牌費和貸款手續費上都為自己優惠了接近7千多塊,最後的全款購車價格159819元的基礎上減去七千。

4S店給出的貸款政策優惠的確非常吸引,特別是後面“免息”二字。如果以6萬進行計算,那麼首付需要7.68萬,對於很多消費者來說還想獲得更低的首付,此時銷售告知我們,能貸款到9萬。

與全款購車的價格對比,貸款的費用越高,分期的時間越長,所花費的錢就越多。所以如果需要分期購車,不妨考慮貸款費用和貸款年限在4S店政策支持範圍內。

現金優惠8千,不要精品再優惠五千!

如今在銷量榜上排在第四位的傳祺GS4,優惠幅度還是挺吸引人的。在現金優惠的幅度上與哈弗H6相比,兩者相差接近三倍。我們以同樣的預算,要求銷售給我們推薦店裡最熱銷的車型。

在談論價格優惠之前,我們便在網上同樣參考GS4的價格優惠幅度,根據網上不同的汽車網站显示優惠幅度在1.46萬。同樣這些巨額優惠也就只能吸引客戶到店,實際消費者想購買的車型並沒有這麼大優惠。

在談價格前,銷售非常直接告知我們目前這款車型的優惠是8000元,除此以外就沒有任何優惠了。然後我們便直接開始聊全款和分期的事情。

從上圖中我們可以看到除了一些常規的保險費用以外,多了一項綜合服務費的收費項目。經過我們向銷售諮詢,該項服務費主要包括:pDI檢測費、洗車費等一些雜費。聽完銷售跟我們詳細的講解后,內心是這樣的……

所以說4S店的套路永遠都是非常多的,不過只要你詳細問下去,4S店的銷售也並不能說得清楚這筆費用究竟是做什麼的。那麼就有機會爭取更多的優惠價格了。

相比哈弗H6來說,GS4的手續費便宜500元,同時在按揭的利息上也有一定的區別,GS4貸款年限24期所需利息更低,相反哈弗h6需要12%。等我們聊完分期購車的事情后,故事情節好像發生了改變。

對於購車大禮包這些東西,很多4S店都為他們貼上“原廠”的標籤,瞬間價格翻了幾倍,聽起來也更高大上了。作為唯一一間答應我們不要禮包換取優惠的店,可以得到購車優惠還是能夠繼續砍下去,根據以上銷售提供的優惠,這款車大概優惠在1.4萬左右。

現金優惠+利息補貼,不同的優惠方式!

來到長安的4S店,我們同樣將購車預算購車意向告知銷售顧問,便得到長安CS75比較熱銷的車型以及庫存等情況。

出乎意外的是,到店后銷售所給出的優惠幅度與我們在網上不同的汽車網站上看到的優惠幅度有一定的相差,因為在來之前我們查詢到長安CS75的現金優惠只有2千元,但是到店后所得到的是接近四千元。想必這肯定是國慶庫存太多所導致的結果,否則不會出現高於網上參考的優惠幅度。

為了更切合實際,這次CS75的價格優惠全部以貸款購車形式,對比一下首付多與少哪種更貼合自身的實際情況。

相信現在很多的消費者在購買金額比較大的商品都會有一種習慣,就是最好首付更低、分期時間更長,這樣沒有太大的生活壓力。的確,這次我們採用不同的首付金額,得出的結論就是:首付能給多點盡量給多點,所需貸款的金額也減少,每個月需要還的金額也隨之減低。

同時,4S店的購車優惠除了現金優惠4200元以外,分期購車還有5200元不限年限的利息補貼。由於最後的銷售給出的價格為142000元,我們提議將後面兩千元去掉,銷售也表示能夠向領導申請,所以整體的優惠也同樣接近1萬。

最美的國產SUV,直接1萬現金優惠!

對於國產車,想贏得消費者的芳心,顏值其實是挺重要的。被譽為最美國產SUV的榮威RX5,在優惠幅度上直接給出最高優惠1萬,對於消費者來說也算較大的驚喜,那麼榮威RX5哪款車型更值得我們參考呢?

參考汽車網站提供的優惠信息,與到店提供的優惠相差三千元。其中,同樣存在國慶庫存較多的原因,另外一個原因就是榮威RX5為了獲得更好的銷量表現,以更大的優惠幅度吸引消費者購買。

從上圖可知,現金優惠還是非常大的,對於全款購車的消費者來說的確是一件非常好的事情。不過上牌費高達四千元也是四款車型中最貴的,而且是硬性規定必須在店上牌。此時,銷售推薦我們購買一張國慶剩餘的優惠券,用300元可換購車的交強險和一年保養,整體算下來也就大概優惠一千元左右。

兩種不同的購車方式在總金額上相差6527元,相對其他三款車型來說,榮威RX5的按揭利息還是比較低的,因為需要按揭三年也只是需要6%的利息,對於希望輕鬆買車的消費者來說的確是不錯的貸款政策,唯一不足的地方,就是按揭的手續費收得相對較高。

總結

總之,能夠蹭上熱門的SUV,優惠肯定是比較少,還可能存在加價提車。這是很多消費者普遍存在的想法,但是通過暗訪看來,不一定存在這樣的情況。適合一次性全款買車的車型是:GS4和榮威RX5,同時榮威RX5貸款買車利息也是最低;長安CS75現金優惠適中,如果貸款買車建議選擇一年,因為廠家補貼5200元的利息,所以選擇一年的貸款年限是最划算的;至於哈弗H6,現金優惠幅度最小,如需貸款買車,建議還是選擇貸款6萬18個月免息的政策,否則並不划算。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

10萬買啥車?這5大終極問答能解決99.1%的選車難題!

還有划不划算。其實這裏還需考慮兩個問題:一、貨幣的貶值二、投資的收益第一點很簡單就是你今年花10萬元買的車這10萬元三年後可能就不值10萬元了又比如你貸10萬元買車這10萬元三年後可能也不值10萬元了可是這個必須考慮到貨幣的貶值率而按現在趨勢來說,是一直在貶值的第二點也舉個例子就是如果你有20萬元預算購車可是你只給10萬,貸款10萬再把剩下的10萬去投資(炒個房地產、買個理財產品什麼的)而當你所投資后得到的收益對於你10萬貸款所需的利息那這個貸款就很划算了可是具體購車貸款利率多少。

人生第一次買車

都有哪些情景?

情景一:

打開微信,找個比較懂車的朋友,上來就問

情景二:

找個汽車App或小程序,勾選條件篩出自己的意向車型

情景三:

先逛幾家4S店,看看哪個銷售妹子腿更長?

好吧,其實情況有很多種,可是最有效的方法應該是:打開微信→點擊公眾號→添加公眾號→搜索“車買買”→點擊關注,一系列購車選車用車評車信息任你看(這裏希望老闆可以看到,嘻嘻嘻),好吧,廢話說多了,直接上乾貨。

如果有足夠預算,就選擇進口車吧

要多大有多大、要多長有多長

要多快有多快、要多帥有多帥

然而理想和現實還是有很大差距的

畢竟不是每個人都叫思聰

而且還有一個姓王的爸爸

更多人還是得用自己辛苦賺來的錢

買一輛物有所值或物超所值的車

選擇自主或合資比起進口,性價比會更高一點吧

每次聊到自主和合資品牌

評論區似乎都兩極分化

要不就是以愛國的名義支持國產

要不就是要求更高的品質支持合資

在此虎哥就不參与任何政治言論了

(希望大家能理解)

不過只針對產品本身

虎哥的看法是:都可以買,喜歡就好

由於現在國人的消費水平越來越高

就算買到不好或者不合適自己的車

用一段時間賣掉好像也不是很虧

誰都說不準哪輛車的質量一定就好或壞

國產車也有五菱宏光這樣的神車

合資品牌也有很多斷軸燒機油的案例

你可以根據自己的見解去選

沒有見解,那自主跟合資可以一起考慮

相信每個男人都有個跑車夢吧?

可是跑車對於在座大部分人來說都不現實吧

我認為絕大部分跑車都只是男人們的大玩具

對於有錢又帥又單身的你,買來耍耍還是可以的

當然,今天虎哥並不是推薦你去買跑車上下班

而是告訴你除了轎車和SUV還有很多選擇

比如近期越來越火的MpV車型

裝得比轎車多,用起來比SUV更舒適更方便

比如越野能力和裝載能力都很強的皮卡

關於買轎車還是SUV的問答在網上實在太多

其實對於城市用車來說無非也就那幾個區別

轎車:操控好、加速快、更省油等等

SUV:坐姿高、視野好、裝得多等等

如果當你了解兩者的區別後還不會選

那你就很有必要去看看其它車型了

二手車性價比高是毋庸置疑的

不過二手車最大的問題就是“水太深”

對於不懂行的人來說沒有必要去嘗試

可是如果你有足夠的積累和經驗

肯定能用很低的價錢買到二手好車

(而且不用給中間商賺差價哦)

所以人生第一次買車

更多的是建議買新車

新車不僅可以給你更多的保障

而且還能給你一種超爽的新鮮感

就跟你小時候收到新玩具的感覺一樣

貸款不是都要收利息嗎?還有划不划算?

其實這裏還需考慮兩個問題:

一、貨幣的貶值

二、投資的收益

第一點很簡單

就是你今年花10萬元買的車

這10萬元三年後可能就不值10萬元了

又比如你貸10萬元買車

這10萬元三年後可能也不值10萬元了

可是這個必須考慮到貨幣的貶值率

而按現在趨勢來說,是一直在貶值的

第二點也舉個例子

就是如果你有20萬元預算購車

可是你只給10萬,貸款10萬

再把剩下的10萬去投資

(炒個房地產、買個理財產品什麼的)

而當你所投資后得到的收益

對於你10萬貸款所需的利息

那這個貸款就很划算了

可是具體購車貸款利率多少?

不同主機廠或經銷商所提供的政策不同

具體還需你親自去計算過才知道

而且那些說0%利率的優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

11_條件隨機場

  今天是2020年6月14號星期日。從這篇開始,發布時間就正常了。前邊的文章是在寒假寫好的,後來因為趕進度和改小論文,沒有及時整理。3月到6月,兩個半月的時間做了些什麼?真的好怕浪費了時間~《統計學習方法》這本書(第二版),大概在四月底就看個差不多了,半生不熟的好歹是通篇過了一遍,當然不止一遍,除了潛在狄利克雷分配。期間夾雜着手撕代碼的過程,因為腦容量有限的原因,寫代碼的時候,又要把書面內容重新過一遍。就連發布,也要重新過一遍,把重要的地方加上不同顏色…於是這個假期,《統計學習方法》這本書的每個章節,不知道反反覆復的過了多少遍,肯定不超過十遍…也就是看的多,不代表學的多…但是看了總歸是看了,也比不看強…

  CRF條件隨機場~本節說難,挺難;說簡單,也簡單。為什麼我覺得CRF和LR很像呢?可能有很多人在應用場景上反駁我,LR做數據分類問題,CRF是針對序列標註問題。我覺得像,提供一下幾個觀點(遞進着看):

  1.LR和CRF都是判別模型。這句話要從計算方式上來理解:LR是通過線性函數,對特徵向量擬合不同的權值,最後加和的線性函數值作為sigmoid函數的輸入,轉換為概率值(對數幾率)。CRF在公式(11.10)中,不,在更明顯的公式(11.15)中,wkfk(y,x)1->K加和。個人膚淺的理解:公式(11.15)就是對各個特徵擬合不同的權值wk,加和的線性函數值作為某個“求概率公式”的輸入。

  2.從觀點1出發,思考一下“特徵”。在LR中,“特徵”很明顯,就是特徵向量的各個分量,相當於一個分量一個特徵。LR擬合的w,其實就是看看每個特徵分量對分類結果占什麼樣的貢獻。在CRF中,“特徵”是定義在最大團上的,拋去“最大團”“勢函數”“特徵函數”這些抽象概念,CRF的“特徵”其實不就是定義在相互聯繫的兩個節點上嘛?相當於“成對”構成一個特徵。轉移特徵也好,狀態特徵也罷,線性鏈條件隨機場不就限定了“特徵”要產生在“成對”的關聯關係上。然後,公式(11.15)告訴我,[權值×特徵==》加和],CRF和LR計算線性函數值的方式沒區別啊(區別在特徵的定義上)。

  3.從觀點1出發,思考一下“概率的計算”。不知道大家有沒有仔細思考LR中,第六章公式(6.6)分母位置的1是怎麼計算的,或者為什麼要在分母添加這一項。是不是可以理解為exp(0*wx)=1,這樣能夠把分母作為歸一化項Z(x)看待。分母的1+exp(wx)看作Z(x)=exp(0*wx)+exp(1*wx)P(Y=1|x)=exp(1*wx)/Z(x),另一個類別Y=0的概率P(Y=0|x)=exp(0*wx)/Z(x),怎麼樣,香不香?這裏不就是兩個項的線性函數值進行了指數歸一化。在這個角度上,看CRF的概率計算方式,公式(11.15)和公式(11.16)不就是多了幾項嘛…多個項(K項)進行了歸一化,把數值轉換成了概率值,這這這,和softmax()指數歸一化不就是一回事嘛…然後再考慮考慮為什麼CRF中勢函數是嚴格正的…

  暫時先這三點,實際上都是圍繞[判別模型]提出的。

GitHub:https://github.com/wangzycloud/statistical-learning-method

條件隨機場

引入

  書中對條件隨機場CRF的描述是:給定一組輸入隨機變量條件下另一組輸出隨機變量的條件概率分佈模型,其特點是假設輸出隨機變量構成馬爾可夫隨機場。先拋去“輸出隨機變量構成馬爾可夫隨機場”這個限定條件不看,該模型是條件概率模型,並且是一組隨機變量,在另外一組隨機變量條件下的條件概率模型。

  組這個概念,限定了模型中發生作用的因素要產生在多個隨機變量上。類似這樣,形象不(僅代表個人理解哈~不能保證正確):

  像示意圖中表示的,如果一組隨機變量之間,關係是散亂無序的,我們該怎樣求解呢?各個組上隨機變量的關係都發現不了,何談尋找A組在B組條件下的條件概率分佈?因此,考慮對輸出組的隨機變量施加限定條件。人類最常做的就是發現規律,總結經驗。比如說,讓輸出組的隨機變量兩兩之間產生聯繫,會不會簡化問題?

  於是,模型變成了這樣。又或者,讓輸入組隨機變量和輸出組隨機變量,都變成序列關係。這時,問題變成了由輸入序列對輸出序列預測的判別模型。具體的,讓輸出組隨機變量構成馬爾可夫隨機場。需要的知識包括圖與無向圖、馬爾可夫性、無向圖模型的因子分解、團與最大團…

概率無向圖

  概率無向圖模型,又稱為馬爾可夫隨機場,是一個可以由無向圖表示的聯合概率分佈。“馬爾可夫隨機場”正式出場了,簡單講它是一個聯合概率分佈,是由無向圖表示的。想象無向圖的樣子,由結點和邊構成,且邊沒有方向(無箭頭)。深入一點,一個結點表示一個隨機變量,如果兩個結點之間有邊關聯,就認為這兩個結點具有關聯關係,並且是“相互關聯”(無向邊),相互產生影響。

  可以看到,“有向變無向”這個操作,讓限定條件變的更寬泛了,也就是讓CRF的應用場景增多。當然,無向圖不只是上圖的線性序列關係。根據馬爾可夫性的不同,概率無向圖模型也有很多種類。接下來由圖開始,具體介紹一下模型的定義。

  看一下馬爾可夫性,我的理解是這樣:在無向圖G上給定Cs(與集合C有關的一堆結點),AsBs條件獨立。AsBs之間沒有邊關聯,就算利用Cs作為結點把AsBs連接了起來,兩者仍然保持獨立關係。

  有三種類型,分別是成對馬爾可夫性、局部馬爾可夫性、全局馬爾可夫性,具體定義較晦澀。如下:

  由以上馬爾可夫性,概率無向圖模型定義為:

  有了概率無向圖的定義,實際上我們更關心的是如何求解各個隨機變量的聯合概率分佈。那麼,對給定的概率無向圖模型,我們能不能找到將整體的聯合概率寫成若干個子聯合概率的乘積的方法?也就是將聯合概率進行因子分解(類似:6=2×3,整數6可以分解成因子2乘以因子3),就能方便模型的學習與計算了。事實上,概率無向圖模型最大的特點就是易於因子分解。這裏留幾個疑問,“整體的”變成若干個“子聯合概率的”這句話,子聯合概率我不是可以理解成“子集”的,這個子集要如何劃分?我們知道,圖結構動不動就會像漁網一樣,橫七豎八的全是連接邊。

  另外,聯合概率的因子分解,不僅要能解決無向圖上劃分小子集的問題,還要保證各個小子集乘積的聯合概率要等於整體的聯合概率。

  這裏劃分小子集的問題,由無向圖上的“最大團”解決的,定義如下:

  例子中描述的,應該是挺清楚的,最大團中的任何兩個結點均有邊連接。於是,概率無向圖模型的聯合概率分佈可以表示為其最大團上的隨機變量的函數的乘積,這種操作稱為概率無向圖模型的因子分解。接下來,考慮聯合概率如何計算。

  不管整體的聯合概率也好,子集的聯合概率也好,無非是表示一大撮和一小撮隨機變量的區別。問題在於,這個東西它是概率啊。怎麼在圖結構上體現概率啊…公式(11.15)和公式(11.16)給出了答案,對每個Yi(N個狀態序列中的一個序列Yi)求分值,將各個“得分”歸一化后的結果作為該無向圖Yi的聯合概率。實際上也就是每一個無向圖Yi是所有狀態序列中的一種情況,這個Yi的得分佔所有情況的比例,就認為是Yi的聯合概率。

  這裏Yi的得分,是如何反映的呢?可以從公式(11.15)看出,圖中所有最大團上各個最大團勢函數的乘積,作為Yi的得分。

  串到CRF特徵抓取的過程中,我認為這裏的勢函數,就是抓取最大團集合中的結點特徵,並且是各個結點之間的關聯特徵。由於訓練過程中擬合參數wk,所以這裏只需要將每個特徵作為正的“量”就可以了。勢函數僅作標示“特徵”的度量(metric,擬合的wk決定該特徵對“得分”影響的正負、大小程度(對應開頭的觀點3)。

  現在利用的這種方式,不是空穴來風,是有定理來保證的:

條件隨機場的定義與形式

  條件隨機場是給定隨機變量X條件下,隨機變量Y的馬爾可夫隨機場。我們要用到的CRF指的是定義在線性鏈上的特殊的條件隨機場,稱為線性鏈條件隨機場。一般情況下,在條件概率模型P(Y|X)中,Y是輸出變量,表示標記序列;X是輸入變量,表示觀測序列。這裏,我們也把標記序列稱為狀態序列。在學習時,利用訓練數據通過極大似然估計或正則化的極大似然估計得到條件概率模型;預測時,對於給定的輸入序列x,求出條件概率模型最大的輸出序列y。

  我認為在給定輸入序列X的條件下,計算輸出序列Y的條件概率(且序列Y構成線性鏈條件隨機場)的場景是與圖11.4相符合的。文中描述的,現實中一般假設X和Y有相同的圖結構,形如圖11.5這樣,這就好像對模型進行了簡化,讓模型更容易實現。換一個角度看,圖11.4中的X表達的是輸入序列整體對輸出序列Y的影響,具體到如何產生影響,如圖11.5表達的這樣,輸入序列X的每個分量Xi,與相同時刻的Yi相互影響。

  這裏留兩個疑問,(Xi,Yi)的相互影響關係,會不會傳遞到t時刻?與時刻t有沒有關係?

  看一下線性鏈條件隨機場的具體定義:

  圖示如下,公式(11.9)實際上表達了:馬爾可夫性規定Yi(Y3)只與相鄰的結點有關聯關係(藍框代表等式左側,紅框代表等式右側)

  根據定理11.1,可以給出線性鏈條件隨機場P(Y|X)的因子分解式,各因子是定義在相鄰兩個結點(最大團)上的勢函數。兩個疑問的解答,相信後面部分的描述中可以找到答案。接下來看一下條件隨機場的三種形式。

1)條件隨機場的參數化形式

  該基本形式,表示給定輸入序列x,對輸出序列y預測的條件概率。實際上現在的具體定義,就是把最大團、勢函數、概率求解等概念落實下來,變成可以量化的東西。公式(11.11)為歸一化項,相當於把Y的每種情況(各種Y序列)考慮到,作為求概率時的分母(每種情況下,各個得分加和)。在某個情況Y下,計算該Y的概率,需要先得到所有勢函數構成的特徵和,也就是“得分分值”。

  這裏的特徵來自兩部分,yi-1yi橫向上的轉移特徵tk(·)yix豎向上的狀態特徵sl(·)

  看一下具體定義:

  這裏tksl的取值,類似於最大熵模型中特徵函數的定義。我的理解,取1或者0,是標記這個特徵有還是沒有。至於此時該特徵的貢獻是大是小,是正是負,這取決於模型訓練時擬合的參數情況(λ、μ)。

  舉個例子,看下計算時的具體過程:

  該標記序列y=(y1,y2,y3)的非規範化概率,實際上就是通過存在的特徵×對應權重,然後加和。符合[權值×特徵==》加和]這個方式。

2)條件隨機場的簡化形式

  注意到條件隨機場公式(11.10)中同一個特徵在各個位置都有定義,可以對同一個特徵在各個位置求和,從而將局部特徵轉化為一個全局特徵函數。這樣就可以將條件隨機場寫成權值向量和特徵向量的內積形式,也就是現在要描述的簡化形式。實際上,這更進一步貼近了[權值×特徵==》加和]這種方式。

  如上圖所示,假設每個位置i都有這個局部特徵(沒有該特徵的話,特徵值為0),每個i都要針對這一個局部特徵求一個參數,這個工作量似乎有點大,並且重複。那麼,是不是可以把該局部變量上升到全局特徵,每個局部位置特徵值加和,讓這一個特徵在全局上學習一個權重參數。一來減少沒必要的參數估計,二來可以把重點放在“特徵”的增加和估計上。

  這裏公式(11.13)表明了全局特徵是通過各個位置i的局部特徵加和得到的。

  接下來,用向量形式進行表示:

  這樣就得到了條件隨機場的簡化形式。經過知識點細化,再抽象,看CRF公式(11.19)和公式(11.20)和LR中的公式(6.5)和公式(6.6)多像。各個特徵的線性函數值(得分),通過指數歸一化轉化成概率,學習的過程擬合各個參數。

3)條件隨機場的矩陣形式

  先看定義:

  這裏先記一個要點,對每個標記序列引進特殊的起點和終點狀態標記,這時標註序列的概率Pw(y|x)可以通過矩陣形式表示並有效計算。划重點:引進特殊起點和重點標記之後,才可以通過矩陣形式計算。

  不知道怎麼分析這個地方,現在從以下幾個問題開始:

  ①公式(11.21)中Mi的下標iyi的下標i是否表示同一個意思?

  我認為這兩個下標i指的不是同一個意思,yii表示該矩陣中狀態的取值;Mi中的i表示第i個矩陣,實際上每個位置都有對應的矩陣。同時矩陣的維度是M×M維的,因為yi可以取到M個狀態(yiM個狀態,yi-1的某個狀態可以轉移到M个中任意一個,yi-1M個狀態,所以轉移有M×M)。例M2(1,3)表示在狀態序列的第2個位置上(t=2),由t=1時刻的“狀態1”轉移到t=2的“狀態3”的非規範化概率。

  Mi中的元素是怎麼計算的,是什麼?

  計算方式見公式(11.22)和公式(11.23)。我先想到的是,Mi矩陣為yi-1yi的轉移矩陣,每個時刻yi都是M種狀態,轉移也都是yi-1yi這些轉移方式,計算方式都由公式(11.23)計算。那麼,不同的Mi之間的區別是什麼呢。我的理解是針對不同的時刻t、不同的x,特徵函數是否存在和相應的權值大小,決定了Mi不同。

  ③公式(11.24)的分子部分n+1個矩陣的適當元素的乘積,是什麼?

  仔細看一下CRF的簡化形式中公式(11.15)的分子部分,利用公式(11.13)對i進行展開,有:

  是不是就變成了公式(11.24)的分子部分n+1個矩陣的適當元素乘積的形式。也就是說,CRF的矩陣形式來源於簡化形式,至於為什麼會有這種方式,我覺得是便於計算吧,下邊前後向算法會用到這種形式。

  ④公式(11.25)規範化因子,是什麼?

  看上去是對所有序列的非規範化概率的總和。其實追根究底④這個問題是想知道矩陣運算在這裏計算的是什麼。

  綜上,條件隨機場矩陣形式的要點有:

  以2×2Mi矩陣為例(例11.2),具體表示如下:

  看一下例11.2:

  以狀態序列(1,2,1)為例:

  解析如下:

  接下來看一下求規範化因子的過程:

  上面提到的問題④,n+1個矩陣連乘后,得到的結果仍然是M×M維的。但第1行第1列的元素,正好是所有路徑上的非規範化概率之和。

  了解完概率無向圖、條件隨機場的定義和各種表示方法之後,與隱馬爾可夫模型類似的,接下來介紹條件隨機場的3個基本問題:概率計算問題、學習問題、預測問題。

條件隨機場的概率計算問題

  與隱馬爾可夫模型類似,引進前向-後向向量,遞歸的計算概率(遞歸的計算過程是非常不同的)。

  先看前向計算過程。注意CRF作為無向圖模型,拋去了HMM的方向性,我們要從矩陣乘法的角度進行分析。仔細看一下公式(11.28),轉換成矩陣語言如下(m=5為例,這裏將時刻的下標標記為t,用來區分yi的狀態和時刻tαi->αt,Mi->Mt):

  具體到轉換過程中(考慮矩陣乘法的過程):

  可以看到,α1t的得出,是結合了t-1時刻各個αi的結果。再來理解一下“αt(yi|x)表示在位置t的標記是yi並且從1t的前部分標記序列的非規範化概率”這句話,見下圖:

  實際上,t時刻α向量中的某一個分量,αi可以視作終點狀態取yi時的非規範化概率,並且這個概率是1->t時刻的整個過程中,所有可能序列的非規範化概率之和(從startstop所有路徑上的非規範化概率之和)。如圖中,α1t也就是從start=y1stop=y1過程中,所有可能序列的非規範化概率之和。因此,每個αistart=y1stop=yi的規範化因子Zi這樣就能看出與HMM特別不同了,一個應用矩陣乘法,一個應用條件概率公式。

  我理解的大概就是這個樣子,不知道能不能寫清楚,接下來看下後向算法的計算過程。從公式(11.31)開始:

  具體到轉換過程中(考慮矩陣乘法的過程):

  於是,βt(yi|x)可以表示從t時刻yt:start=y1yn+1:stop=y1,所有路徑上的非規範化概率之和(共T-t個結點狀態的序列)。前向算法也好,後向算法也好,這裏的箭頭指向僅表示乘的方向,不是有向圖結構。

  與HMM類似的,接下來看幾個概率計算。

  公式(11.32)和公式(11.33)還是好理解的,看一下示意圖:

  通過示意圖,先來看一下Z(X)Z(X)既可以通過前向向量,又可以通過後向向量來求。實際上,不管前向向量αn也好,後向向量β1也好,Z(X)的計算矩陣過程,實際上是把m個值加和,也就是得到所有狀態序列的規範化因子。

  分開看公式(11.32)分子部分,我覺得是兩個值(α、β)進行了相乘。第一個α值,代表了從0時刻start=y1t時刻stop=yi的非規範化概率;第二個β值,代表了從t時刻start=yin+1時刻stop=y1的非規範化概率。公式(11.33)是類似分析方法。

  再來看一下幾個期望的計算,就不具體分析了:

 

 

條件隨機場的學習問題

  學習問題實際上討論的是在給定訓練數據集上估計模型參數的問題。條件隨機場模型實際上定義在時序數據上的對數線性模型(是不是與LR像),學習方法包括極大似然估計等,具體的有改進的迭代尺度法IIS、梯度下降法及擬牛頓法。

  具體的方法目前先不弄了(有大概了解,但了解的程度,不足以寫出來),趕趕刷題的進度去了…等找着工作了,準備畢業的時候再把這些方法整理上~

  話說這章的代碼沒寫,因為不會…不知道從什麼地方下手。

條件隨機場的預測問題

  同HMM,大名鼎鼎的維特比算法。

PASS

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

【原創】Linux中斷子系統(三)-softirq和tasklet

背景

  • Read the fucking source code! –By 魯迅
  • A picture is worth a thousand words. –By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

中斷子系統中有一個重要的設計機制,那就是Top-half和Bottom-half,將緊急的工作放置在Top-half中來處理,而將耗時的工作放置在Bottom-half中來處理,這樣確保Top-half能儘快完成處理,那麼為什麼需要這麼設計呢?看一張圖就明白了:

  • ARM處理器在進行中斷處理時,處理器進行異常模式切換,此時會將中斷進行關閉,處理完成后再將中斷打開;
  • 如果中斷不分上下半部處理,那麼意味着只有等上一个中斷完成處理后才會打開中斷,下一个中斷才能得到響應。當某个中斷處理處理時間較長時,很有可能就會造成其他中斷丟失而無法響應,這個顯然是難以接受的,比如典型的時鐘中斷,作為系統的脈搏,它的響應就需要得到保障;
  • 中斷分成上下半部處理可以提高中斷的響應能力,在上半部處理完成后便將中斷打開(通常上半部處理越快越好),這樣就可以響應其他中斷了,等到中斷退出的時候再進行下半部的處理;
  • 中斷的Bottom-half機制,包括了softirqtaskletworkqueue、以及前文中提到過的中斷線程化處理等,其中tasklet又是基於softirq來實現的,這也是本文討論的主題;

在中斷處理過程中,離不開各種上下文的討論,了解不同上下文的區分有助於中斷處理的理解,所以,還是來一張老圖吧:

  • task_struct結構體中的thread_info.preempt_count用於記錄當前任務所處的context狀態;
  • PREEMPT_BITS:用於記錄禁止搶佔的次數,禁止搶佔一次該值就加1,使能搶佔該值就減1;
  • SOFTIRQ_BITS:用於同步處理,關掉下半部的時候加1,打開下半部的時候減1;
  • HARDIRQ_BITS:用於表示處於硬件中斷上下文中;

前戲結束了,直奔主題吧。

2. softirq

2.1 初始化

softirq不支持動態分配,Linux kernel提供了靜態分配,關鍵的結構體描述如下,可以類比硬件中斷來理解:

/* 支持的軟中斷類型,可以認為是軟中斷號, 其中從上到下優先級遞減 */
enum
{
	HI_SOFTIRQ=0,       /* 最高優先級軟中斷 */
	TIMER_SOFTIRQ,      /* Timer定時器軟中斷 */
	NET_TX_SOFTIRQ,     /* 發送網絡數據包軟中斷 */
	NET_RX_SOFTIRQ,     /* 接收網絡數據包軟中斷 */
	BLOCK_SOFTIRQ,      /* 塊設備軟中斷 */
	IRQ_POLL_SOFTIRQ,   /* 塊設備軟中斷 */
	TASKLET_SOFTIRQ,    /* tasklet軟中斷 */
	SCHED_SOFTIRQ,      /* 進程調度及負載均衡的軟中斷 */
	HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq, RCU相關的軟中斷 */

	NR_SOFTIRQS
};

/* 軟件中斷描述符,只包含一個handler函數指針 */
struct softirq_action {
	void	(*action)(struct softirq_action *);
};
/* 軟中斷描述符表,實際上就是一個全局的數組 */
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

/* CPU軟中斷狀態描述,當某個軟中斷觸發時,__softirq_pending會置位對應的bit */
typedef struct {
	unsigned int __softirq_pending;
	unsigned int ipi_irqs[NR_IPI];
} ____cacheline_aligned irq_cpustat_t;
/* 每個CPU都會維護一個狀態信息結構 */
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

/* 內核為每個CPU都創建了一個軟中斷處理內核線程 */
DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

來一張圖吧:

  • softirq_vec[]數組,類比硬件中斷描述符表irq_desc[],通過軟中斷號可以找到對應的handler進行處理,比如圖中的tasklet_action就是一個實際的handler函數;
  • 軟中斷可以在不同的CPU上并行運行,在同一個CPU上只能串行執行;
  • 每個CPU維護irq_cpustat_t狀態結構,當某個軟中斷需要進行處理時,會將該結構體中的__softirq_pending字段或上1UL << XXX_SOFTIRQ

2.2 流程分析

2.2.1 軟中斷註冊

中斷處理流程中設備驅動通過request_irq/request_threaded_irq接口來註冊中斷處理函數,而在軟中斷處理流程中,通過open_softirq接口來註冊,由於它實在是太簡單了,我忍不住想把代碼貼上來:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

也就是將軟中斷描述符表中對應描述符的handler函數指針指向對應的函數即可,以便軟中斷到來時進行回調。

那麼,問題來了,什麼時候進行軟中斷函數回調呢?

2.2.2 軟中斷執行之一:中斷處理后

先看第一種情況,用圖片來回答問題:

  • Linux中斷子系統(二)-通用框架處理文章中講述了整个中斷處理流程,在接收到中斷信號后,處理器進行異常模式切換,並跳轉到異常向量表處進行執行,關鍵的流程為:el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq
  • __handle_domain_irq函數中,irq_enterirq_exit分別用於來標識進入和離開硬件中斷上下文處理,這個從preempt_count_add/preempt_count_sub來操作HARDIRQ_OFFSET可以看出來,這也對應到了上文中的Context描述圖;
  • 在離開硬件中斷上下文後,如果!in_interrupt() && local_softirq_pending為真,則進行軟中斷處理。這個條件有兩個含義:1)!in_interrupt()表明不能處在中斷上下文中,這個範圍包括in_nmiin_irqin_softirq(Bottom-half disable)in_serving_softirq,凡是處於這幾種狀態下,軟中斷都不會被執行;2)local_softirq_pending不為0,表明有軟中斷處理請求;

軟中斷執行的入口就是invoke_softirq,繼續分析一波:

  • invoke_softirq函數中,根據中斷處理是否線程化進行分類處理,如果中斷已經進行了強制線程化處理(中斷強制線程化,需要在啟動的時候傳入參數threadirqs),那麼直接通過wakeup_softirqd喚醒內核線程來執行,否則的話則調用__do_softirq函數來處理;
  • Linux內核會為每個CPU都創建一個內核線程ksoftirqd,通過smpboot_register_percpu_thread函數來完成,其中當內核線程運行時,在滿足條件的情況下會執行run_ksoftirqd函數,如果此時有軟中斷處理請求,調用__do_softirq來進行處理;

上圖中的邏輯可以看出,最終的核心處理都放置在__do_softirq函數中完成:

  • local_softirq_pending函數用於讀取__softirq_pending字段,可以類比於設備驅動中的狀態寄存器,用於判斷是否有軟中斷處理請求;
  • 軟中斷處理時會關閉Bottom-half,處理完后再打開;
  • 軟中斷處理時,會打開本地中斷,處理完后關閉本地中斷,這個地方對應到上文中提到的Top-halfBottom-half機制,在Bottom-half處理的時候,是會將中斷打開的,因此也就能繼續響應其他中斷,這個也就意味着其他中斷也能來打斷當前的Bottom-half處理;
  • while(softirq_bit = ffs(pending)),循環讀取狀態位,直到處理完每一個軟中斷請求;
  • 跳出while循環之後,再一次判斷是否又有新的軟中斷請求到來(由於它可能被中斷打斷,也就意味着可能有新的請求到來),有新的請求到來,則有三個條件判斷,滿足的話跳轉到restart處執行,否則調用wakeup_sotfirqd來喚醒內核線程來處理:
    1. time_before(jiffies, MAX_SOFTIRQ_TIME),軟中斷處理時間小於兩毫秒;
    2. !need_resched,當前沒有進程調度的請求;
    3. max_restart = MAX_SOFTIRQ_RESTART,跳轉到restart循環的次數不大於10次;
      這三個條件的判斷,是基於延遲和公平的考慮,既要保證軟中斷儘快處理,又不能讓軟中斷處理一直佔據系統,正所謂trade-off的藝術;

__do_softirq既然可以在中斷處理過程中調用,也可以在ksoftirqd中調用,那麼softirq的執行可能有兩種context,插張圖吧:

讓我們來思考最後一個問題:硬件中斷觸發的時候是通過硬件設備的電信號,那麼軟中斷的觸發是通過什麼呢?答案是通過raise_softirq接口:

  • 可以在中斷處理過程中調用raise_softirq來進行軟中斷處理請求,處理的實際也就是上文中提到過的irq_exit退出硬件中斷上下文之後再處理;
  • raise_softirq_irqoff函數中,最終會調用到or_softirq_pending,該函數會去讀取本地CPU的irq_stat__softirq_pending字段,然後將對應的軟中斷號給置位,表明有該軟中斷的處理請求;
  • raise_softirq_irqoff函數中,會判斷當前的請求的上下文環境,如果不在中斷上下文中,就可以通過喚醒內核線程來處理,如果在中斷上下文中處理,那就不執行;
  • 多說一句,在軟中斷整個處理流程中,會經常看到in_interrupt()的條件判斷,這個可以確保軟中斷在CPU上的串行執行,避免嵌套;

2.2.3 軟中斷執行之二:Bottom-half Enable后

第二種軟中斷執行的時間點,在Bottom-half使能的時候,通常用於併發處理,進程空間上下文中進行調用:

  • 在討論併發專題的時候,我們談到過Bottom-half與進程之間能產生資源爭奪的情況,如果在軟中斷和進程之間有臨界資源(軟中斷上下文優先級高於進程上下文),那麼可以在進程上下文中調用local_bh_disable/local_bh_enable來對臨界資源保護;
  • 圖中左側的函數,都是用於打開Bottom-half的接口,可以看出是spin_lock_bh/read_lock_bh/write_lock_bh等併發處理接口的變種形式調用;
  • __local_bh_enable_ip函數中,首先判斷調用該本接口時中斷是否是關閉的,如果已經關閉了再操作BH接口就會告警;
  • preempt_count_sub需要與preempt_count_add配套使用,用於操作thread_info->preempt_count字段,加與減的值是一致的,而在__local_bh_enable_ip接口中,將cnt值的減操作分成了兩步:preempt_count_sub(cnt-1)preempt_count_dec,這麼做的原因是執行完preempt_count_sub(cnt-1)后,thread_info->preempt_count字段的值保留了1,把搶佔給關閉了,當do_softirq執行完畢后,再調用preempt_count_dec再減去剩下的1,進而打開搶佔;
  • 為什麼在使能Bottom-half時要進行軟中斷處理呢?在併發處理時,可能已經把Bottom-half進行關閉了,如果此時中斷來了后,軟中斷不會被處理,在進程上下文中打開Bottom-half時,這時候就會檢查是否有軟中斷處理請求了;

3. tasklet

從上文中分析可以看出,tasklet是軟中斷的一種類型,那麼兩者有啥區別呢?先說結論吧:

  • 軟中斷類型內核中都是靜態分配,不支持動態分配,而tasklet支持動態和靜態分配,也就是驅動程序中能比較方便的進行擴展;
  • 軟中斷可以在多個CPU上并行運行,因此需要考慮可重入問題,而tasklet會綁定在某個CPU上運行,運行完后再解綁,不要求重入問題,當然它的性能也就會下降一些;

3.1 數據結構

  • DEFINE_PER_CPU(struct tasklet_head, tasklet_vec)為每個CPU都分配了tasklet_head結構,該結構用來維護struct tasklet_struct鏈表,需要放到該CPU上運行的tasklet將會添加到該結構的鏈表中,內核中為每個CPU維護了兩個鏈表tasklet_vectasklet_vec_hi,對應兩個不同的優先級,本文以tasklet_vec為例;
  • struct tasklet_structtasklet的抽象,幾個關鍵字段如圖所示,通過next來鏈接成鏈表,通過state字段來標識不同的狀態以確保能在CPU上串行執行,func函數指針在調用task_init()接口時進行初始化,並在最終觸發軟中斷時執行;

3.2 流程分析

  • tasklet本質上是一種軟中斷,所以它的調用流程與上文中討論的軟中斷流程是一致的;
  • 調度tasklet運行的接口是tasklet_schedule,如果tasklet沒有被調度則進行調度處理,將該tasklet添加到CPU對應的鏈表中,然後調用raise_softirq_irqoff來觸發軟中斷執行;
  • 軟中斷執行的處理函數是tasklet_action,這個在softirq_init函數中通過open_softirq函數進行註冊的;
  • tasklet_action函數,首先將該CPU上tasklet_vec中的鏈表挪到臨時鏈表list中,然後再對這個list進行遍歷處理,如果滿足執行條件則調用t->func()執行,並continue跳轉遍歷下一個節點。如果不滿足執行條件,則繼續將該tasklet添加回原來的tasklet_vec中,並再次觸發軟中斷;

3.3 接口

簡單貼一下接口吧:

/* 靜態分配tasklet */
DECLARE_TASKLET(name, func, data)

/* 動態分配tasklet */
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

/* 禁止tasklet被執行,本質上是增加tasklet_struct->count值,以便在調度時不滿足執行條件 */
void tasklet_disable(struct tasklet_struct *t);

/* 使能tasklet,與tasklet_diable對應 */
void tasklet_enable(struct tasklet_struct *t);

/* 調度tasklet,通常在設備驅動的中斷函數里調用 */
void tasklet_schedule(struct tasklet_struct *t);

/* 殺死tasklet,確保不被調度和執行, 主要是設置state狀態位 */
void tasklet_kill(struct tasklet_struct *t);

收工!

歡迎關注個人公眾號,不定期分享Linux內核機制文章。

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

【其他文章推薦】

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

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

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

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

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