Linux线程入门

目录

1、概念

2、线程API函数

2.1创建线程

2.2线程属性

获取线程属性API:

设置线程属性API

主要的几个属性API

获取、设置线程的分离属性

以下接口跟线程的调度相关:

获取、设置线程是否继承创建者的调度策略

获取、设置线程的调度策略

线程的静态优先级和动态优先级的设置

跟线程的栈和警戒区的大小相关API

获取、设置线程栈大小、警戒区大小

退出、接合线程

给指定线程发送一个取消请求(杀死线程)

获取、设置线程的取消状态和取消类型

压栈、或弹栈线程的取消处理例程


1、概念

        线程实际上是应用层的概念,在 Linux 内核中,所有的调度实体都被称为任务(task)他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。
进程线程的一些区别:
本质区别:进程是操作系统资源分配的基本单位,而线程是CPU基本调度单位。
        进程之间资源是独立的(进程之间不能共享数据段),一个进程下必然有>=1个线程,同一进程下的多线程之间有一部分资源是共享的。

共享的资源:

  1. 进程内存空间:同一进程中的所有线程共享该进程的内存,包括堆区和数据段(全局变量、静态变量等)。不同线程可以访问和修改共享的全局变量和动态分配的内存。

  2. 文件描述符:所有线程共享进程打开的文件描述符,这意味着它们可以共享文件、网络连接等资源。

  3. 进程的代码段:所有线程执行相同的程序代码。

  4. 内存映射区域(Memory-mapped regions):如果有内存映射的文件或共享内存区域,线程也可以共享这些内存区域。

独立的资源:

  1. 线程栈:每个线程都有自己的栈空间,用于存储局部变量和函数调用信息。不同线程的栈是相互独立的,互不干扰。

  2. 程序计数器(PC):每个线程有自己独立的程序计数器,用来跟踪该线程当前执行到哪一条指令。

  3. 寄存器:每个线程有自己的寄存器值,独立于其他线程。

  4. 线程局部存储(TLS):每个线程可以有自己的线程局部存储(例如,pthread中的thread_local变量),这些变量对其他线程是不可见的。

健壮性:进程健壮性更强,同一进程下的多线程,有一个线程崩溃整个进程就会崩溃。

切换效率:不同进程之间的切换效率低于同一进程下的多线程之间切换。 

2、线程API函数

2.1创建线程

对此函数的使用需要知道以下几点:
        1,线程例程指的是:如果线程创建成功,那么该线程会立即去执行的函数。
        2,POSIX 线程库的所有 API 对返回值的处理原则都是一致的:成功返回 0,失败返回错误码              errno。
        3,线程属性如果为 NULL,则会创建一个标准属性的线程

        -l线程库名 

练习

创建线程

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



void * Func (void * arg)
{
    while(1)
    {
        printf("新创建线程打印输出\n");
        sleep(1);
    }
}

int main(int argc, char const *argv[])
{
    
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//     void *(*start_routine) (void *), void *arg);
    pthread_t My_Pth_TID = 1;
    pthread_create(&My_Pth_TID,NULL,Func,NULL);
    while(1)
    {

        printf("这里是初始线程打印输出\n");
        sleep(1);
    }
    return 0;
}

2.2线程属性

1. 线程创建类型 (pthread_create 的行为)

  • 属性类型PTHREAD_CREATE_JOINABLE 或 PTHREAD_CREATE_DETACHED

  • 说明:此属性指定线程的创建方式。

    • PTHREAD_CREATE_JOINABLE:这是默认的设置,表示线程在结束后可以被 pthread_join() 等待。

    • PTHREAD_CREATE_DETACHED:表示线程是分离的,线程结束后系统自动回收其资源,不能被 pthread_join() 等待。

2. 线程栈大小 (pthread_attr_setstacksize)

  • 属性类型size_t

  • 说明:设置线程的栈大小。每个线程都有一个栈,它是线程执行时的内存区域。如果栈空间不足,线程可能会崩溃或出现栈溢出。

  • 函数pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)

    • 例如:pthread_attr_setstacksize(&attr, 1024 * 1024); 设置线程栈大小为 1MB。

3. 线程栈地址 (pthread_attr_setstack)

  • 属性类型void *

  • 说明:设置线程栈的起始地址。这通常用于在特定的内存区域分配栈。一般情况下不需要设置,除非你有特殊需求(比如在嵌入式系统中)。

  • 函数pthread_attr_setstack(pthread_attr_t *attr, void *stack, size_t stacksize)

    • 例如:pthread_attr_setstack(&attr, stack_buffer, 1024 * 1024); 设置栈起始地址为 stack_buffer

4. 线程调度策略 (pthread_attr_setschedpolicy)

  • 属性类型int

  • 说明:设置线程的调度策略。不同的调度策略决定了操作系统如何安排线程执行。

    • SCHED_FIFO:先来先服务调度。

    • SCHED_RR:轮转调度。

    • SCHED_OTHER:默认调度策略,通常是基于时间片的调度策略。

  • 函数pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)

    • 例如:pthread_attr_setschedpolicy(&attr, SCHED_FIFO); 设置为先来先服务调度。

5. 线程优先级 (pthread_attr_setschedparam)

  • 属性类型struct sched_param

  • 说明:设置线程的调度优先级。优先级值通常是整数,值越大,线程的优先级越高。

  • 函数pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param)

    • 例如:struct sched_param param; param.sched_priority = 20; pthread_attr_setschedparam(&attr, &param); 设置线程的优先级为 20。

6. 线程继承调度策略 (pthread_attr_setinheritsched)

  • 属性类型int

  • 说明:设置线程是否继承创建线程的调度属性。

    • PTHREAD_INHERIT_SCHED:线程继承创建线程的调度策略和优先级。

    • PTHREAD_EXPLICIT_SCHED:线程不继承父线程的调度属性,使用自己设置的属性。

  • 函数pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched)

    • 例如:pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); 设置线程不继承父线程的调度策略。

7. 线程的守护线程属性 (pthread_attr_setdetachstate)

  • 属性类型int

  • 说明:设置线程的分离状态。

    • PTHREAD_CREATE_DETACHED:线程是分离的,不能被 pthread_join() 等待。

    • PTHREAD_CREATE_JOINABLE:线程是可连接的,允许 pthread_join() 等待线程结束。

  • 函数pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

    • 例如:pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 设置线程为分离状态。

8. 线程的调度优先级范围 (pthread_attr_getschedparam 和 pthread_attr_getschedpolicy)

  • 说明:用于获取线程的调度参数和调度策略

获取线程属性API:

设置线程属性API

以上 API 都是针对线程属性操作的,所谓线程属性是类型为 pthread_attr_t 的变量,设置一个线程的属性时,通过以上相关的函数接口,将需要的属性添加到该类型 变量里面,再通过 pthread_create( )的第二个参数来创建相应属性的线程。线程属性变量的使用步骤是:
        1,定义线程属性变量,并且使用 pthread_attr_init( )初始化。
        2,使用 pthread_attr_setXXX( )来设置相关的属性。
        3,使用该线程属性变量创建相应的线程。
        4,使用 pthread_attr_destroy( )销毁该线程属性变量。

主要的几个属性API

获取、设置线程的分离属性
一条线程如果是 可接合的 ,意味着这条线程在退出时 不会自动释放自身资源 ,而会成为僵尸线程,同时意味着该线程的 退出值可以被其他线程获取 。因此,如果 不需要某条线程的退出值的话 ,那么最好将线程 设置为分离状态 ,以保证该线程不会成为僵尸线程。
以下接口跟线程的调度相关:
获取、设置线程是否继承创建者的调度策略

当需要给一个线程设置调度方面的属性时,必须先将线程的inheritsched设置为PTHREAD_EXPLICIT_SCHED。

获取、设置线程的调度策略

以下是关于调度策略,需要知道的几点:
1,当线程的调度策略为 SCHED_FIFO 时,其静态优先级(static priority)必须设置为 1-99,这将意味着一旦这种线程处于就绪态时,他能立即抢占任何静态优先级为 0 的普通线程。采用 SCHED_FIFO 调度策略的线程还遵循以下规则:
        A) 当他处于就绪态时,就会被放入其所在优先级队列的队尾位置。
        B) 当被更高优先级的线程抢占后,他会被放入其所在优先级队列的队头位置,当
        所有优先级比他高的线程不再运行后,他就恢复运行。
        C) 当他调用 sched_yield( )后,他会被放入其所在优先级队列的队尾的位置。
        总的来讲,一个具有 SCHED_FIFO 调度策略的 线程会一直运行 直到发送 I/O 请求
        或者被更高优先级线程 抢占 ,或者调用 sched_yield( ) 主动让出 CPU。
2,当线程的调度策略为 SCHED_RR 时,情况跟 SCHED_FIFO 是一样的 ,区别在于:每一个 SHCED_RR 策略下的线程都将会被 分配一个额度的时间片 ,当时间片 耗光 时,他会被放入其所在优先级队列的 队尾的位置 。可以用 sched_rr_get_interval( )来获得时间片的具体数值。
3,当线程的调度策略为 SCHED_OTHER 时,其静态优先级(static priority)必须设置为 0。该调度策略是 Linux 系统调度的默认策略,处于 0 优先级别的这些线程按照所谓的 动态优先级被调度 ,而动态优先级 起始于线程的 nice 值 ,且每当一个线程已处于就绪态但 被调度器调度无视时 ,其动态优先级会自动 增加一个单位 ,这样能保证这些线程竞争CPU 的公平性。
线程的静态优先级和动态优先级的设置

跟线程的栈和警戒区的大小相关API
获取、设置线程栈大小、警戒区大小

 为什么常常不需要增大栈的空间?

  1. 默认的栈大小通常已经足够。
  2. 栈溢出问题常常是由于算法设计或代码逻辑问题,而不是栈空间不足。
  3. 增加栈空间会浪费系统资源,并不一定能解决根本问题。 因此,最好的做法是通过优化代码和算法来避免栈溢出。
退出、接合线程
线程跟进程类似,在缺省的状态下退出之后,会变成僵尸线程,并且保留退出值。其他 线程可以通过相关 API 接合该线程 ——(使其资源被系统回收,如果愿意的话还可以顺便获取其退出值
)。下面是相关 API:

如果线程退出时没有退出值,那么 retval 可以指定为 NULL。
 

pthread_join( )指定的线程如果尚在运行,那么他将会阻塞等待。
pthread_tryjoin_np( )指定的线程如果尚在运行,那么他将会立即出错返回。
  • retval 的取值逻辑

    1. 线程被取消(通过 pthread_cancel):
      retval 会被设置为 PTHREAD_CANCELED(一个特殊宏,通常是 (void*)-1)。

    2. 线程正常退出(通过 return 或 pthread_exit):
      retval 的值是线程函数的返回值(或 pthread_exit 传入的指针)。

    3. 线程未终止(未调用 pthread_join 前):
      调用 pthread_join 的线程会阻塞,直到目标线程结束。

测试:
1、创建一个默认属性的线程,主线程等待被创建的线程结束后,接合被创建的线程并接收返回值
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>



void * Func (void * arg)
{
    while(1)
    {
        printf("新创建线程打印输出\n");
        sleep(3);
        break;
    }
    //void *calloc(size_t nmemb, size_t size);
//注意这里不能创建一个变量进行返回,因为当这个线程退出后,创建变量的源空间就会销毁。
//因此我们需要再堆上开辟空间返回!!!!
     int* tetVal_Addr = (int*)calloc(1, 4);
     *tetVal_Addr = 115200;
     printf("新线程准备退出\n");
     //void pthread_exit(void *retval);
     pthread_exit((void*)tetVal_Addr);//之后的程序不会被执行
     printf("新线程已退出\n");

}

int main(int argc, char const *argv[])
{
    
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//     void *(*start_routine) (void *), void *arg);
    pthread_t My_Pth_TID = 1;
    pthread_create(&My_Pth_TID,NULL,Func,NULL);
    while(1)
    {
        printf("这里是初始线程打印输出\n");
        sleep(1);
        break;
    }
    //接合线程,回收线程资源
    //阻塞等待接合
    //int pthread_join(pthread_t thread, void **retval);
    int *retval ;
    pthread_join(My_Pth_TID, (void*)&retval);
    printf("新线程的退出返回值为:%d\n",*retval);
    return 0;
}
2、将被创建的线程设置为分离属性,被创建的线程不需要返回值(分离属性的线程无法返回值)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

void * Func(void *arg)
{
    while (1)
    {
        printf("新创建线程打印输出\n");
        sleep(3);
        break;
    }

    // 直接打印数值,而不是返回指针
    int tetVal = 115200;
    printf("新线程准备退出,返回值为:%d\n", tetVal);

    // 退出线程
    pthread_exit(NULL); // 无需返回指针
}

int main(int argc, char const *argv[])
{
    pthread_attr_t attr;
 
    
    // 初始化线程属性
    pthread_attr_init(&attr);

    // 将线程设置为分离属性,(线程在退出后可以直接被系统收尸)
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    // 将线程设置为接合属性
    //pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
    pthread_t My_Pth_TID;
    pthread_create(&My_Pth_TID, &attr, Func, NULL);

    // 销毁线程属性
    pthread_attr_destroy(&attr);
    while (1)
    {
        printf("这里是初始线程打印输出\n");
        sleep(10);
        break;
    }

    //分离线程不需要 pthread_join,移除以下代码
    int *retval ;
    if (0 == pthread_join(My_Pth_TID, (void*)&retval))
    {
        printf("新线程的退出接合成功,返回值为:%d\n",*retval);
    }
    else
    {        
        printf("接合失败\n");
    }
    
    return 0;
}
给指定线程发送一个取消请求(杀死线程)
另外,或许在某个时刻不能等某个线程“自然死亡”,而需要勒令其马上结束,此时可以给线程发送一个取消请求,让其中断执行而退出。

练习:杀掉创建的线程 

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


void * Func (void * arg)
{
    while(1)
    {
        printf("新创建线程打印输出\n");
        sleep(3);
    }
    //void *calloc(size_t nmemb, size_t size);
     int* tetVal_Addr = (int*)calloc(1, 4);
     *tetVal_Addr = 115200;
     printf("新线程准备退出\n");
     //void pthread_exit(void *retval);
     pthread_exit((void*)tetVal_Addr);//之后的程序不会被执行
     printf("新线程已退出\n");

}

int main(int argc, char const *argv[])
{
    int i = 0;
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//     void *(*start_routine) (void *), void *arg);
    pthread_t My_Pth_TID = 1;
    pthread_create(&My_Pth_TID,NULL,Func,NULL);
    while(1)
    {
        printf("这里是初始线程打印输出\n");
        sleep(1);
        i ++;
        if(i > 10)
        {
            printf("我要杀掉创建的进程\n");
            //杀死创建的进程
            pthread_cancel(My_Pth_TID);
            printf("我要杀掉完成\n");
            sleep(5);
            
            break;
        }
       
    }
    //如果杀死线程,就不能进行接合操作!!!!!

    //接合线程,回收线程资源
    //阻塞等待接合
    //int pthread_join(pthread_t thread, void **retval);
    // int *retval ;
    // pthread_join(My_Pth_TID, (void*)&retval);
    // printf("新线程的退出返回值为:%d\n",*retval);
    return 0;
}

通过结果我们可知:线程被杀掉我们不能去接合突然死掉的线程。 

获取、设置线程的取消状态和取消类型

 

测试:

启用取消+异步取消

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


// 线程2:启用取消 + 异步取消类型
void* thread_func2(void* arg) {
    int old_state, old_type;
    
    // 获取并设置取消状态(虽然默认是启用,这里为了演示API使用)
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
    // 设置取消类型为异步(PTHREAD_CANCEL_ASYNCHRONOUS)
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
    
    printf("Thread2: 旧取消类型: %s\n", 
          (old_type == PTHREAD_CANCEL_DEFERRED) ? "延迟" : "异步");

    // 模拟工作循环(没有取消点)
    while(1) {
        printf("Thread2: 运行中...\n");
        sleep(1);
        // 注意:异步取消不需要取消点
        // 这里故意不使用sleep等可能产生取消点的函数
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;


    // 创建线程2(异步取消)
    if(pthread_create(&tid2, NULL, thread_func2, NULL) != 0) {
        perror("创建线程2失败");
        return 1;
    }

    sleep(2); // 等待线程运行

    // 发送取消请求
    printf("\n主线程发送取消请求...\n");
    pthread_cancel(tid2);

    // 等待线程结束
    void* ret1, *ret2;

    pthread_join(tid2, &ret2);

    printf("线程2退出状态: %s\n", 
          (ret2 == PTHREAD_CANCELED) ? "被取消" : "正常退出");

    return 0;
}

2.禁用取消+延时取消 

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

// 线程1:禁用取消请求 + 延迟取消(默认类型,虽然实际不会执行取消)
void* thread_func1(void* arg) {
    int old_state;
    
    // 设置取消状态为禁用(PTHREAD_CANCEL_DISABLE)
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
    printf("Thread1: 旧取消状态: %s\n", 
          (old_state == PTHREAD_CANCEL_ENABLE) ? "启用" : "禁用");

    // 模拟工作循环
    for(int i = 0; i < 5; i++) {
        printf("Thread1: 工作中...(%d/5)\n", i+1);
        sleep(1);
    }

    // 重新启用取消
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
    printf("Thread1: 完成工作,退出\n");
    return NULL;
}

// 线程2:启用取消 + 异步取消类型
void* thread_func2(void* arg) {
    int old_state, old_type;
    
    // 获取并设置取消状态(虽然默认是启用,这里为了演示API使用)
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state);
    // 设置取消类型为异步(PTHREAD_CANCEL_ASYNCHRONOUS)
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
    
    printf("Thread2: 旧取消类型: %s\n", 
          (old_type == PTHREAD_CANCEL_DEFERRED) ? "延迟" : "异步");

    // 模拟工作循环(没有取消点)
    while(1) {
        printf("Thread2: 运行中...\n");
        sleep(1);
        // 注意:异步取消不需要取消点
        // 这里故意不使用sleep等可能产生取消点的函数
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;

    // 创建线程1(禁用取消)
    if(pthread_create(&tid1, NULL, thread_func1, NULL) != 0) {
        perror("创建线程1失败");
        return 1;
    }

    // // 创建线程2(异步取消)
    // if(pthread_create(&tid2, NULL, thread_func2, NULL) != 0) {
    //     perror("创建线程2失败");
    //     return 1;
    // }

    sleep(2); // 等待线程运行

    // 发送取消请求
    printf("\n主线程发送取消请求...\n");
    pthread_cancel(tid1);
    // pthread_cancel(tid2);

    // 等待线程结束
    void* ret1, *ret2;
    pthread_join(tid1, &ret1);

    // pthread_join(tid2, &ret2);

    printf("\n线程1退出状态: %s\n", 
          (ret1 == PTHREAD_CANCELED) ? "被取消" : "正常退出");
    // printf("线程2退出状态: %s\n", 
    //       (ret2 == PTHREAD_CANCELED) ? "被取消" : "正常退出");

    return 0;

压栈、或弹栈线程的取消处理例程

练习:

1. 一个线程函数,使用pthread_cleanup_push注册清理函数。

2. 清理函数的具体实现,如关闭文件或释放内存。

3. 主线程创建子线程,并在适当时候取消它。

4. 使用pthread_cleanup_pop弹出清理处理程序,可能带参数执行或不执行。

5. 验证资源是否被正确释放,无论线程是被取消还是正常退出。

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

/* 模拟资源结构体 */
typedef struct {
    FILE* logfile;
    char* buffer;
} ThreadResource;

// 清理函数1:关闭文件
void cleanup_file(void* arg) {
    FILE** fp = (FILE**)arg;
    if (*fp) {
        fclose(*fp);
        printf("清理处理: 已关闭文件\n");
        *fp = NULL;
    }
}

// 清理函数2:释放内存
void cleanup_memory(void* arg) {
    char** buffer = (char**)arg;
    if (*buffer) {
        free(*buffer);
        printf("清理处理: 已释放内存\n");
        *buffer = NULL;
    }
}

void* thread_function(void* arg) {
    ThreadResource res = {NULL, NULL};
    
    /* 压栈清理处理程序(FILO顺序) */
    pthread_cleanup_push(cleanup_file, &res.logfile);
    pthread_cleanup_push(cleanup_memory, &res.buffer);

    /* 关键代码段开始 */
    printf("线程: 正在打开日志文件\n");
    res.logfile = fopen("thread_log.txt", "w");
    if (!res.logfile) {
        perror("fopen失败");
        pthread_exit(NULL);
    }

    printf("线程: 正在分配缓冲区\n");
    res.buffer = (char*)malloc(1024);
    if (!res.buffer) {
        perror("malloc失败");
        pthread_exit(NULL);
    }

    /* 模拟耗时操作(可能被取消)*/
    for (int i = 0; i < 5; i++) {
        printf("线程: 正在工作(%d/5)\n", i+1);
        sleep(1);  // 这是一个取消点
    }
    /* 关键代码段结束 */

    /* 弹栈清理处理程序(0表示不执行)*/
    pthread_cleanup_pop(0);  // 弹出内存清理但不执行
    pthread_cleanup_pop(0);  // 弹出文件清理但不执行

    /* 正常释放资源 */
    free(res.buffer);
    fclose(res.logfile);
    printf("线程: 正常完成资源释放\n");
    return NULL;
}

int main() {
    pthread_t tid;
    ThreadResource resource = {NULL, NULL};

    if (pthread_create(&tid, NULL, thread_function, &resource) != 0) {
        perror("pthread_create失败");
        return 1;
    }

    sleep(2);  // 等待线程进入工作状态
    printf("\n主线程: 发送取消请求\n");
    pthread_cancel(tid);

    void* retval;
    pthread_join(tid, &retval);

    if (retval == PTHREAD_CANCELED) {
        printf("\n线程已被成功取消\n");
    } else {
        printf("\n线程正常退出\n");
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值