偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

安全函數(shù)不安全-多線程慎用List.h

系統(tǒng) Linux
linux 開發(fā)應(yīng)該多少都聽過大名鼎鼎的 list.h ,其簡潔優(yōu)雅的設(shè)計(jì),一個(gè)頭文件完成了一個(gè)高可用的鏈表。

[[440988]]

 本文轉(zhuǎn)載自微信公眾號(hào)「非典型技術(shù)宅」,作者無知少年。轉(zhuǎn)載本文請(qǐng)聯(lián)系非典型技術(shù)宅公眾號(hào)。

前言

linux 開發(fā)應(yīng)該多少都聽過大名鼎鼎的 list.h ,其簡潔優(yōu)雅的設(shè)計(jì),一個(gè)頭文件完成了一個(gè)高可用的鏈表。

但是 list.h 并不是線程安全的,在多線程的情況下使用,必須考慮多線程數(shù)據(jù)同步的問題。

然而。。。。

我在使用互斥鎖對(duì)鏈表的操作進(jìn)行保護(hù)之后,還是被坑了!

下面是把我坑了的 list_for_each_entry 和 list_for_each_entry_safe 兩個(gè)函數(shù)的詳細(xì)分析。

list.h 單線程使用

在 list.h 這個(gè)文件中有非常多值得學(xué)習(xí)的地方,比如其最經(jīng)典的 container_of 的實(shí)現(xiàn)。

在這里只介紹幾個(gè)常用的函數(shù),然后重點(diǎn)分析在多線程使用時(shí)的碰到的坑。

鏈表初始化及添加節(jié)點(diǎn)

首先定義一個(gè)鏈表和鏈表節(jié)點(diǎn),定義一個(gè)產(chǎn)品,其屬性為產(chǎn)品重量(weight)。

  1. typedef struct product_s 
  2.     struct list_head product_node; 
  3.     uint32_t index
  4.     uint32_t weight;   
  5. }product_t; 
  6.  
  7. //初始化鏈表頭 
  8. LIST_HEAD(product_list); 

生產(chǎn)者在生產(chǎn)完產(chǎn)品后,將產(chǎn)品加入鏈表,等待消費(fèi)者使用。

  1. void producer(void) 
  2.     product_t *product = malloc(sizeof(product_t)); 
  3.  
  4.     // 產(chǎn)品重量為 300 ± 10 
  5.     product->weight = 290 + rand() % 20; 
  6.      
  7.     printf("product :%p, weight %d\n", product, product->weight); 
  8.     list_add_tail(&product->product_node, &product_list); 

遍歷鏈表

使用 list_for_each_entry 可以將鏈表進(jìn)行遍歷:

  1. // 遍歷打印鏈表信息 
  2. void print_produce_list(void) 
  3.     product_t *product; 
  4.     list_for_each_entry(product, &product_list, product_node) 
  5.     { 
  6.         printf("manufacture product :%p, weight %d\n", product, product->weight); 
  7.     } 

其具體實(shí)現(xiàn)是使用宏將 for 循環(huán)的初始條件和完成條件進(jìn)行了替換:

  1. #define list_for_each_entry(pos, head, member)                \ 
  2.     for (pos = list_first_entry(head, typeof(*pos), member);    \ 
  3.          &pos->member != (head);                    \ 
  4.          pos = list_next_entry(pos, member)) 

其中for循環(huán)的第一個(gè)參數(shù)將 pos = list_first_entry(head, typeof(*pos), member); 初始化為鏈表頭指向的第一個(gè)實(shí)體鏈表成員。

第二個(gè)參數(shù) &pos->member != (head) 為跳出條件,當(dāng)pos->member再次指向鏈表頭時(shí)跳出for循環(huán)。

for的第三個(gè)參數(shù)通過pos->member.next指針遍歷整個(gè)實(shí)體鏈表,當(dāng)pos->member.next再次指向我們的鏈表頭的時(shí)候跳出for循環(huán)。

但是 list_for_each_entry 不能在遍歷的循環(huán)體中刪除節(jié)點(diǎn),因?yàn)樵谘h(huán)體中刪除鏈表節(jié)點(diǎn)后,當(dāng)前節(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)指針會(huì)被置空。

在for循環(huán)的第三個(gè)參數(shù)中,獲取下一個(gè)節(jié)點(diǎn)時(shí),會(huì)發(fā)生非法指針訪問

“安全遍歷鏈表”

為了解決在遍歷鏈表過程中,無法刪除結(jié)點(diǎn)的問題,在 list.h 中提供了一個(gè)安全刪除節(jié)點(diǎn)的函數(shù)

  1. // 刪除重量小于300的節(jié)點(diǎn) 
  2. void remove_unqualified_produce(void) 
  3.     product_t *product, *temp
  4.     list_for_each_entry_safe(product, temp, &product_list, product_node) 
  5.     { 
  6.         // 移除重量小于300的產(chǎn)品 
  7.         if (product->weight < 300) 
  8.         { 
  9.             printf("remove product :%p, weight %d\n", product, product->weight); 
  10.             list_del(&product->product_node); 
  11.             free(product); 
  12.         } 
  13.     } 

其實(shí)現(xiàn)是使用一個(gè)中間變量,在開始每次開始執(zhí)行循環(huán)體前,將當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)保存到中間變量,從而實(shí)現(xiàn)"安全"遍歷

  1. #define list_for_each_entry_safe(pos, n, head, member)            \ 
  2.     for (pos = list_first_entry(head, typeof(*pos), member),    \ 
  3.         n = list_next_entry(pos, member);            \ 
  4.          &pos->member != (head);                    \ 
  5.          pos = n, n = list_next_entry(n, member)) 

多線程中使用list.h

上面我們?cè)谥骶€程里面創(chuàng)建了產(chǎn)品,并放入到鏈表中并,并過濾了重量小于300的產(chǎn)品。

后面我們?cè)诙嗑€程中對(duì)產(chǎn)品進(jìn)行消費(fèi)(兩個(gè)線程同時(shí)消費(fèi)鏈表的數(shù)據(jù),使用完成后刪除并釋放結(jié)點(diǎn))。

這里的邏輯和單線程中的差不多,同樣是遍歷鏈表,然后從鏈表中刪除節(jié)點(diǎn)。不同的是,由于list.h自身沒有帶鎖,所以需要使用互斥鎖將鏈表的操作進(jìn)行保護(hù)。

于是很自然的有了下面的代碼

  1. void * consumer(void *arg) 
  2.     product_t *product, *temp
  3.  
  4.     // 使用互斥鎖對(duì)鏈表進(jìn)行保護(hù) 
  5.     pthread_mutex_lock(&producer_mutex); 
  6.     list_for_each_entry_safe(product, temp, &product_list, product_node) 
  7.     { 
  8.         list_del(&product->product_node); 
  9.         printf("consume product :%p, weight %d, consumer :%p\n", product, product->weight, (void *)pthread_self()); 
  10.         pthread_mutex_unlock(&producer_mutex); 
  11.  
  12.         // 睡一會(huì),防止太快了 
  13.         usleep(10*1000); 
  14.         free(product); 
  15.         pthread_mutex_lock(&producer_mutex); 
  16.     } 
  17.     pthread_mutex_unlock(&producer_mutex); 
  18.      
  19.     return NULL

在上面的這段代碼中,在對(duì)鏈表操作時(shí),使用互斥鎖對(duì)鏈表進(jìn)行了保護(hù),使同時(shí)只能有一個(gè)線程訪問鏈表。

不過你以為這樣就好了嘛,如果時(shí)這樣,這篇文章就沒存在的必要了。。。

[[440989]]

在兩個(gè)線程同時(shí)遍歷時(shí),即便是加了鎖之后,數(shù)據(jù)訪問也不安全。

在遍歷使用的 list_for_each_entry_safe 宏中,使用了一個(gè)零時(shí)變量對(duì)保存著當(dāng)前鏈表的下一個(gè)節(jié)點(diǎn)。

但是多線程訪問鏈表時(shí),有可能零時(shí)變量保存的節(jié)點(diǎn),被另一個(gè)線程刪除了,所以訪問的時(shí)候又是 Segmentation fault

[[440990]]

后記

原因找到了,也就好辦了。以至于解決方法嘛,我是使用一個(gè)全局的零時(shí)變量,將需要?jiǎng)h除節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)保存起來,手動(dòng)實(shí)現(xiàn)多線程的"安全刪除"。

  1. // 消費(fèi)者 
  2. void * consumer(void *arg) 
  3.     product_t *product; 
  4.  
  5.     // 使用互斥鎖對(duì)鏈表進(jìn)行保護(hù) 
  6.     pthread_mutex_lock(&producer_mutex); 
  7.     list_for_each_entry(product, &product_list, product_node) 
  8.     { 
  9.         temp = list_next_entry(product, product_node); 
  10.         list_del(&product->product_node); 
  11.         printf("consume product :%p, weight %d, consumer :%p\n", product, product->weight, (void *)pthread_self()); 
  12.         pthread_mutex_unlock(&producer_mutex); 
  13.  
  14.         // 睡一會(huì),防止太快 
  15.         usleep(10*1000); 
  16.         free(product); 
  17.  
  18.         pthread_mutex_lock(&producer_mutex); 
  19.         if(temp != NULL){ 
  20.             product = list_prev_entry(temp, product_node); 
  21.         } 
  22.     } 
  23.     pthread_mutex_unlock(&producer_mutex); 
  24.  
  25.     return NULL

一個(gè)晚上找到了這個(gè)bug,然后又花了一個(gè)晚上記錄下來這個(gè)bug。

據(jù)說 klist.h 是 list.h 的線程安全版本,后面花時(shí)間在研究一下去,今天就先睡了。。。

 

責(zé)任編輯:武曉燕 來源: 非典型技術(shù)宅
相關(guān)推薦

2012-04-16 10:12:54

Java線程

2020-04-22 20:35:02

HashMap線程安全

2024-01-19 08:42:45

Java線程字符串

2015-07-01 14:48:51

2014-04-09 09:37:29

2024-03-22 12:29:03

HashMap線程

2014-09-12 17:44:23

2023-06-01 19:24:16

2020-11-03 12:32:25

影子物聯(lián)網(wǎng)物聯(lián)網(wǎng)IOT

2021-04-04 23:16:52

安全刷臉銀行

2009-08-03 16:58:59

C#不安全代碼

2015-05-27 16:13:05

2017-02-16 08:50:00

2021-05-17 07:51:44

SimpleDateF線程安全

2009-11-12 08:38:34

2018-01-26 10:49:19

2009-11-18 10:05:13

2010-08-16 10:01:01

2021-12-08 07:31:40

Linux安全病毒

2023-09-18 08:01:06

Spring管理Mybatis
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)