zoukankan      html  css  js  c++  java
  • linux 线程基础

    线程基础函数

    查看进程中有多少个线程,查看线程的LWP

    ps -Lf 进程ID(pid)
    

    执行结果:LWP列

    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
    

    线程共享的资源:

    注意:信号和线程最好不要一起使用。又用信号又用多线程的架构不太合理。

    • 文件描述符表
    • 信号的处理方式
    • 当前工作目录
    • 用户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
    ", getpid(), pthread_self());
    }
    
    int main(){
      pthread_t tid;
      pthread_create(&tid, NULL, thr, NULL);
      printf("in main thread %d, %lu
    ", 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
    ", 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
    ", 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
    ", 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
    ", getpid(), pthread_self());
    
      void *ret;
      pthread_join(tid, &ret);
      printf("return value:%d
    ", (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
    ", 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
    ", (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
    ", 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
    ", 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
    ");
    }
    
    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
    ", 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
    ", *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
    ", *(int*)ret);
      }
    
      for(int i = 0; i < 5; ++i){
        free(pi[i]);
      }
    }
    

    c/c++ 学习互助QQ群:877684253

    本人微信:xiaoshitou5854

  • 相关阅读:
    矩阵
    向量
    Matlab中的基本数据类型介绍
    静态代码块在何时调用
    保留上次访问网站的时间的几种方法
    用ServletContext做一个简单的聊天室
    ServletContext结合Servlet接口中的init()方法和destroy()方法的运用----网站计数器
    用ServletContext读取.properties文件
    用类加载器的5种方式读取.properties文件
    如何用cookie保存用户的登录的密码和用户名
  • 原文地址:https://www.cnblogs.com/xiaoshiwang/p/11038532.html
Copyright © 2011-2022 走看看