成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

安全函數不安全-多線程慎用List.h

系統 Linux
linux 開發應該多少都聽過大名鼎鼎的 list.h ,其簡潔優雅的設計,一個頭文件完成了一個高可用的鏈表。

[[440988]]

 本文轉載自微信公眾號「非典型技術宅」,作者無知少年。轉載本文請聯系非典型技術宅公眾號。

前言

linux 開發應該多少都聽過大名鼎鼎的 list.h ,其簡潔優雅的設計,一個頭文件完成了一個高可用的鏈表。

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

然而。。。。

我在使用互斥鎖對鏈表的操作進行保護之后,還是被坑了!

下面是把我坑了的 list_for_each_entry 和 list_for_each_entry_safe 兩個函數的詳細分析。

list.h 單線程使用

在 list.h 這個文件中有非常多值得學習的地方,比如其最經典的 container_of 的實現。

在這里只介紹幾個常用的函數,然后重點分析在多線程使用時的碰到的坑。

鏈表初始化及添加節點

首先定義一個鏈表和鏈表節點,定義一個產品,其屬性為產品重量(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); 

生產者在生產完產品后,將產品加入鏈表,等待消費者使用。

  1. void producer(void) 
  2.     product_t *product = malloc(sizeof(product_t)); 
  3.  
  4.     // 產品重量為 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 可以將鏈表進行遍歷:

  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.     } 

其具體實現是使用宏將 for 循環的初始條件和完成條件進行了替換:

  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循環的第一個參數將 pos = list_first_entry(head, typeof(*pos), member); 初始化為鏈表頭指向的第一個實體鏈表成員。

第二個參數 &pos->member != (head) 為跳出條件,當pos->member再次指向鏈表頭時跳出for循環。

for的第三個參數通過pos->member.next指針遍歷整個實體鏈表,當pos->member.next再次指向我們的鏈表頭的時候跳出for循環。

但是 list_for_each_entry 不能在遍歷的循環體中刪除節點,因為在循環體中刪除鏈表節點后,當前節點的前驅結點和后繼結點指針會被置空。

在for循環的第三個參數中,獲取下一個節點時,會發生非法指針訪問

“安全遍歷鏈表”

為了解決在遍歷鏈表過程中,無法刪除結點的問題,在 list.h 中提供了一個安全刪除節點的函數

  1. // 刪除重量小于300的節點 
  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的產品 
  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.     } 

其實現是使用一個中間變量,在開始每次開始執行循環體前,將當前節點的下一個節點保存到中間變量,從而實現"安全"遍歷

  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

上面我們在主線程里面創建了產品,并放入到鏈表中并,并過濾了重量小于300的產品。

后面我們在多線程中對產品進行消費(兩個線程同時消費鏈表的數據,使用完成后刪除并釋放結點)。

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

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

  1. void * consumer(void *arg) 
  2.     product_t *product, *temp
  3.  
  4.     // 使用互斥鎖對鏈表進行保護 
  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.         // 睡一會,防止太快了 
  13.         usleep(10*1000); 
  14.         free(product); 
  15.         pthread_mutex_lock(&producer_mutex); 
  16.     } 
  17.     pthread_mutex_unlock(&producer_mutex); 
  18.      
  19.     return NULL

在上面的這段代碼中,在對鏈表操作時,使用互斥鎖對鏈表進行了保護,使同時只能有一個線程訪問鏈表。

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

[[440989]]

在兩個線程同時遍歷時,即便是加了鎖之后,數據訪問也不安全。

在遍歷使用的 list_for_each_entry_safe 宏中,使用了一個零時變量對保存著當前鏈表的下一個節點。

但是多線程訪問鏈表時,有可能零時變量保存的節點,被另一個線程刪除了,所以訪問的時候又是 Segmentation fault

[[440990]]

后記

原因找到了,也就好辦了。以至于解決方法嘛,我是使用一個全局的零時變量,將需要刪除節點的下一個節點保存起來,手動實現多線程的"安全刪除"。

  1. // 消費者 
  2. void * consumer(void *arg) 
  3.     product_t *product; 
  4.  
  5.     // 使用互斥鎖對鏈表進行保護 
  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.         // 睡一會,防止太快 
  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

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

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

 

責任編輯:武曉燕 來源: 非典型技術宅
相關推薦

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

影子物聯網物聯網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
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩一区二区三区在线观看 | 免费精品| 成人久久久久 | 找个黄色片 | 在线毛片网 | 成人av电影免费在线观看 | 一区二区三区在线免费观看视频 | 视频一区二区中文字幕 | 日本精品视频在线观看 | 日韩高清黄色 | 日韩精品影院 | www.国产精| 欧美日韩在线一区二区三区 | 黄色电影在线免费观看 | 99热国产在线播放 | 91久久久久久久 | 日本人爽p大片免费看 | 国产成人高清在线观看 | 国产特黄一级 | 成人av免费在线观看 | 日本免费黄色一级片 | 国产精品视屏 | 一级毛片观看 | 国产日韩欧美中文 | 日本一道本视频 | 美日韩一区二区 | 91精品国模一区二区三区 | 亚洲国产精品久久久 | 亚洲一二三在线观看 | 亚洲精品乱码久久久久久黑人 | 中文字幕综合 | 国产激情一区二区三区 | 国产精品亚洲第一区在线暖暖韩国 | av色站| 中文字幕高清av | 成人九区| 国产日韩欧美一区二区 | 精品一区二区电影 | 二区在线视频 | 国产精品国产三级国产播12软件 | 国产福利资源在线 |