Linux线程(一)初识线程

目录

一、什么是线程

二、线程和进程的区别

三、线程的操作

1、创建线程

2、获取线程ID

3、线程的终止与等待

4、线程分离 


一、什么是线程

        在Linux中,线程(thread)是一种轻量级进程(Light-weight Process, LWP)的概念,它是进程内部的一个执行流,代表了程序中的一个独立执行路径。

        每个线程都拥有自己的程序计数器(PC)、栈、寄存器集合以及错误返回码(errno),但同时,线程之间共享所属进程的地址空间、文件描述符、信号处理器以及其它资源。这意味着线程可以在同一地址空间内并发执行不同的任务,有效地利用多核处理器的能力,并且线程间的通信开销比进程间通信要小得多。

从内核角度来看,线程和进程在Linux中并没有本质区别,它们都被视为任务,并由内核调度。每个线程和进程都有自己的进程控制块(PCB),在Linux中称为task_struct,但线程之间的PCB在某些方面(如内存空间)是共享的。线程的创建通常通过clone()系统调用来实现,通过传递特定的标志来决定新创建的实体与父进程之间资源共享的程度。

线程具有两个特点:

1、轻量化:创建线程更简单,因为不需要申请资源,与进程共用。

2、线程在进程的地址空间中运行。

示意图:

 

 OS如果支持线程,那么也必须管理,之前我们知道管理进程的结构PCB,但是如果管理线程再实现一个虽然可以,但是没有必要,在Linux中的实现方法是统一视为轻量级进程,这样实现更为简单。

二、线程和进程的区别

进程=内核数据结构+代码和数据

进程时承担系统资源的基本实体,线程是CPU调度的基本单位。而进程是操作系统调度的基本单位。

进程是资源分配的最小单位。每个进程都有独立的地址空间、内存、文件描述符集、打开的文件和其他资源。进程之间是隔离的,一个进程的崩溃通常不会直接影响其他进程。

线程共享其所属进程的资源,包括

文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id

线程不直接拥有系统资源,但可以访问其所在进程的所有资源。

每个线程有自己的

线程ID
一组寄存器
errno
信号屏蔽字
调度优先级

进程和线程的关系图: 

三、线程的操作

POSIX线程库:

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 “pthread_” 打头的
要使用这些函数库,要通过引入头文 <pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

1、创建线程

使用pthread_create()函数创建新线程。这个函数需要四个参数:指向线程标识符的指针、线程属性(通常为NULL使用默认属性)、线程入口函数的地址以及传递给线程入口函数的参数。

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
参数
thread: 返回线程 ID
attr: 设置线程的属性, attr NULL 表示使用默认属性
start_routine: 是个函数地址,线程启动后要执行的函数
arg: 传给线程启动函数的参数
返回值:成功返回 0 ;失败返回错误码

代码示例:

#include <pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void* thread_function(void* arg);

int main() {
    pthread_t thread_id;
    int rc = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (rc) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }
    // 主线程继续执行其他任务
    while(1)
    {
    printf("i am running,pid:%d\n",getpid());
    sleep(1);
    }
    return 0;
}

void* thread_function(void* arg) {
    while(1)
    sleep(1);
    // 线程执行的代码
    return NULL;
}

 

 可以看到LWP就是线程ID,而PID=LWD的就是主线程,Linux将它们都视为轻量级进程。

2、获取线程ID

pthread_ create 函数会产生一个线程 ID ,存放在第一个参数指向的地址中。该线程 ID 和前面说的线程 ID不是一回事。
前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程 ID ,属于NPTL 线程库的范畴。线程库的后续操作,就是根据该线程 ID 来操作线程的。
代码示例:
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>


const int threadnum = 5;
// 新线程
void *ThreadRountine(void *args)
{
    size_t num=(size_t)args;
    while (true)
    {
        std::cout<<"i am thread"<<num<<"thread ID"<<pthread_self()<<std::endl;
        sleep(2);
    }
}
int main()
{
    std::vector<pthread_t> pthreads;
    for (size_t i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);

        pthread_t tid;
        pthread_create(&tid, nullptr, ThreadRountine, (void*)i);
        pthreads.push_back(tid);
        sleep(1);
    }
    std::cout << "thread id: ";
    for(const auto &tid: pthreads)
    {
        std::cout << tid << std::endl;
    }
    std::cout << std::endl;
    while (true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }
}

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质 就是一个进程地址空间上的一个地址。

3、线程的终止与等待

pthread_exit函数
pthread_exit 函数 是POSIX线程库中的一个函数,用于强制退出当前调用该函数的线程。它的工作原理和作用类似于进程中的exit函数,但只影响调用它的线程,而不是整个进程。
功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。如果不需要传递退出状态,可以传递NULL.
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

 pthread_join函数

功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

我们使用这段代码来测试:

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

// 线程执行的函数
void* thread_function(void* arg) {
    printf("Thread start working...\n");
    sleep(5); // 模拟工作一段时间
    // 使用pthread_exit退出线程并传递一个整数作为退出状态
    pthread_exit((void*)100); // 传递100作为退出状态
}

int main() {
    pthread_t thread_id; // 线程ID
    int* exit_status=NULL; // 用于存储线程退出状态的指针

    // 创建线程
    if(pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    printf("Main thread continues doing other tasks...\n");

    // 等待线程结束并获取退出状态
    if(pthread_join(thread_id, (void**)&exit_status) != 0) {
        perror("pthread_join");
        exit(EXIT_FAILURE);
    }
    printf("Thread exited with status: %p\n", exit_status);
    return 0;
}

最后等待到进程:

当然,退出的返回值可以定义成任何东西,我们得到的应该是void*

要注意void不能定义变量,但是void*可以,因为void*本质是地址。

4、线程分离 

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
//可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());

代码示例:
我们在线程调用函数中分离这个线程:

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

// 线程执行的函数
void* thread_function(void* arg) {
    printf("Thread start working...\n");
    sleep(1); // 模拟工作一段时间
    // 使用pthread_exit退出线程并传递一个整数作为退出状态
    pthread_detach(pthread_self());
    pthread_exit((void*)100); // 传递100作为退出状态
    return NULL;
}

int main() {
    pthread_t thread_id; // 线程ID
    int* exit_status=NULL; // 用于存储线程退出状态的指针

    // 创建线程
    if(pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    printf("Main thread continues doing other tasks...\n");
    sleep(1);  //一定要先分离,在等待
    // 等待线程结束并获取退出状态
    if ( pthread_join(thread_id, NULL ) == 0 ) {
    printf("pthread wait success\n");
    } else {
    printf("pthread wait failed\n");
    }
    return 0;
}

测试的时候一定要注意先分离,再等待。

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

Joinable线程:默认情况下,新创建的线程是joinable的。这意味着它可以在完成执行后被其他线程通过pthread_join函数等待并获取其退出状态。如果一个joinable线程结束了但没有其他线程调用pthread_join来等待它,那么它的资源(如栈空间)将不会被完全回收,直到某个线程成功调用了pthread_join

分离(Detached)线程:分离线程在结束时会自动释放所有资源,不需要也不应该被其他线程调用pthread_join。线程可以通过调用pthread_detach(pthread_self())函数自我分离,或者在创建时通过线程属性设置为分离状态,从而成为一个分离线程。

简而言之,一旦一个线程被标记为分离(通过调用pthread_detach或创建时设置属性),它就不再是joinable的,其他线程就不能再通过pthread_join来等待它。反之,如果一个线程保持joinable状态,那么它就应该在某个时刻被其他线程通过pthread_join等待,否则可能会造成资源泄露。

  • 45
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值