zoukankan      html  css  js  c++  java
  • 线程控制原语

    线程是进程中最小的执行单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

    线程,其实也是轻量级的进程。一个进程,即使我们没有主动创建线程,也会有一个默认的主线程(即进程本身)。线程只用复杂代码如何执行,而进程还需要管理内存和文件系统等。线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。一个进程内的线程通信比进程之间的通信更快速和高效。

    进程相当于一个项目,而线程就是为了完成项目需求而建立的一个个开发任务。默认情况下,可以建立一个大的任务,就是完成某某功能,然后交给一个人从头做到尾,这就是主线程。但有时候,你发现任务是可以拆解的,如果没有非常大的前后关联关系,就可以并发执行。例如实现一个浏览器,多个页面标签可以是多个进程,但是一个页面中可能又包含多个进程。

    察看LWP(light-weight process,轻量级进程)号(线程号):

    线程间的共享资源
    1.文件描述符表
    2.每种信号的处理方式
    3.当前工作目录
    4.用户ID和组ID
    5.内存地址空间

    线程间的非共享资源
    1.线程id
    2.处理器现场和栈指针(内核栈)
    3.独立的栈空间(用户空间栈)
    4.errno变量
    5.信号屏蔽字
    6.调度优先级

    线程的优点:

    提高程序的并发性
    开销小,不用重新分配内存
    通信和共享数据方便

    线程的缺点:

    线程不稳定(库函数实现,所以在编译时需要链接线程库即 -lpthread)
    线程调试比较困难(gdb支持不好)
    线程无法使用unix经典事件,例如信号

    查看manpage关于pthread的函数:man  -k pthread

    创建线程:

    #include <pthread.h>
        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        void *(*start_routine) (void *), void *arg);

    pthread_t *thread:传递一个pthread_t变量地址进来,用于保存新线程的tid(线程ID)
    const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL
    void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块
    void *arg:指定线程将要加载调用的那个函数的参数返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。

      Compile and link with -lpthread.
           typedef unsigned long int pthread_t;

    在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值。

    pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid()可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self()可以获得当前线程的id。

    获取调用线程tid:

    pthread_t  pthread_self(void);

    退出线程pthread_exit:

    void pthread_exit(void *retval);
    void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。

    调用线程退出函数,注意和exit函数的区别,任何线程里调用exit函数都会导致进程退出。其他线程未工作结束,主控线程退出时不能return或exit。如果主线程创建了线程之后,立即使用return返回了,此时子线程可能无法执行,但是主线程如果使用pthread_exit退出,子线程则可以执行。pthread_exit是退出当前线程,不会影响其他线程的运行。

    需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者静态的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

    回收线程:

    int  pthread_join(pthread_t thread, void **retval);
           pthread_t  thread:回收线程的tid
           void **retval:接收退出线程传递出的返回值
           返回值:成功返回0,失败返回错误号

    调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
    如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

    在进程内某个线程可以取消另一个线程。

    int pthread_cancel(pthread_t thread);

    被取消的线程,退出值,定义在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:

    当创建多个线程时,哪一个线程最先执行依赖于具体的系统实现。由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信
    息,可以先用strerror()把错误码转换成错误信息再打印。
    如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,从main函数return也相当于调用exit。

    eg:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <pthread.h>
     4 #include <unistd.h>
     5 #include <string.h>
     6 struct STU{
     7     int age;
     8     char name[20];
     9     };
    10 
    11 void sys_err(int err, void * exitno)
    12 {
    13     fprintf(stderr,"can't creat thread: %s
    ",strerror(err));
    14     pthread_exit(exitno);
    15 }
    16 void *thr_fn1(void *arg)
    17 {
    18     pthread_t tid;
    19     tid=pthread_self();
    20     printf("thread 1 . ID = %ld =0x%X
    ",(unsigned long)tid,(unsigned int)tid);
    21     return (void *)1;
    22 }
    23 void *thr_fn2(void *arg)
    24 {
    25     
    26     pthread_t tid;
    27     tid=pthread_self();
    28     printf("thread 2 . ID = %ld =0x%X
    ",(unsigned long)tid,(unsigned int)tid);
    29     ((struct STU *)arg)->age=100;
    30     sprintf(((struct STU *)arg)->name,"%s","Lihua");
    31     pthread_exit((void *)2);
    32 }
    33 void *thr_fn3(void *arg)
    34 {
    35     pthread_t tid;
    36     tid=pthread_self();
    37     while(1)
    38     {
    39         printf("thread 3 . ID = %ld =0x%X
    ",(unsigned long)tid,(unsigned int)tid);
    40         sleep(1);
    41     }
    42     return (void *)3;
    43 }
    44 
    45 int main(int argc,char *argv[])
    46 {
    47     pthread_t tid,tid1,tid2,tid3;
    48     void *tret1,*tret2,*tret3;
    49     int err;
    50 
    51     struct STU test;
    52     
    53     tid=pthread_self();
    54     printf("main thread = %ld =0x%X
    ",(unsigned long)tid,(unsigned int)tid);
    55     err=pthread_create(&tid1,NULL,thr_fn1,NULL);
    56     if(err!=0)
    57     {
    58         sys_err(err,(void *)-1);
    59     }
    60     err=pthread_create(&tid2,NULL,thr_fn2,&test);
    61     if(err!=0)
    62     {
    63         sys_err(err,(void *)-2);
    64     }
    65     
    66     err=pthread_create(&tid3,NULL,thr_fn3,NULL);
    67     if(err!=0)
    68     {
    69         sys_err(err,(void *)-3);
    70     }
    71 
    72     pthread_join(tid1,&tret1);//阻塞等待线程1结束
    73     printf("thread 1 exit code is %ld
    ",(long)tret1);
    74 
    75     pthread_join(tid2,&tret2);
    76     printf("thread 2 exit code is %ld
    ",(long)tret2);
    77 
    78     printf("arg->age=%d, arg->name=%s
    ",test.age,test.name);
    79     
    80     sleep(3);
    81     
    82     pthread_cancel(tid3);
    83     pthread_join(tid3,&tret3);
    84     printf("thread 3 exit code is %ld
    ",(long)tret3);//注意,调用cancel之后,此时线程的退出值不是3而是-1
    85     
    86     pthread_exit((void *)0);//return 0;
    87 }

    判断两个线程ID是否相同:

    int pthread_equal(pthread_t t1, pthread_t t2);
    分离属性:

    int pthread_detach(pthread_t tid);
    pthread_t tid:分离线程tid
    返回值:成功返回0,失败返回错误号。
    一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取
    它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收
    它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用
    pthread_join,这样的调用将返回EINVAL。如果已经对一个线程调用了pthread_detach就不
    能再调用pthread_join了 。
    最后,还有设置线程属性的一些相关函数:

    int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性
    int pthread_attr_destroy(pthread_attr_t *attr); //销毁线程属性所占用的资源
    但是,目前我们可以暂时不理会,以后需要使用的时候再深究。

  • 相关阅读:
    火狐浏览器kaptcha验证码点击无法刷新问题解决方法
    算法学习笔记——洗碗时遇到的汉诺塔问题
    JSP学习笔记
    springmvc中Tomcat跨服务器上传中文名文件报错解决方案
    MAVEN项目报错解决方法集锦(1)
    原生js模板语法之underscore.js
    HTML针式打印机打印模板
    elementui form表单验证
    vue+swiper背景图随swiper改变
    uni.navigateTo和uni.switchTab的区别
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/10826060.html
Copyright © 2011-2022 走看看