2012-02-02 90 views
15

我曾經認爲所有可重入函數都是線程安全的。不過,我看到Reentrancy page in Wiki,其職位代碼爲「完全可重入的,但不是線程安全的。因爲它並不能保證在執行過程中,全球的數據是一致的狀態」爲什麼這段代碼是可重入的,但不是線程安全的

int t; 

void swap(int *x, int *y) 
{ 
     int s; 

     s = t; // save global variable 
     t = *x; 
     *x = *y; 
     // hardware interrupt might invoke isr() here! 
     *y = t; 
     t = s; // restore global variable 
} 

void isr() 
{ 
     int x = 1, y = 2; 
     swap(&x, &y); 
} 

我不明白它的解釋。爲什麼這個函數不是線程安全的?是否因爲線程執行期間全局變量int t將被更改?

+0

這個例子不是有點人爲的。但是,重入和線程安全是正交的概念。 – 2012-02-02 17:24:13

+1

Posix有重入的另一個定義「在POSIX.1c中,一個」可重入函數「被定義爲一個函數,當兩個或多個線程調用時,它的作用保證就好像每個線程都一個接一個地執行函數以一個未定義的順序,即使實際的執行是交錯的「,這在維基百科上的那個(imo。相當不好)示例將不符合 – nos 2012-02-02 18:20:43

+0

在我看來,這個例子不是可重入的:中斷的'swap()'會不按預期交換'x'和'y'指向的值(無論'* x'的初始值如何,'* y'可能設置爲2)。 – rom1v 2018-01-07 00:03:47

回答

4

爲了給出更通用的答案,重入僅限於功能級別。這意味着一次函數調用不會改變可以改變第二次調用功能的狀態。

在給出的例子中,全局變量在函數的兩次調用之間沒有改變。函數內部發生的情況對函數的每次調用都沒有影響。

的非重入函數的一個例子是strtok

它例如不能嵌套2解析環路與它:

/* To read a several lines of comma separated numbers */ 
char buff[WHATEVER], *p1, *p2; 

    p1 = strtok(buff, "\n"); 
    while(p1) { 
    p2 = strtok(p1, ","); 
    while(p2) { 
     atoi(p2); 
     p2 = strtok(NULL, ","); 
     } 
    } 
    p1 = strtok(NULL, "\n"); 
    } 

這是不行的,因爲外部的狀態strtok循環被第二次調用破壞(一個必須使用可重入變體strtok_r)。

+3

問題是要求解釋可重入函數不是線程安全的,這並沒有回答這個問題,它只是提供了一個相反的例子。 – Jed 2012-09-21 20:12:30

0

因此,由於一些奇怪的原因而使用名爲t的全局變量來函數混淆。如果同時從兩個不同的線程調用此函數,則可能會得到意外的錯誤結果,因爲一個實例會覆蓋另一個實例寫入的t中的值。

1

如果你有2個實例(每個在不同的線程)執行它,人們可以踩對方的腳趾:如果一個人得到了在「硬件中斷」的評論打斷,另一執行,它可能會改變T,使切換回第一個會產生不正確的結果。

4

與這種類型的可重入的訣竅在於,在執行所述第二呼叫的第一呼叫的執行停止。就像一個子功能調用。第二次通話完成後,第一個通話繼續。由於該函數在入口時保存了t的狀態,並在退出時將其恢復,所以在第一次調用繼續時沒有任何變化。因此,無論第一次呼叫的中斷在哪裏,您都始終有一個明確而嚴格的執行順序。

當此功能在多線程運行時,所有執行並行完成,即使在多核CPU真正的並行。沒有在所有線程上執行定義的執行順序,只在單個線程內執行。所以t的值可以隨時由其他線程中的一個來改變。

+0

所以重入不包含並行嗎?例如,foo(){a(); b()},當重入發生時,它只能是(ab)b,但可以' t是多線程可以的一個b b? – 2012-02-03 02:20:35

+0

基本上是的。這裏給出的重入總是(a2 b2),因爲內部調用總是在外部調用繼續之前完全執行。在多線程中,它取決於線程計劃,也可能是a1 a2 b1 b2。使函數重入並不意味着它是線程安全的。另一種方式:使其線程安全並不意味着它是可重入的。兩者都必須單獨處理,總是存在問題以及如何在調用之間以及線程之間共享狀態(即變量)。 – Secure 2012-02-03 07:55:46

+0

只是爲了說明。對我而言,真的很難想象*「只能從同一線程重新進入」情況。因爲我害怕heisenbug,所以我訓練自己暗示並行執行線程的東西。我只從線程相關的文本中看到術語*重入*。我認爲在搶先式多線程變得普遍後開始編程的人也是如此。但具有諷刺意味的是,這是理解這個術語的關鍵。 – Eonil 2014-01-23 19:29:01

2

我要去嘗試提供的函數,而折返的另一(可能不太做作)的例子,但不是線程安全的。

這裏是「河內塔」的實現,使用共享全球的「臨時」堆:

stack_t tmp; 

void hanoi_inner(stack_t src, stack_t dest, stack_t tmp, int n) 
{ 
    if (n == 1) move(src, dest) 
    else { 
    hanoi_inner(src, tmp, dest, n - 1); 
    move(src, dest); 
    hanoi_inner(tmp, dest, src, n - 1); 
    } 
} 

void hanoi(stack_t src, stack_t dest, int n) { hanoi_inner(src, dest, tmp, n); } 

功能hanoi()是折返的,因爲它留下了全球緩衝區的狀態tmp不變時,退貨(一個警告:tmp上盤片尺寸不斷增加的常見限制可能會在可重入呼叫期間被違反。)但是hanoi()不是線程安全的。

下面是這既是線程安全和可重入,如果遞增運算n++是原子的一個例子:

int buf[MAX_SIZE]; /* global, shared buffer structure */ 
int n;    /* global, shared counter */ 

int* alloc_int() { return &buf[n++]; } 

你真的可以使用它作爲一個整數單元的分配器(不檢查溢出;我知道)。如果n++不是原子操作,則兩個線程或兩個可重入調用可能很容易被分配到同一個單元。

+1

如果n ++是原子的(這通常不是),這個只是可重入的。否則,這兩個調用可能最終返回相同的指針。 – 2012-02-02 23:10:29

+0

@per謝謝 - 我將嘗試將其更改爲可重入而不訴諸於鎖定或原子n ++ – gcbenison 2012-02-02 23:46:15

3

假定線程A以及線程B.線程A具有兩個局部變量A = 5,B = 10和線程B具有兩個局部變量p值= 20,Q = 30。

線程A調用:交換(& a,& b);線程B調用:swap(& p,& q);

我假設這兩個線程都在不同的內核上運行,並且屬於同一個進程。 變量t是全局變量,int x,int y是給定函數的局部變量。下面的線程調度顯示了't'的值可以根據線程的調度而變化,因此使得代碼線程不安全。說全球t = 100;

Thread A   Thread B 
1) int s;  int s; 
2) s = 100;  s = 100; 
3) t = 5;  no operation(nop); 
4) nop;   t = 20; // t is global so Thread A also sees the value as t = 20 
5) x = 10;  x = 30; 
6) y = 20;  y = 20; // Thread A exchange is wrong, Thread B exchange is OK 

現在試着想象如果語句3和4的順序不同,會發生什麼情況。 t然後會得到值5,並且線程B中的交換將是錯誤的。如果兩個線程位於同一處理器上,情況會更加容易。那麼以上操作都不是同時發生的。我剛剛在步驟3和4中展示了交錯,因爲這些是最重要的。