多進程與多線程(3)

 線程和進程是面試中最常遇見的問題。有一個問題就是線程之間哪些東西是共享的。線程共享進程的整個地址空間,共享打開的文件,建立的socket等。線程有獨立的棧以及一些寄存器,用來進行調度。堆,數據區和代碼區是共享的。

地址空間

Linux下32位系統進程地址空間有4G1G是內核地址,3G屬於自己,這3G內存由所有線程共享。這3G內存也包含了棧,那麼爲什麼說線程有自己獨立的棧呢?

看下面的程序

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <sys/types.h> 
  4. #include <unistd.h> 
  5. int global; 
  6. int *heap; 
  7. int *stack_address; //線程1將自己棧中的變量地址賦給stack_address,然後線程2訪問這個地址 
  8. void * run1(void *param) 
  9.     int stack_v = 123; 
  10.     global = 5; 
  11.     heap = malloc(12); 
  12.     heap[0] = 2; 
  13.     heap[1] = -3; 
  14.     stack_address = &stack_v; 
  15.     while(1) {} 
  16. void * run2(void *param) 
  17.     printf("global %d\n",global); 
  18.     printf("heap[1] %d\n",heap[1]); 
  19.     printf("stack_v %d\n",*stack_address); 
  20.      
  21. int main() 
  22.     pthread_t tid1,tid2; 
  23.     pthread_attr_t attr; 
  24.      
  25.     pthread_attr_init(&attr); 
  26.     pthread_create(&tid1,&attr,run1,NULL); 
  27.     pthread_create(&tid2,&attr,run2,NULL); 
  28.     pthread_join(tid1,NULL); 
  29.     pthread_join(tid2,NULL); 
  30.     return 0; 

 

運行結果:

可以看出,線程2和線程1的棧是位於同一個地址空間的,都是進程的地址空間。

線程的棧實際上是在進程的棧裏的,每個都是獨立的。

可以使用pthread_attr_getstacksize獲取線程棧的大小。

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <sys/types.h> 
  4. #include <unistd.h> 
  5. int global; 
  6. int *heap; 
  7. int *stack_address; 
  8. pthread_attr_t attr1,attr2; 
  9.  
  10. void * run1(void *param) 
  11.     int stack_v = 123; 
  12.     size_t stacksize; 
  13.     pthread_attr_getstacksize(&attr1,&stacksize); 
  14.     printf("stacksize of thread1 is %d MB\n",stacksize/(1024*1024)); 
  15.     global = 5; 
  16.     heap = malloc(12); 
  17.     heap[0] = 2; 
  18.     heap[1] = -3; 
  19.     stack_address = &stack_v; 
  20.     sleep(3); 
  21.  
  22. void * run2(void *param) 
  23.     size_t stacksize; 
  24.     printf("global %d\n",global); 
  25.     printf("heap[1] %d\n",heap[1]); 
  26.     printf("stack_v %d\n",*stack_address); 
  27.     pthread_attr_getstacksize(&attr2,&stacksize); 
  28.     printf("stacksize of thread2 is %d MB\n",stacksize/(1024*1024)); 
  29.  
  30. void fun() 
  31.     static calltime = 1; 
  32.     char dummy[1024*1024];   
  33.     printf("calltime=%d\n",calltime); 
  34.     calltime ++; 
  35.     fun(); 
  36. }    
  37.      
  38. int main() 
  39.     pthread_t tid1,tid2; 
  40.     pthread_attr_init(&attr1); 
  41.     pthread_attr_init(&attr2); 
  42.     pthread_create(&tid1,&attr1,run1,NULL); 
  43.     pthread_create(&tid2,&attr2,run2,NULL); 
  44.     pthread_join(tid1,NULL); 
  45.     pthread_join(tid2,NULL); 
  46.     fun(); 
  47.     return 0; 

運行結果:

可以看到線程1,2和主線程的棧的大小都是10M,調用了11次才崩潰是因爲有一些對齊以及段與段之間有一些gap

我當前的ulimit將棧的大小設置成了10M。可以使用pthread_attr_setstacksize在創建線程的時候設置線程的棧大小。

因此,對於線程有自己獨立的棧空間的真正理解應該是:線程的棧位於同一個地址空間裏,但是互相不重疊,線程屬性裏有它的棧的起止地址,一個線程可以讀寫其它線程的棧,只要它知道地址。

 

打開的文件(包括socket)

對於兩個進程,它們分別打開了一個文件,進程1得到的文件描述符是fd1,進程2得到的是fd2。即使進程1通過某種手段將fd1傳到進程2中,進程2也訪問不了fd1對應的文件,因爲進程間文件描述符是獨立的。fd1可能等於fd2,但是它們不對應同一個文件。

對於線程就不一樣了。線程1得到了描述符fd1,那麼線程2就可以通過fd1訪問對應的文件,即使線程1不告訴線程2,線程2自己通過某種手段知道了,它也能訪問。

每個進程有一個文件描述符空間,就類似於地址空間,線程也是共享這個文件描述符空間的。

 

信號

如果向一個進程發送信號,而這個進程有多個線程。那麼發給哪個呢?還是所有的線程都能收到?

實際上,進程維護一個統一的信號隊列,給這個進程發送一個信號,信號被存到信號隊列裏了,所有線程都可以看到。當調度到某個線程時,它發現隊列裏有信號,它拿出來處理了,這樣其它線程進入被調度時已經看不到這個信號了。

看下面這個例子:

  1. #include <stdio.h> 
  2. #include <pthread.h> 
  3. #include <sys/types.h> 
  4. #include <unistd.h> 
  5. #include <signal.h> 
  6. void sigroutine1(int no) 
  7.     printf("thread1 received a signal\n"); 
  8. void sigroutine2(int no) 
  9.     printf("thread2 received a signal\n"); 
  10. void * run1(void *param) 
  11.     while(1) 
  12.     { 
  13.         signal(SIGHUP,sigroutine1); 
  14.     }    
  15. void * run2(void *param) 
  16.      
  17.     while(1) 
  18.     { 
  19.         signal(SIGHUP,sigroutine2); 
  20.     } 
  21. int main() 
  22.     pthread_t tid1,tid2; 
  23.     pthread_attr_t attr; 
  24.     pthread_attr_init(&attr); 
  25.     pthread_attr_init(&attr); 
  26.     pthread_create(&tid1,&attr,run1,NULL); 
  27.     pthread_create(&tid2,&attr,run2,NULL); 
  28.     pthread_join(tid1,NULL); 
  29.     pthread_join(tid2,NULL); 
  30.     return 0; 

兩個線程都在等待一個信號,我們向這個進程發送信號。

每發送一個信號,只有一個線程收到了,而且不一定是哪個線程。

(上述內容僅僅是我自己實驗得到的結論,不一定正確)

 

參考文獻

https://computing.llnl.gov/tutorials/pthreads/#Abstract

http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/

http://lenky.info/2013/02/06/linux%E7%BA%BF%E7%A8%8B%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5/

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章