pthread_create執行緒建立的過程剖析
概述
在Linux環境下,pthread庫提供的pthread_create()API函式,用於建立一個執行緒。執行緒建立失敗時,它可能會返回ENOMEM或EAGAIN。這篇文章主要討論執行緒建立過程中碰到的一些問題和解決方法。
建立執行緒
首先,本文用的例項程式碼example.c:
/* example.c*/ sleep(30); } int main(int argc,char **argv) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return } |
編譯,執行下面命令:
# example.c -lpthread -o example -g |
用strace工具跟蹤執行緒建立的過程:
# strace ./example |
Strace工具輸出:
getrlimit(RLIMIT_STACK, {rlim_cur=10240*1024, rlim_max=RLIM_INFINITY}) = 0 clone(child_stack=0xb771c494, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE _PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xb771cbd8, {entry_number:6, base_addr:0xb771cb70, limit:1048575, seg_32bi t:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xb771cbd8) = 17209 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 |
由上表中的輸出可以看出建立執行緒過程中的呼叫步驟:
- 通過系統呼叫getrlimit() 獲取執行緒棧的大小(引數中的RLIMIT_STACK),在我的環境裡(CentOS6),預設值是10M。
- 呼叫mmap2()分配記憶體,大小為10489856位元組,合10244K,比棧空間大了4K。返回0xb6d1c000。
- 呼叫mprotect(),設定個記憶體頁的保護區(大小為4K),頁面起始地址為0xb6d1c000。這個頁面用於監測棧溢位,如果對這片記憶體有讀寫操作,那麼將會觸發一個SIGSEGV訊號。下面佈局圖中的紅色區域既是。
- 呼叫clone()建立執行緒。呼叫的第一個引數是一個地址:棧底的地址(這裡具體為0xb771c494)。棧空間的記憶體使用,是從高位記憶體開始的。
從/proc/<pid>/smaps檔案裡,我們可以清楚地看到棧記憶體的對映情況:
090e0000-09101000 rw-p 00000000 00:00 0 [heap] |
從上面的對映檔案的深藍色部分中,我們看到,棧的空間總共為10244Kb,記憶體段是從b6d1d000到b771e000。從strace的輸出中,我們看到棧底的地址為0xb771c494,那麼,從0xb771c494到b771e000這段記憶體是做什麼用的呢?它就是執行緒的TCB(thread's control block)和TLS區域(thread's local storage)。具體的執行緒記憶體空間佈局如下:
GLIBC2.5與2.8
研究GLIBC2.5和2.8裡的pthread_create()相關程式碼,會發現在mmap()呼叫失敗並返回ENOMEM時,作了點變動,新版裡替換了錯誤碼。
V2.5相關程式碼.../nptl/allocatestack.c:
mem = mmap (NULL, size, prot, |
V2.8裡的.../nptl/allocatestack.c:
mem = mmap (NULL, size, prot, |
如上面的程式碼片段所示,在V2.5,簡單地將mmap()呼叫結果返回給使用者,而在V2.8裡,如果mmap()返回ENOMEM,那麼GLIBC會將錯誤碼改成EAGAIN再返回。
為什麼pthread_create()會呼叫失敗?
隨著執行中的執行緒數量的增大,pthread_create()失敗的可能性也會增大。因為這會使分配給執行緒的記憶體空間(比如說執行緒棧)累積太多,導致mmap()系統呼叫失敗。
比如說,/proc/<pid>/smaps裡有這樣一個記憶體對映片段:
[...] |
可用的記憶體空間是最後一個記憶體段和[stack]標籤之間的空間:0x7F8F5000 - 0x7F33C000 = 0x5B9000 = 6000640位元組(也就是6MB)。按預設配置,小於一個執行緒棧的空間(10MB)。這時再建立執行緒就要失敗。
解決方法
通常情況下,預設10M的執行緒棧空間顯然是太大了,所以建議通過呼叫pthread_attr_setstacksize()API來改變執行緒棧的大小。比如說以下程式碼片段:
//------------------------------------------------------- |
符號版本的連結問題
回到我們前面的示例程式碼中來,在裡面,我們在主程序裡直接呼叫pthread_create()函式。我們來看一下它的連結情況:
[[email protected] yeyj]# nm example | grep pthread |
而上次在除錯Freeswitch時,發現配置的棧大小居然不生效,所有子執行緒全部繼承父執行緒的大小。這是怎麼回事呢?Freeswitch呼叫的是apr封裝後的介面,那我們看下apr的連結符號:
[[email protected] .libs]# nm libapr-1.a | grep pthread |
和前面相比,好像符號後面少了[email protected]@GLIBC_2.1或者[email protected]@GLIBC_2.0。通過GDB跟蹤,發現最終呼叫的是[email protected]@GLIBC_2.0。弄出兩個版本來了。通過第三庫呼叫pthread庫,經常會出現這種情況。
我們看2.0的程式碼,開啟檔案…//nptl/pthread_create.c:
int |
很明顯,如果連結到老版本,那麼設定棧大小的屬性完全被忽略掉了。
怎麼解決這個問題呢?強制指定連結的符號,讓它呼叫GLIBC_2.1。感謝Linux提供的系統呼叫,dlvsym()正好可以解決這個問題:
#include <dlfcn.h> |
相關推薦
pthread_create執行緒建立的過程剖析
概述 在Linux環境下,pthread庫提供的pthread_create()API函式,用於建立一個執行緒。執行緒建立失敗時,它可能會返回ENOMEM或EAGAIN。這篇文章主要討論執行緒建立過程中碰到的一些問題和解決方法。 建立執行緒 首先,本文用的例項程式碼exam
pthread執行緒建立過程(未完)
1. 當我們使用pthread_create來建立執行緒的時候, 實際上呼叫的是__pthread_create_2_1 versioned_symbol (libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1); 2. 而_
執行緒建立函式pthread_create的引數解析
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); void *(*start_routine) (void
執行緒建立 pthread_create 中自定義引數注意事項
1. 函式原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 本文主要討論最後一個引數,同時傳遞多個的問題
Python多工(2.執行緒(建立執行緒的兩種方式))
Python中threading模組 可以總結出: (1)當呼叫Thread的時候,不會建立執行緒 (2)呼叫Thread創建出來的例項物件的start方法的時候,才會建立執行緒以及讓這個執行緒開始執行  
Thread、執行緒建立、synchronized、執行緒生命週期
程序:程序指正在執行的程式,當一個程式進入記憶體執行,即變成一個程序,程序是處於執行過程中的程式,並且具有一定獨立功能。 執行緒:執行緒是程序中的一個執行單元,負責當前程序中程式的執行,一個程序中至少有一個執行緒。 jvm啟動後,必然有一個執行路徑(執行緒)從main方法開始的,一直執行到main方法結束
鎖和多執行緒:執行緒建立3種方式(一)
執行緒 鎖Synchronized 搞明白 執行緒 鎖和多執行緒系列 1.執行緒建立 執行緒建立常見的三種方式: 繼承Thread類 實現Runnable介面
Java執行緒建立的兩種方式
package test; /** * 建立執行緒 * */ public class Demo1 { public static void main(String arg[]){
一、多執行緒[建立,interrupt,setDaemon,getPriority,isAlive等]
一、多執行緒簡介 在CPU上的執行緒,執行一個任務,巨集觀當然是同時執行的,但微觀裡的確實序列執行的,CPU通過執行緒中斷,讓某一個執行緒掛起來,然後切換到另一個執行緒,執行一會兒,再切換回來。但是執行緒切換來回會犧牲一定的效能,如果增加CPU那麼執行緒是可以達到並行的。
java之執行緒建立的兩種方式,六種狀態和匿名內部類建立子類或實現類物件
一.匿名內部類建立子類或實現類物件 new Test(){} 相當於建立了Test類的子類物件 並且沒有類名 建立介面實現類 new 介面名() {};介面實現類的物件 注意 : new 後邊是類或者介面名 大括號內是類或者介面中的方法 public
Java中執行緒建立的方式:繼承thread類與實現Runnable介面
Java中執行緒的建立有兩種方式: 1. 通過繼承Thread類,重寫Thread的run()方法,將執行緒執行的邏輯放在其中 2. 通過實現Runnable介面,例項化Thread類 在實際應用中,我們經常用到多執行緒,如車站的售票系統,車站的
Java多執行緒建立的三種方式與對比
一、繼承Thread類建立執行緒類 1、定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體代表了執行緒需要完成的任務,即執行緒的執行體。 2、建立Thread子類的例項,即建立執行緒物件。 3、呼叫執行緒物件的start()方法來啟動該執行緒
在MDI子窗體中開執行緒建立新彈出窗體程式會被掛起無法繼續下去
public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String[] st = sc.nextLine().split("
Java:多執行緒 - 建立方法
多執行緒的理解 可以理解成程序中獨立執行的子任務,比如QQ.exe執行時的視訊聊天執行緒,下載檔案執行緒,傳送表情執行緒等,這些不同的任務或功能可以“同時”執行。實際上,CPU在這些執行緒之間不斷的切換,這樣做可以最大限度的利用CPU的空閒時間。 Java多執行緒的建立和使用 j
Java多執行緒-----建立執行緒的三種方式
1.繼承Thread類建立執行緒 定義Thread類的子類,並重寫該類的run()方法,該方法的方法體就是執行緒需要完成的任務,run()方法也稱為執行緒執行體 建立Thread子類的例項,也就是建立了執行緒物件 啟動執行緒,即呼叫執行緒的start()方法
java建立多執行緒&建立程序
概述 併發和並行是即相似又有區別: 並行:指兩個或多個事件在同一時刻發生; 併發:指兩個或多個事件在同一時間段內發生。 程序是指一個記憶體中執行中的應用程式。每個程序都有自己獨立的一塊記憶體空間,一個應用程式可以同時啟動多個程序。比如在Windows系統中,一個執行的abc.exe就是一個程序。 那麼我們
Java基礎加強之多執行緒篇 - 執行緒建立與終止、互斥、通訊、本地變數
執行緒建立與終止 執行緒建立 Thread類與 Runnable 介面的關係 public interface Runnable { public abstract void run(); } public class Thread implements Run
50、多執行緒建立的三種方式之實現Runnable介面
實現Runnable介面建立執行緒 使用Runnable建立執行緒步驟: package com.sutaoyu.Thread; //1.自定義一個類實現java.lang包下的Runnable介面 class MyRunnable implements Runnable{ /
執行緒和程序的關係 ----執行緒建立有幾種方式
程序是一個應用程式在處理機上的一次執行過程,他是一個動態的概念 ,執行緒是程序的一部分是程式執行的最小單元 一個程序中有多個執行緒 個人理解 :  
【JAVA】執行緒建立和匿名內部類
前言 看多執行緒時,發現一些匿名內部類的東西,然後就來總結一下。 1.繼承Thread類 在類上實現匿名內部類 public class Demo1 { public static void main(String[] args) { Thread t = new T