我有2個執行緒安全的串列(同一型別),其中每個節點都有一個互斥(我知道這不是最佳解決方案,但這是我被要求做的)。我有下面這個函式,我想匹配2個節點,每個節點來自不同的串列。其邏輯是,我從第一個串列中傳遞一個節點,然后與第二個串列中的所有節點進行檢查。
下面是節點和串列的型別:
typedef struct node_type{>
//some data;
struct node_type *next;
pthread_mutex_t鎖。
}node_t;
typedef struct list_type{>
node_t *head。
pthread_mutex_t lock。
}list_t;
這是函式的代碼:
node_t *find_match(list_t *l, node_t *n)。
{
node_t *cur,*prev;
pthread_mutex_lock(&l->lock)。
if(l->head == NULL)
{
pthread_mutex_unlock(&l->lock)。
return NULL。
}
else; }
{
cur = l->head;
while(cur != NULL)
{
pthread_mutex_lock(&cur->lock)。
if(n != NULL)
{
pthread_mutex_lock(&n->lock)。
//比較資料并檢查是否匹配。。
}
else
{
//some message or something }
return NULL。
}
if(cur == l->head)
{
pthread_mutex_unlock(&l->lock)。
}
else { pthread_mutex_unlock(&l->lock)
{
pthread_mutex_unlock(&prev->lock)。
prev = cur;
cur = cur->next;
}
pthread_mutex_unlock(&prev)。
return NULL。
}
我想把你的注意力放在if陳述句 "n != NULL "上。在那里,如果n不是NULL,那么執行緒將嘗試鎖定該節點的mutex。如果另一個執行緒擁有它并試圖洗掉該節點,會發生什么?我可以通過某種方式避免嗎?是否有更好的方法來寫這個?
uj5u.com熱心網友回復:
問題。如果另一個執行緒擁有它并試圖洗掉該節點,會發生什么?
非常簡短的問題。
非常簡短的回答。會發生什么是未定義的,可能是災難性的。" 非常簡短的回答:"會發生什么?
簡短的回答。使用一個在另一個執行緒中被洗掉的節點的mutex,會導致未定義的行為。當其他執行緒在洗掉程序中使用該突變時也是如此,并且無論它是在鎖定還是解鎖狀態下洗掉,都是獨立的"。
首先:這是個不錯的問題! 首先讓我們來看看這個問題。
首先,我們來解釋一下 "洗掉 "的含義,這樣你就可以判斷它是否適用于你的情況。第二,解釋一下為什么使用已洗掉節點的突變會導致未定義的、因而不正確的行為(而且很可能是災難),即使該突變在你的find_match(l,n)示例實作中的洗掉和匹配時都被使用。第三,我們探討了一些解決方案,盡管你可能不喜歡這些答案。
假設洗掉一個物件也會洗掉其記憶體。在C語言中,預計這將通過malloc()和free()或類似的方式發生。
因此,任何依賴于在記憶體取消分配時傳達資訊的機制都會受到未定義行為的影響,并且肯定是不正確的。
因此,依賴于可能被洗掉/分配的物件內的突變的機制也會受到未定義和不正確行為的影響。由于該行為是未定義的,因此,如果洗掉節點的一方使用了鎖定或解鎖的突變器,這并不重要。該行為將僅僅是不同的未定義,請查看 "鎖定-洗掉和魔法值 "以了解更多相關的闡述。
我并不確切知道你被 "要求做什么"。什么是固定的,什么是允許你改變的?根據這一點,解決方案可以從 "沒有 "到 "丑陋 "再到 "燦爛 "的解決方案。
你當前的實作試圖通過使用n->mutex;n的一部分來防止洗掉節點n。正如上文所解釋的,這將導致未定義的行為和可能的災難。
為了防止洗掉節點 n,你必須在節點之外使用一些超越它的東西,讓我們把它定義為 owner,owner(n)。在你的例子中,owner(n)是串列l_n,如果l_n->head是n,或者是l_n中的一個節點p,其中p->Next是p。l_n和p都有一個mutex,所以在訪問n之前鎖定該mutex是可以的。當然,洗掉 n 的函式也應該鎖定同一個 mutex。
然而,如果你不打算改變 find_match(l,n) 的函式簽名,也不打算改變節點和串列的資料結構,你就不可能知道owner(n)。畢竟,n與它的所有者沒有聯系,所有者也不是一個函式引數。
在這種情況下,解決這個問題的唯一正確或安全的方法是擁有一種全域鎖--不屬于節點或串列的一部分--來保護洗掉和n與另一個節點的n匹配。嗯。 如果你被允許將簽名改為find_match(l,n,o),其中o是所有者,這似乎是一個解決方案。然而,除非o是擁有n本身的串列,否則同樣的所有權問題會遞回地發生:你不能信任有p的節點p->next==n的mutex,除非你鎖定owner(p)的mutex。而你并沒有這樣做。這意味著你也應該把owner(p)作為一個引數。換句話說:它不能解決你的問題。
另一種方法是使用 find_match(l,n,list_that_contains_n),并且在遍歷一個串列、匹配串列中的一個專案或洗掉/添加一個元素時始終鎖定整個串列(當然,同時鎖定 l 和 list_that_contains_n)。這樣你就完全不需要節點的互斥了。這加快了遍歷、洗掉和添加函式本身的速度,但減少了對它們的并發訪問。這取決于領域和用例,是好是壞。
從list_that_contains_n->head開始為每個元素加鎖,這也是可行的,但是要正確地做到這一點,你必須鎖定list_that_contains_n中所有在n之前的元素。其原因與前面提到的遞回owner引數的原因類似。可以有一個不需要鎖定所有這些元素的解決方案,但我想它可能不怎么好讀。如果有人能給出一個可讀的、正確的、經過驗證的、高效的例子,而不鎖定n前面的所有元素:那就太好了! 如果你可以改變節點和串列的資料結構,你可以添加對所有者的反向參考,這很有趣,因為所有者既可以是串列也可以是節點。最好是將問題重構為使用完整的雙鏈接串列,并為此找到一個正確的實作。
假設一個洗掉和分配節點的執行緒,首先鎖定了該節點的mutex。 如果你在deallocation之后解鎖節點,pthread_mutex_unlock(&node->lock)會訪問被沒收的記憶體,很可能會破壞你程式的其他部分。祈禱這段記憶體不會被重用。 如果你不解鎖節點,會發生幾種情況。如果執行find_match(l,node)的執行緒剛好處于pthread_mutex_lock(&node->lock)中,如果記憶體沒有被重用,它將永遠等待。如果記憶體被重用,那么如果執行緒醒來會發生什么是無法定義的,因為該mutex很可能被其他東西覆寫了。祈禱pthread_mutex_lock(&node->lock)只是回傳一個錯誤代碼... 該記憶體仍有可能被重用,因此仍未確定會發生什么。
另一個問題是:你將在節點鎖中放入什么魔法值?mutex的實作是針對平臺的。而POSIX執行緒只定義了一個INITIALIZED的mutex的值,而不是一個應該不使用的mutex。你可以嘗試pthread_mutex_destroy(node->lock),但同樣,這對重復使用記憶體沒有任何幫助。
uj5u.com熱心網友回復: 根據pthread_mutex_lock的檔案,如果 "mutex指定的值不是指一個初始化的mutex物件",它將回傳EINVAL。因此,你可以在釋放節點之前將 然后當你使用mutex_lock時,你可以檢查它的回傳值(無論如何這是一個好的做法)。
標籤:洗掉
在使用自定義(去)分配器的情況下,或者在所有節點處理之前分配,在所有節點處理之后去分配,并且從不重復使用_的情況下,這種情況被明確地忽略了。其中,"重用 "是指在 "洗掉 "后,將一個注釋分配給node->next或list->head。在這種 "靜態 "情況下,雖然洗掉后要解鎖節點的mutex,否則pthread_mutex_lock(delete_node)中的所有執行緒將耐心等待很長時間......
為什么會出現未定義的行為?
記憶體的重新分配,例如通過呼叫free(node),必須被視為銷毀該記憶體:在銷毀之前寫入記憶體的值必須被視為丟失,而在銷毀之后從記憶體中讀取的值應被視為未定義或 "隨機"。C語言和運行時根本沒有提供任何保證,即資訊可以在記憶體的銷毀程序中傳達,例如,因為記憶體被重復使用,或者分配實作為記賬而向其寫入值。
避免出現這種情況
。
結論
正如你所看到的,你提出了一個有趣的問題。如果你能提供更多關于你被要求做什么的資訊,我想有人--也許是我--可以給你一個更精確或合適的答案!鎖定的--鏈接串列的實作。
鎖定-洗掉和神奇的值
。
但是如果我在洗掉節點之前鎖定它呢?
但是如果我在節點鎖中放置一個神奇的值呢?
在C語言中,不能保證僅僅向node->lock賦值就能被其他執行緒看到。為爭論起見,讓我們假設在你的平臺和架構上有這樣的保證。
lock欄位置空。就像這樣:n->lock = NULL。
free(n); //或任何你所說的破壞節點的意思。
