10.线程: 创建(prhread_create)、退出(prhread_exit)、等待

本文详细介绍了Linux线程的概念,包括进程与线程的区别、线程的创建、退出、等待和分离。线程作为轻量级进程,提供了更高效的资源利用和并发执行能力。在Linux中,多线程主要通过POSIX线程库实现,如pthread_create、pthread_join等API。线程同步方面,提到了互斥锁和条件变量的作用。示例代码展示了如何创建、退出线程以及等待线程完成,并传递返回值。
摘要由CSDN通过智能技术生成

Linux线程介绍

线程与进程

        典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。  

  进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

  线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

进程——资源分配的最小单位,线程——程序执行的最小单位"

         进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

使用线程的理由

        1 线程占用的资源比进程少,只虚复制PCB即可
        2.创建时代价较小
        3.线程间的切换(调度)需要操作系统做的工作少很多
        4.线程间共享数据更容易
        5.在等待慢速 I/O操作结束的同时,程序可执行其他的计算任务。
        6.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
        7.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
注:关于I/O密集型和计算密集型可参考这篇文章:CPU-bound(计算密集型) 和I/O bound(I/O密集型


  在操作系统内部,它不管什么进程线程的,它只以PCB为准,只有在用户态里才有线程的概念。一般实现线程会用到一个POSIX线程库,在这里可以通过调用POSIX库里的函数来实现有关线程的各种操作。不过内核中也有一种内核级线程。

  两个基本类型:

用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。如POSIX线程库。

系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。

POSIX线程库

  由系统库支持。线程的创建和撤销以及线程状态的变化都由库函数控制并在目态(user态)完成,与线程相关的控制结构TCB保存在目态并由系统维护。由于线程对操作不可见(操作系统可见的必然保存在kernel态由系统维护),系统调度仍以进程为单位(同一进程内线程相互竞争),核心栈的个数与进程个数相对性。

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的要使用这些库函数,就要引入头文件

gcc在链接这些线程函数库时要使用编译器命令的“-lpthread”选项(pthread是共享库文件)

 Linux上线程API

        多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:       

        线程运行过程中必须带上:gcc  f10.c      -lpthread   

a5efeb4ef3254fa29daa4e7246be9630.png

 线程API

具体参考:线程的创建、线程等待、线程终止、线程分离

注意:线程ID、创建时传递的参数得注意转换 、空类型的指针一般都要强转过去

           ,因为输出ptthread_t 、void * 类型,大多数参数都要转

          线程ID:prhread_t     ,读取时转为unsigned   long    %ld

         参数:  int ret=1100;   创建时  (void * )&ret  转换为空类型指针,读取时在转回来(int *)&ret

          设置线程退出的返回值,必须加static,join和exit搭配时,注意转换

    1. 线程创建                   

#include <pthread.h>
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
      
调用样例:ID、NULL、函数名、参数

参数: 
id:     输出时应该强制转换unsigned long   %ld
thread: 线程id地址,将线程id存入,线程标识符,线程栈的起始地址,输出型参数
attr:   线程属性,NULL,8种选项
函数指针:新线程执行该函数指针指向的代码,线程回调函数     void* func (void *)
arg:    传递给函数指针指向的函数的参数,线程回调函数的参数一个地址、不同类型的地址得转换void *

返回值:成功返回0,失败返回错误码,如:
   EAGAIN :  描述: 超出了系统限制,如创建的线程太多。 
   EINVAL :  描述: tattr 的值无效。

        当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。像进程id一样,由内核决定,再存放进去。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。

  新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

  2. 线程退出

  单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

  1)线程只是从启动例程中返回,返回值是线程的退出码。

  2)线程可以被同一进程中的其他线程取消。

  3)线程调用pthread_exit:

#include <pthread.h>
int pthread_exit(void *rval_ptr);//rval_ptr:退出返回值 (void *)(&(int ret))强转
// rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问这个指针

        在与pthread_join配合传递返回值的时候,返回值的定义一定要用static,防止函数调用重置数据,从而pthread_join读取到随机数。应该是强制转换的时候,会改变地址,加static可以不变。

rval_ptr 可以为空,线程退出时,传入参数。

    3. 线程等待

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
//pthread_t thread, //要等待的线程ID
//void **retval     //用于接收线程退出的退出码
// 返回:若成功返回0,否则返回错误编号

        功能:以阻塞的方式回收新线程,可以得到线程的退出码,并回收其资源
   如果不使用pthread_join回收线程,有可能造成和僵尸进程一样的问题,造成内存泄漏;

        调用这个函数的线程将一直阻塞,直到指定的线程(pthread_t thread)调用pthread_exit、从启动例程中返回或者被取消。( void **rval_ptr)为进程id(pthread_t thread)返回或退出的返回码

       指定线程exit退出后,静态返回码static int c;的地址被存放在另一个位置,这个位置被join读取。所以这里接收要用二级指针,而存放用的是一级指针

        如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。

  可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。

  如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。

  4. 线程脱离

  一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。脱离的线程却像守护进程,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态。

  pthread_detach函数把指定的线程转变为脱离状态。

#include <pthread.h>
int pthread_detach(pthread_t thread);
// 返回:若成功返回0,否则返回错误编号

  本函数通常由想让自己脱离的线程使用,就如以下语句:

pthread_detach(pthread_self());

  5. 线程ID获取及比较

#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID

  对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。我们应该要用下边的函数:

  对于多线程程序来说,我们往往需要对这些多线程进行同步。同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源。而在此时间内,不允许其它的线程访问该资源。我们可以通过互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock)来同步资源。在这里,我们暂不介绍读写锁。

示例

示例1:线程的创建、等待

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

void *func(void * age){

        printf("This is next 线程 ID:%ld\n",(unsigned long)pthread_self());//转化
        printf("传入的参数%d\n",*((int*)age)); //转换
}

int main()
{
        pthread_t main_xpid;   //读取要转换

        int date=100;         //运用要转换
        pthread_create(&main_xpid,NULL,func,(void*)&date);
//函数名+地址的转换,主要传地址
//pthread_create(&main_xpid,NULL,func,NULL);

        printf("这是主线程的id:%ld\n",(unsigned long)pthread_self());
//  sleep(2);
        pthread_join(main_xpid,NULL);//等待次线程退出,也可接收返回码

  

示例二 :线程的退出,等待

等待退出和之前的效果一样,空类型的返回值可以为任意值,字符串也行

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *func(void * age){
        static int back=10;//设置的返回值
        printf("This is next 线程 ID:%ld\n",(unsigned long)pthread_self());

        printf("传入的参数%d\n",*((int*)age));
        pthread_exit((void*)&back);//强转为void *类型指针   
        //字符串的话,(void*)back
}

int main()
{
        pthread_t ci_xpid;
        int date=100;
        int *back;   //设置为一级指针,方便强转
        pthread_create(&ci_xpid,NULL,func,(void*)&date);
        printf("这是次线程的id:%ld\n",(unsigned long)ci_xpid);
        printf("这是主线程的id:%ld\n",(unsigned long)pthread_self());

        pthread_join(ci_xpid,(void **)&back);//取地址后为int**类型,强转void**类型
        printf("返回值%d\n",*back);//字符串的话back

        return 0;
}
//exit退出后,static int c;的地址被存放在另一个位置,这个位置被join的&p读取。
所以指针p就等于&c,即p指向了c,*p==c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值