Linux系统编程——线程

本文详细介绍了Linux系统中的线程概念,包括线程的定义、Linux内核线程实现原理、线程控制原语如pthread_self、pthread_create等函数的使用,以及线程属性和NPTL等内容。通过示例展示了线程创建、线程ID获取、线程退出和线程资源管理。强调了在多线程编程中应注意的事项,如线程间的资源共享和通信,以及避免使用exit函数导致所有线程退出的问题。
摘要由CSDN通过智能技术生成

1. 线程

       线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

       一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

       在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

       同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

       一个进程可以有很多线程,每条线程并行执行不同的任务。在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

1.1 什么是线程

在这里插入图片描述

在这里插入图片描述
进程A独享地址空间,线程B,C共享进程地址空间

1.2 Linux内核线程实现原理

在这里插入图片描述

查看LWP号ps –Lf pid 查看指定线程的lwp号。
线程号:cpu分配时间轮片的依据
线程ID:进程内部区分线程依据
在这里插入图片描述

三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元
三级映射描述了MMU如何把虚拟地址映射到物理内存
请添加图片描述
参考:《Linux内核源代码情景分析》 ----毛德操

       对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
       但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
       实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
       因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用

1.3 线程共享资源与非共享资源

线程共享资源 线程非共享资源
文件描述符表 线程id
每种信号的处理方式 处理器现场(寄存器的值)和栈指针(内核栈)
当前工作目录 独立的栈空间(用户空间栈)
用户ID和组ID errno变量
内存地址空间 (.text/.data/.bss/heap/共享库)(stack不共享) 信号屏蔽字
- 调度优先级

1.4 线程优、缺点

  • 优点
    • 提高程序并发性
    • 开销小
    • 数据通信、共享数据方便
  • 缺点
    • 库函数,不稳定
    • 调试、编写困难、gdb不支持
    • 对信号支持不好

优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大

2. 线程控制原语(重点)

2.1 pthread_self函数

获取线程ID。其作用对应进程中 getpid() 函数。

  • pthread_t pthread_self(void);返回值:成功:0; 失败:无!
  • 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
  • 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)
  • 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。

2.2 pthread_create函数

创建一个新线程。 其作用,对应进程中fork() 函数。

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    • 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。
    • 参数:
      • pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
      • 参数1:传出参数(没有用const描述),保存系统为我们分配好的线程ID
      • 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
      • 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
      • 参数4:线程主函数执行期间所使用的参数。

       在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态,稍后详细介绍pthread_join。
       pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self(3)可以获得当前线程的id。
       attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值,感兴趣的读者可以参考APUE。

创建一个新线程,打印线程ID。注意:链接线程库 -lpthread

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;
//线程函数,线程创建成功,系统自动调用
void *thrd_func(void *arg){
   
    cout << "In thread: thread id is :" << pthread_self() << " pid is :" << getpid() << endl;
    return NULL;
}

int main(){
   
    pthread_t tid;
    cout << "In main 1: thread id is :" << pthread_self() << " pid is :" << getpid() << endl;
    int ret = pthread_create(&tid,NULL,thrd_func,NULL);
    if(ret != 0){
   
    	perror("pthread_create error");
        exit(1);
    }
    sleep(1);//主线程等待1s,保证子线程输出
    cout << "In main 2: thread id is :" << pthread_self() << " pid is :" << getpid() << endl;
    return 0;//将当前进程退出
}

       由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行,下一节我们会看到更好的办法。

循环创建多个线程,每个线程打印自己是第几个被创建的线程。(类似于进程循环创建子进程)

#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *thrd_func(void *arg){
   
    long i = (long)arg;
    sleep(i);//保证子线程按顺序输出
    cout << "I am " << i+1 << " thread: thread id is :" << pthread_self() << " pid is :" << getpid() << endl;
    return NULL;
}

int main(){
   
    pthread_t tid;
    int i;
    //循环创建n个子线程
    for(i = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值