多线程编程

本文深入探讨了进程与线程的区别,详细解析了线程的概念、特点及其在Linux系统下的实现原理。阐述了线程的优缺点,以及线程控制、创建、终止、等待和分离等关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本片博客会粘贴部分代码,想要了解更多代码信息,可访问小编的GitHub关于本篇的代码

对比进程与线程的区别

线程概念及特点

  1. Linux下线程是以进程模拟的,Linux下的进程控制块pcb实际就是一个线程,其他系统不一定
  2. Linux对进程和线程不作区分,Linux下的线程以进程PCB模拟,也就是说task_struct其实就是线程CPU调度的基本单位是线程,进程就是线程组
  3. Linux下的pcb其实就是线程的描述,Linux下的线程以进程pcb模拟,因此也叫轻量级进程。
  4. 一个进程中至少有一个线程,线程是进程内的一条执行流。

线程共享进程数据,但也拥有自己的部分数据
线程ID 、组寄存器(上下文数据)、栈、errno 、信号屏蔽字、调度优先级

如果定义1个函数,在各线程中都可以调用,如果定义1个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者?定义的信号处理函数)
当前工作目录
用户id和组id
Text Segment、Data Segment都是共享的。

线程优缺点

线程的优点
一个进程中有可能会有多个线程,而这些线程共享同一个虚拟地址空间,因此有时候也会说线程是在进程中的
a、 因此他们共享了整个代码段、数据段,线程间通信变得极为方便
b、创建或销毁一个线程相比较于进程来说成本更低(不需要额外创建虚拟地址空间)
c、线程的调度切换相较于进程也较低
d、线程占用的资源比进程少很多
e、能够充分利用多处理器的可并行数量
f、在等待IO操作、CPU计算等任务时候,线程支持多处理器并行处理,任务分摊,提高效率。

线程的缺点
一个进程中有可能会有多个线程,而这些线程共享同一个虚拟地址空间
a. 因为线程间的数据访问变得简单,因此数据安全访问问题更加突出,要考虑的问题增多,编码难度增多。资源争抢问题更加突出。
b. 一些系统调用和异常都是针对整个进程的,因此一个线程中出现了异常,那么,整个进程都会受到影响,以及一些系统调用的使用也是需要注意的。

进程的优点:安全、稳定(因为进程的独立性)

线程的控制

进程是操作系统资源分配的一个基本单位(通过页表)
线程是CPU调度的一个基本单位(CPU调度的是pcb)

线程创建:线程共享进程地址空间,但是每一个线程都有自己相对独立的一个地址空间
操作系统并没有提供系统调用来创建线程,所以就在posix标准库中,实现了一套线程控制(创建、终止、等待…)的接口,因为这套接口创建的线程是库函数,是用户态的线程创建,因此创建的线程也叫做用户线程。 一个用户线程对应了一个轻量级进程来进行调度运行的。(对操作系统来说,创建了一个轻量级进程)

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.

Linux下线程操作函数都是库函数,需要链接动态库-pthread

thread: 用于接受一个用户线程ID
attr: 用户设置线程属性,一般置空
start_routine:线程的入口函数,线程运行的就是这个函数,这个函数退出了,线程也就退出了
arg:用于给线程入口函数传递参数
返回值:成功:0 失败:errno

pthread_create接口创建了一个用户线程,并且通过第一个参数返回了一个用户的id,这个id数字非常大,其实它就是一个地址,是指向自己的线程地址空间在整个进程虚拟地址空间中的位置。
每一个线程都需要有自己的栈区,否则如果所有线程共用一个栈的话,会引起调用栈混乱,并且因为CPU是以pcb来调度的,因此CPU调度的基本单位,所以每一个线程也都应该有自己的上下文数据来保存CPU调度切换时的数据。(而这些都是在线程地址空间中,每一个线程都有自己的线程地址空间,它们相对来说独立,但是线程地址空间是在虚拟地址空间内的)

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

void* start(void *arg){
    int num = (int)arg;
    while(1){
    printf("The pthread%d\n",num);
    sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,start,(void*)999);
    //pthread_t pthread_self(void);
    //  获取调用线程的线程id(用户态线程id)tid是pthread_t类型即无符号长整型lu
    printf("Main pthread ID:%lu\n",pthread_self());
    printf("The pthread!!ID:%lu\n",tid);
    while(1){
        printf("This is main pthread!!\n");
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
其实在每一个task_struct里边都有pid、tgid,其中pid线程标识,tgid是描述该线程属于哪个线程组(进程)的线程,这个tgid实际上是该线程所处线程组的首线程的pid,也就是说在一个线程组中tgid等于pid的那个线程就是主线程。

  1. 查看进程中所有线程信息1:ps -efL
ps -efL |head -1&&ps -efL |grep create

在这里插入图片描述
LWP:轻量级进程,这一列记录的就是每个线程task_struct中的tid
NLWP:这个进程中有几个线程
PPID:父进程ID
PID:进程ID(线程组tgid),也就是主线程的tid。

查看线程

ps -aL |head -1&&ps -aL |grep create

在这里插入图片描述
PID:进程ID(线程组tgid),也就是主线程的tid。
LWP:线程ID,各个task_struct中的tid.

 ps aux -L | head -1 && ps aux -L | grep exit | grep -v 'test'
ps -aL

线程没有父子之分,所有线程都是有平级的,如果非要说有区别的话,那么就是主线程和其他线程的区别。

线程终止

线程的退出方式:

  1. return num;退出:在main函数中调用return,效果是退出进程,在线程中调用return,退出线程。
  2. exit(num)针对整个进程,即使在非主线程中使用exit(),也会是整个进程退出.
void pthread_exit(void *retval);

       Compile and link with -pthread.

DESCRIPTION
The  pthread_exit()  function terminates the calling thread and returns a value
 via retval that (if the thread is joinable)is available to another thread in
  the same process that calls pthread_join(3).

retval存储线程退出状态信息,如果不关心,可以置空。
终止一个调用线程,线程调用线程退出,main函数调用,主线程退出,其他线程成了僵尸线程,进程显示僵尸进程,进程显示的是主线程stat。终止一个调用线程,线程调用线程退出,main函数调用,主线程退出,其他线程成了僵尸线程,进程显示僵尸进程,进程显示的是主线程stat。

int pthread_cancel(pthread_t thread);
	  Compile and link with -pthread.

主线程用来取消主线程自己的线程id为thread的线程

//这段代码用于演示线程退出的几种方式
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>

void *thr_start(void *arg)
{
    //在线程中调用exit函数会怎样?
    //进程要是退出了,那么进程中的线程也会退出
    //exit(0);
    //return NULL;
    //sleep(5);
    //pthread_exit(NULL);
    while(1){
    printf("child pthread!!!\n");
    sleep(1);
    }
return NULL;
}
int main()
{
    pthread_t tid;
    int ret = -1;

    ret = pthread_create(&tid,NULL,thr_start,NULL);
    if(ret != 0){
        printf("pthread create error\n");
        return -1;
    }
    //int pthread_cancel(pthread_t thread);
    //取消普通线程
    pthread_cancel(tid);
    while(1){
        printf("first pthread!!\n");
        sleep(1);
    }
    return 0;
}

线程等待(线程分离)

主线程退出,其他线程也会形成僵尸线程:占用了一部分资源不释放,最终造成资源泄露。
线程等待就是接受线程的返回值,然后释放线程的所有资源。
线程处于joinable状态才能被等待,如果一个线程在调用pthread_join函数之前已经退出,则pthread_join函数立即返回,否则阻塞等待,直到这个指定的线程退出,才会返回。

pthread_join函数的返回值只需要考虑线程的退出码或者errno,不需要考虑线程异常的情况,因为一旦线程产生异常,系统会认为整个进程异常,这个进程就挂了。

 int pthread_join(pthread_t thread, void **retval);   //避免资源泄露,线程属性是joinable状态

pthread_t thread:指定等待线程的线程ID
void **retval:将线程退出信息接收到retval中

The pthread_join() function waits for the thread specified by thread to terminate. If that thread has already terminated,
then pthread_join() returns immediately. The thread specified by thread must be joinable.

//演示线程退出等待,获取线程返回值
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

//线程需要被等待的条件是:线程处于joinable状态,一个线程创建出来默认属性就是joinable状态
void* Func()
{
    sleep(2);//让这个非主线程睡觉,让主线程等它睡醒后,接收该线程的退出状态值,释放资源
    printf("I am not the main pthread\n");
    pthread_exit("I am not the main pthread !I will exit!");
    return "I am the best!!";
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,Func,NULL);
    if(ret!=0){
        perror("pthread_create error");
        exit(-1);
    }
   
    printf("I am the Main pthread\n");

    char* addr;//定义一级指针用于给pthread_join的第二个参数初始化,否则二级指针为空,不能使用
    char** ptr = &addr;
    sleep(5);
    pthread_join(tid,(void**)ptr);//主线程到这里会阻塞等待它创建的线程id是tid的线程
    printf("%s\n",*ptr);

    //pthread_cancel(tid);
    //sleep(5);
    //pthread_join(tid,(void**)ptr);//主线程到这里会阻塞等待它创建的线程id是tid的线程
    //如果一个线程是被取消的那么它的返回值只有一个-1,PTHREAD_CANCELD
    //printf("%d\n",addr);
    return 0;
}

在这里插入图片描述
线程分离:功能是设置状态
我们等待一个线程是因为需要获取线程的返回值,并且释放资源。那么假如我不关心返回值,那么这个等待将毫无意义,仅仅是为了释放资源。因此就有一个线程属性叫:线程分离属性 detach属性,这个属性就是需要设置,它是告诉操作系统,这个指定的线程我不关心返回值,所以如果线程退出了,操作系统就自动把所有资源回收。而设置一个线程分离属性我们常称为线程分离

线程的detach属性与joinable属性相对应,也相冲突,两者不会同时存在。如果一个线程属性是detach,那么pthread_join的时候将直接报错,所以我们说,只有一个线程处于joinable状态才可以被等待

线程被设置成detach属性,退出后将自动释放资源,不会形成僵尸线程
detach与joinable属性相冲突,无法同时存在,设置detach就表明了不关心返回值

int pthread_detach(pthread_t thread);

线程也可以分离自己pthread_detach(pthread_self());

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值