关于线程的一些小结

线程是操作系统调度的最小单位,存在于进程中。创建线程通常通过pthread_create函数,主线程创建子线程。线程间通信比进程间通信更便捷,且线程切换开销小。线程安全和可重入函数是多线程编程中的重要概念,确保函数在并发调用时能正确工作。
摘要由CSDN通过智能技术生成

什么是线程?

  • 线程是参与系统调度最小单位包含在进程之中,是进程中的实际运行单位

如何创建线程?

  • 当一个程序启动时,就有一个进程被操作系统创建,与此同时主线程(Main Thread)也立刻运行。
  • 只有主线程的进程称为单线程进程,多线程是指除了主线程以外,还包含其他的线程,通常由主线程来创建,这个创建的新线程就是主线程的子线程。

线程的特点?

  • 线程时程序最基本的运行单位,可以认为进程仅仅是一个容器,包含了线程运行所需要的数据结构、环境变量等信息。
  • 同一进程的多个线程将共享该进程中的全部系统资源,如虚拟地址空间、文件描述符和信号处理等。
  • 同一进程的多个线程有各自的调用栈(call stack),自己的寄存器环境、自己的线程本地存储。

线程与进程之间

  • 进程切换开销大
  • 进程间通信较为麻烦,每个进程都在各自的地址空间中,相互独立、隔离,处在不同的地址空间中。
  • 同一进程多个线程间切换开销比较小
  • 同一进程多个线程间通信容易,共享了进程的地址空间,所以它们都是在同一个地址空间,通信容易
  • 线程创建的速度远大于进程创建的速度
  • 多线程多核处理器上更有优势

线程ID

与每个进程都有一个进程ID一样,线程也有对应的标识,不同的是,进程ID在整个系统中是唯一的,线程ID只有在它所属的进程上下文中才有意义。

pthread_t pthread_self(void);

在linux系统中,使用无符整型(unsigned long int)来表示pthread_t数据类型。

创建线程

主线程可以使用库函数pthread_createf负责创建一个新的线程。

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

thread:pthread_t类型指针。

attr:定义了线程的各种属性

start_routine:是一个函数指针

arg:传给start_routine的参数

编写程序测试。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

static void *new_thread_start(void *arg){
    printf("新线程: 进程ID<%d>  线程ID<%lu>\n",getpid(),pthread_self());
    return (void *)0;//返回指针类型


}

int main(void){
    
    pthread_t tid;//创建线程id
    int ret;

    ret = pthread_create(&tid,NULL,new_thread_start,NULL);
    if(ret){
        fprintf(stderr,"ERROR:%s\n",strerror(ret));
        exit(-1);
    }

    printf("主线程: 进程ID<%d>  线程ID<%lu>\n",getpid(),pthread_self());
    sleep(1);
    exit(0);

}

小tip

fprintf(stderr,"ERROR:%s\n",strerror(ret));

之前有学过fprintf,但是并没有具体用到,现在是个好机会来看看这个函数了。

当一个用户进程被创建的时候,系统会自动为该进程创建三个数据流。分别是标准输出stdout、
标准输入stdin、标准错误stderr。

有了stderr存在,即使对标准输出进行了重定向,写到stderr中的输出通常也会显示在屏幕上。

stderr是无缓冲的。

再来看fprintf,它和printf的区别就是,printf打印输出到屏幕,fprintf是打印输出到文件。


来看一下运行结果。

 终止线程

void pthread_exit(void *retval);

如果主线程调用了pthread_exit(),那么主线程也会终止,但是其他线程依然正常运行,知道进程中所有线程终止才会使得进程终止。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>


static void *new_thread_start(void *arg){
    printf("新线程start\n");
    sleep(1);
    printf("新线程end\n");
    pthread_exit(NULL);


}

int main(void){
    
    pthread_t tid;//创建线程id
    int ret;

    ret = pthread_create(&tid,NULL,new_thread_start,NULL);
    if(ret){
        fprintf(stderr,"ERROR:%s\n",strerror(ret));
        exit(-1);
    }
    
    pthread_exit(NULL);
    printf("主线程end\n");
    exit(0);

}

看一下运行结果。

跟我们预想的一样。

回收线程

在父、子进程中,父进程可通过wait()函数阻塞等待子进程退出并获取其终止状态,回收子进程资源。

那么在线程当中,也需要如此,通过调用pthread_join()函数来阻塞等待线程终止,并获取线程的退出码,回收线程资源。

int pthread_join(pthread_t thread, void **retval);
调用 pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则pthread_join() 立刻返回。如果多个线程同时尝试调用 pthread_join()等待指定线程的终止,那么结果将是不确定的。
与进程的一些差别:
  • 线程的关系是对等的。进程中的任意线程均可调用pthread_join函数来等待另一个线程的终止。这与进程间层次关系不同,父进程如果使用fork()创建了子进程,那么它是唯一能够对子进程调用wait()的进行。
  • 不能以非阻塞的方式调用pthread_join()。对于进程,调用waitpid()既可以实现阻塞方式等待,也可以实现非阻塞方式等待。
测试一下。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>


static void *new_thread_start(void *arg){
    printf("新线程start\n");
    sleep(2);
    printf("新线程end\n");
    pthread_exit((void *)10);
}


int main(void){
    
    pthread_t tid;//创建线程id
    void *tret;   //存放退出码
    int ret;

    ret = pthread_create(&tid,NULL,new_thread_start,NULL);
    if(ret){
        fprintf(stderr,"ERROR:%s\n",strerror(ret));
        exit(-1);
    }
    ret = pthread_join(tid,&tret);
    if(ret){
        fprintf(stderr,"pthread_join error:%s\n",strerror(ret));
        exit(-1);
    }
    
    printf("新线程终止,code = %ld\n",(long)tret);
    exit(0);

}

结果。

线程安全

线程栈

进程中创建的每个线程都有自己的栈地址空间,称为线程栈

那么每个线程运行过程中所定义的自动变量(局部变量)都是分配在自己的线程栈中的,不会互相干扰。

可重入函数

单线程程序只有一条执行流,而对于多线程程序而言,同一进程却存在多条独立、并发的执行流。

如果一个函数被同一进程的多个不同的执行流同时调用,每次函数调用总能产生正确的结果,这样的函数就成为可重入函数

函数被多个执行流同时调用的两种情况:

  • 在一个含有信号处理的程序中,主函数正在执行函数func(),此时进程接收到信号,主程序被打断,跳转到信号处理函数中执行,信号处理函数也调用了func()。
  • 在多线程的环境下 ,多个线程并发调用同一个函数

绝对可重入函数:

  • 函数内所使用到的变量均为局部变量
  • 函数参数和返回值均是值类型
  • 函数内调用的其他函数也均是绝对可重入函数

线程安全函数

一个函数被多个线程同时调用时,它总会一直产生正确的结果,把这样的函数成为线程安全函数。

线程安全函数包括可重入函数

 


参考正点原子应用开发教程​​​​​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值