zoukankan      html  css  js  c++  java
  • 初识多线程

    线程的概念

    线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

     线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。线城是进程的一个执行分支,在进程的内部运行,在Linux下没有真正意义上的线程,他是线程模拟出来的。如下图说明
     这里写图片描述

     线程与进程的区别

    进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
    另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。
    总的来说如下
    1. 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
    2. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
    3. 调度和切换:线程上下文切换比进程上下文切换要快得多。
    4. 在多线程OS中,进程不是一个可执行的实体。

    线程的特点

    1. 轻型实体

    线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
    线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:
      (1)线程状态。
      (2)当线程不运行时,被保存的现场资源。
      (3)一组执行堆栈。
      (4)存放每个线程的局部变量主存区。
      (5)访问同一个进程中的主存和其它资源。
    2. 独立调度和分派的基本单位。
    3. 可并发执行。
    在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
    4. 共享进程资源。
      在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

    创建线程

    int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

    thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。
    attr: 用于指定线程的属性
    start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。
    arg: 传递给线程函数的参数。
    创建线程成功返回0,失败返回错误码。
    下面是具体的代码。  

    #include<stdio.h>
    #include<pthread.h>
    #include<stdlib.h>
    #include <stdlib.h>
    void *thread_run(void *val)
    {
        printf("new thread,thread is: %u,pid is: %d
    ",pthread_self(),getpid());
        return NULL;
    }
    int main()
    {
        pthread_t tid;
       if(pthread_create(&tid,NULL,thread_run,NULL)!=0)//创建线程
       {
           printf("creat thread error!
    ");
           return 0;
       }
       printf("main thread_run:pid is :%d,thread is:%u
    ",getpid(),pthread_self());
       sleep(2);
       return 0;
    }

    运行结果如下
    这里写图片描述

    线程终止

    如果需要只终止某个线程而不终止整个进程,可以有三种方法:
    1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于exit。
    2. 一个线程可以调用pthread_exit来终止自己
    3. 线程还可以调用pthread_cancel终止同一进程中的另一个线程。进程取消,退出结果为-1.
    注意这里不能使用exit函数来结束线程,因为他是用来结束进程的。

    线程等待

    #include <pthread.h>
    void pthread_join(pthread_t th void* thread_return); 
      //挂起等待th结束,*thread_return=retval;
    //成功返回0,失败返回错误号

    thread线程以不同的方法终止,通过pthread_join得到的终.止状态是不同的,总结如下:
    1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
    2. 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元.里存放的是常数PTHREAD_CANCELED(pthread库中一般是-1)。
    3. 如果thread线程是.自.己调.用pthread_exit终.止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数
    下面是具体代码实现线程等待

    #include<stdio.h>
    #include<pthread.h>
    #include<stdlib.h>
    #include <stdlib.h>
    void *thread_run(void *val)
    {
        printf("new thread,thread is: %u,pid is: %d
    ",pthread_self(),getpid());
     //   pthread_exit((void*)123);
        return (void*)12;
    }
    int main()
    {
        pthread_t tid;
       if(pthread_create(&tid,NULL,thread_run,NULL)!=0)//creat thread
       {
           printf("creat thread error!
    ");
           return 0;
       }
       pthread_cancel(tid);//jinchengzhongzhi
       void *ret;
       pthread_join(tid,&ret);
       printf("join new thread sucess,ret:%d
    ",(int)ret);
       return 0;
    }
    

    运行结果如下
    这里写图片描述

    线程的分离

    在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。⼀一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,⼀一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终⽌止时由系统⾃自动释放。

    一般情况下,线程终止后,其终止状态⼀一直保留到其它线程调用pthread_join获取它的状态为止。 但是线程也可以被置为detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调将返回EINVAL。 对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也 就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

    //线程分离
    int pthread_detach(pthread_t thread);

    默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。

    如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process(僵尸进程),即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。

    由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码

    pthread_detach(pthread_self())

    或者父线程调用

    pthread_detach(thread_id)//(非阻塞,可立即返回)

    这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源.
    代码如下

     #include<stdio.h>
     #include<pthread.h>
     #include<stdlib.h>
     #include <stdlib.h>
     void *thread_run(void *val)
     {
         pthread_detach(pthread_self());
         printf("%s
    ",(char*)val);
         return NULL;
     }
     int main()
     {
         pthread_t tid;
        if(pthread_create(&tid,NULL,thread_run,"other thread_run..")!=0)//creat thread
        {
            printf("creat thread error!
    ");
            return 0;
        }
        int ret = 0;
        sleep(1);
        if(0==pthread_join(tid,NULL))//wait success
        {
            printf("pthread wait success
    ");
            ret = 0;
        }
        else
        {
            printf("pthread wait failed!
    ");
            ret = 1;
        }
        return ret;
     }
    

    这里写图片描述
    从结果中我们可以看到在模拟分离之后,依旧在等待。
    注意:在被分离的线程如果发生了严重的情况(例如除0 或者出错),依旧可以影响主线程。

  • 相关阅读:
    云计算设计模式(十一)——健康端点监控模式
    大数据R语言简析
    git查看/修改 用户名和邮箱
    MySQL查询和修改auto_increment的方法
    git 配置用户名和邮箱
    discuz安装小云app
    二维码转化为链接
    discuz更换域名,登录不了解决
    数据结构很重要
    C++ vector错误(1)
  • 原文地址:https://www.cnblogs.com/chan0311/p/9427346.html
Copyright © 2011-2022 走看看