linux 線程基礎

線程基礎函數

查看進程中有多少個線程,查看線程的LWPlinux

ps -Lf 進程ID(pid)

執行結果:LWP列shell

y:~$ ps -Lf 1887
UID        PID  PPID   LWP  C NLWP STIME TTY      STAT   TIME CMD
ys        1887  1341  1887  0    3 14:57 tty2     Sl     0:00 /usr/lib/ibus/ibus
ys        1887  1341  1889  0    3 14:57 tty2     Sl     0:00 /usr/lib/ibus/ibus
ys        1887  1341  1890  0    3 14:57 tty2     Sl     0:00 /usr/lib/ibus/ibus

線程共享的資源:

注意:信號和線程最好不要一塊兒使用。又用信號又用多線程的架構不太合理。session

  • 文件描述符表
  • 信號的處理方式
  • 當前工做目錄
  • 用戶ID和組ID
  • 內存地址空間(.text/.data/.bss/heap/共享庫,棧區(stack)不共享)

非線程共享的資源:

  • 線程ID多線程

  • 處理器現場和棧指針(內核棧)架構

  • 獨立的棧空間(用戶空間棧)併發

  • errno變量函數

    • 因此不能用函數perrno打印錯誤信息了。性能

    • 使用strerror函數打印錯誤信息線程

      #include <string.h>
      char *strerror(int errnum);
      • errnum:errno
  • 阻塞信號集合指針

  • 調度優先級

現場優,缺點

  • 優勢:1,提升程序併發性。2,開銷小。3,數據通訊,共享方便
  • 缺點:1,由於使用的時庫函數,因此不太穩定。2,調試,編寫困難。3,對信號支持很差。

進程和線程都是經過系統函數clone建立的。

每一個線程有本身獨立的PCB

pcb:進程控制塊結構體:/usr/src/linux-headers-4.15.0-29/include/linux/sched.h

  • 進程id:系統中每一個進程有惟一的id,在c語言中用pid_t類型表示,是個非負整數。

  • 進程狀態:就緒,運行,掛起,中止等狀態

  • 描述虛擬地址空間的信息

  • 描述控制終端的信息

  • 進程執行時的當前工做目錄(current working directory)

  • umask掩碼

  • 文件描述符表,包含不少指向file結構體的指針

  • 和信號相關的信息

  • 用戶id和組id

  • 會話(session)和進程組

  • 進程可使用的資源上限(Resource Limit)

    用【ulimit -a】查看:

    ys@ys:~$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 7743
    max locked memory       (kbytes, -l) 16384
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 7743
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited

pthread_create函數:建立一個線程,並開始執行這個線程

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
  • thread:線程的ID
  • attr:線程的屬性,通常不使用
  • start_routine:函數指針
  • arg:start_routine函數的參數
  • 返回值:成功返回0;失敗返回errno

編譯的時候要加【-lpthread】

pthread_self函數:獲得線程的id

#include <pthread.h>
pthread_t pthread_self(void);

例子:獲得主線程的ID和子線程的ID。注意要sleep1秒,不睡的話,主線程就先結束了,因此子線程裏的打印打不出來。

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

void * thr(void* args){
  printf("in thread %d, %lu\n", getpid(), pthread_self());
}

int main(){
  pthread_t tid;
  pthread_create(&tid, NULL, thr, NULL);
  printf("in main thread %d, %lu\n", getpid(), pthread_self());
  sleep(1);
  return 0;
}

pthread_exit函數:終止線程

#include <pthread.h>
void pthread_exit(void *retval);
  • retval:線程的返回值

改進上面的例子,用pthread_exit代替sleep

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

void * thr(void* args){
  printf("in thread %d, %lu\n", getpid(), pthread_self());
  //exit(1);//不要在子線程裏調用此函數,調用的結果是把整個進程終止了。
  //return NULL;//可使用
  //pthread_exit(NULL);//可使用
}

int main(){
  pthread_t tid;
  pthread_create(&tid, NULL, thr, NULL);
  printf("in main thread %d, %lu\n", getpid(), pthread_self());
  //sleep(1);
  pthread_exit(NULL);
  return 0;
}

終止線程時的注意事項

  • 要使用pthread_exit函數,不能使用exit函數
  • 也可使用return,可是在主線程裏使用return的話,就把進程終止了。
  • exit是推出進程,因此不要在線程函數裏調用exit函數

pthread_join函數:阻塞等待回收線程資源,其實也是等待線程結束,因此是阻塞的函數,線程不執行完,pthread_join就一直處於阻塞狀態,相似wait函數(回收子進程的函數,也是阻塞的)。而且它的第二個參數是能夠接收線程的返回值的。

線程不回收也會變成殭屍線程,線程裏也有PCB資源,也要回收。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
  • thread:函數pthread_create調用後,傳出的第一個參數的值,也就是線程的ID
  • retval:二級指針,線程推出時,返回的信息。
  • 返回值:成功返回0;失敗返回errno

例子:實驗pthread_join是不是阻塞。

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

void * thr(void* args){
  printf("in thread %d, %lu\n", getpid(), pthread_self());
  sleep(1);
  return (void*)200;
}

int main(){
  pthread_t tid;
  pthread_create(&tid, NULL, thr, NULL);
  printf("in main thread %d, %lu\n", getpid(), pthread_self());

  void *ret;
  pthread_join(tid, &ret);
  printf("return value:%d\n", (int)ret);

  return 0;
}

結果分析,發如今子線程睡的1秒內,pthread_join是阻塞的,線程的返回值200,也能夠打印出來,可是編譯有警告。

用pthread_exit函數也能夠設置線程的返回值和return的效果如出一轍。

pthread_exit((void*)11);
return (void*)11;

pthread_cancel函數:終止線程,並把線程的返回值設置爲-1(是個宏)

#include <pthread.h>
int pthread_cancel(pthread_t thread);
  • thread:線程ID
  • 返回值:成功返回0;失敗返回errno

例子:驗證被函數pthread_cancel終止的進程的返回值。

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>


void * thr(void* args){
  while(1){
    printf("in thread %d, %lu\n", getpid(), pthread_self());
    sleep(1);
  }

  pthread_exit((void*)101);
  //return (void*)200;
}

int main(){
  pthread_t tid;
  pthread_create(&tid, NULL, thr, NULL);

  sleep(5);
  pthread_cancel(tid);

  void *ret;
  pthread_join(tid, &ret);
  printf("return value:%d\n", (int)ret);

  return 0;
}

注意:pthread_cancel可以執行成功的前提是要終止的線程裏必須有Cancellation points。也就是說線程裏不能光是一個空的死循環,循環裏至少要有一行代碼,不然pthread_cancel不能執行成功。

pthread_detach函數:從父線程中分離出線程,也就是說這個子線程不受父線程的控制了,父線程再也不須要回收這個子線程的資源了,也就是不能夠調用pthread_join函數去回收它所佔的資源了。

#include <pthread.h>
int pthread_detach(pthread_t thread);
  • thread:線程ID
  • 返回值:成功返回0;失敗返回errno

例子:驗證分離後,不能夠調用pthread_join函數。

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>


void * thr(void* args){
  while(1){
    printf("in thread %d, %lu\n", getpid(), pthread_self());
    sleep(1);
  }

  pthread_exit((void*)101);
  //return (void*)200;
}

int main(){
  pthread_t tid;
  pthread_create(&tid, NULL, thr, NULL);

  sleep(5);
  pthread_detach(tid);

  int ret = pthread_join(tid, NULL);
  if(ret != 0){
    printf("return value:%d, %s\n", ret, strerror(ret));
  }
  
  return 0;
}

結果分析:打印出下面的,errno是22.

return value:22, Invalid argument

查看線程庫函數的版本:NPTL 2.27(NPTL:Native POSIX Thread Library)

getconf GNU_LIBPTHREAD_VERSION
NPTL 2.27

線程建立多少個性能好?

CPU核數*2 + 2

pthread_equal函數:判斷2個線程ID是否相同

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

雖然線程ID的類型是pthread_t,也就是無符號長整形,可是也不推薦用==去判斷,由於Linux後續的版本有可能把pthread_t類型變爲構造。

注意:在同一個進程內,線程ID是惟一的。可是在不一樣的進程裏,能夠參數相同的線程ID。這點和進程的ID不一樣,進程ID確定是惟一的。

建立線程時,pthread_create函數的第二參數,能夠設置線程的屬性。

設置線程屬性的步驟:

1,調用pthread_attr_init函數

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);

2,設置屬性。設置屬性的函數以下:

pthread_attr_getaffinity_np   pthread_attr_setaffinity_np
pthread_attr_getdetachstate   pthread_attr_setdetachstate
pthread_attr_getguardsize     pthread_attr_setguardsize
pthread_attr_getinheritsched  pthread_attr_setinheritsched
pthread_attr_getschedparam    pthread_attr_setschedparam
pthread_attr_getschedpolicy   pthread_attr_setschedpolicy
pthread_attr_getscope         pthread_attr_setscope
pthread_attr_getstack         pthread_attr_setstack
pthread_attr_getstackaddr     pthread_attr_setstackaddr
pthread_attr_getstacksize     pthread_attr_setstacksize

3,調用pthread_attr_destroy函數

#include <pthread.h>
int pthread_attr_destroy(pthread_attr_t *attr);

舉例:先指定線程的屬性是detatch,而後再建立線程。建立線程後就不用再調用pthread_detach函數了。這麼作的好處是,若是線程的執行時間特別短,還沒調用pthread_detach函數,線程就結束了的狀況,程序也能夠正常回收線程的資源。

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * thr(void* arg){
  printf("thread\n");
}

int main(){

  pthread_attr_t attr;
  pthread_attr_init(&attr);

  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  pthread_t tid;
  pthread_create(&tid, &attr, thr, NULL);

  int ret = pthread_join(tid, NULL);
  if(ret > 0){
    printf("ret:%d, %s\n", ret, strerror(ret));    
  }
  
  pthread_attr_destroy(&attr);
}

線程傳遞參數的例子

用malloc開闢內存空間,記得釋放。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

void * thr(void* arg){
  int* pi = arg;
  printf("%d thread\n", *pi);
  *pi = 100 + *pi;
  return pi;
}

int main(){

  pthread_t tid[5];
  int* pi[5];
  for(int j = 0; j < 5; ++j){
    pi[j] = (int*)malloc(sizeof(int));
  }
  int i = 0;
  for(; i < 5; ++i){
    *pi[i] = i;
    pthread_create(&tid[i], NULL, thr, pi[i]);
  }

  void* ret;
  for(i = 0; i < 5; ++i){
    pthread_join(tid[i], &ret);
    printf("ret:%d\n", *(int*)ret);
  }

  for(int i = 0; i < 5; ++i){
    free(pi[i]);
  }
}
相關文章
相關標籤/搜索