Linux的多线程(线程的创建,退出,取消请求,取消处理例程,线程属性的设置)

进程:是系统分配资源的最小单位,系统会为每一个进程分配一块独立的虚拟内存空间

线程:是系统调度的最小单位,系统不会为线程分配新的内存空间,但是线程也参与系统调度

cpu把时间片分给每一个进程,进程中的时间片再切分分给每一个线程,所以线程也会得到时间片,所以线程使系统调度的最小单位

线程的创建

#include <pthread.h>

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

pthread_t *thread //线程的tid

const pthread_attr_t *attr //线程的属性

void *(*start_routine) (void *) //线程的任务函数

void *arg //传递给任务函数的参数

编译时的时候记得添加 -lpthread

void *(*start_routine) (void *) 函数指针,指向需要执行的任务函数
任务函数返回值必须为 void *, 参数必须为 void *,回调函数

cpp 复制代码
#include <stdio.h>
#include <pthread.h>

// 任务线程
void *task1(void *arg)
{
    int j = 0;
    while (1)
    {
        printf("j=%d\n", j++);
    }
}

// 任务线程
void *task2(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("i=%d\n", i++);
    }
}

// 任务线程
void *task3(void *arg)
{
    int k = 0;
    while (1)
    {
        printf("k=%d\n", k++);
    }
}

int main()
{
    pthread_t tid1;
    pthread_create(&tid1, NULL, task1, NULL);

    pthread_t tid2;
    pthread_create(&tid2, NULL, task2, NULL);

    pthread_t tid3;
    pthread_create(&tid3, NULL, task3, NULL);

    getchar();  //调用一个阻塞函数不让进程结束
}

线程传递参数

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

struct arg
{
    int a;
    double b;
    char str[1024];
} ;

// 线程任务函数
void *task(void *arg)
{
    struct arg *p = arg;
 
    printf("整数1 %d\n", p->a);
    printf("浮点1 %f\n", p->b);
    printf("字符1 %s\n", p->str);

}

int main()
{
    struct arg arg = {15678, 156.78, "15678"};
    // 创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, &arg);  //&arg是线程函数中的参数

    sleep(1);
    printf("整数2 %d\n", arg.a);
    printf("浮点2 %f\n", arg.b);
    printf("字符2 %s\n", arg.str);

    getchar(); //阻塞主进程
}

线程退出

退出当前线程

#include <pthread.h>
void pthread_exit(void *retval);

参数retval是线程的返回值,对应线程执行函数的返回值。若线程没有数据可返回则可写成NULL。

pthread_exit()用法可参照exit()

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *task(void *arg) // 子线程
{
    int i = 0;
    while (1)
    {
        printf("i=%d\n", i++);
        sleep(1);
        if (i == 10)
        {
            printf("退出子线程\n");
            pthread_exit(NULL); // 退出子线程
        }
    }
}

int main() // main 主线程
{
    // 创建线程线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    // 默认情况,主函数结束,那么进程也会结束,所有线程都会死亡!

    printf("退出主线程\n");
    pthread_exit(NULL); // 退出  main主函数线程。只是结束线程,进程并未结束
                        // 所以就算主线程结束了子线程也还是会继续运行
}

回收一个线程资源,主线程阻塞等待子线程结束,然后回收子线程资源

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval)

thread: 需要回收资源的线程tid

retval:线程的退出参数,参数同上填NULL即可

通过阻塞等待线程执行完回收资源

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>


// 任务线程
void *task(void *arg)
{
    int i = 0; // 局部变量,属于线程的栈空间
    while (1)
    {
        printf("线程正在执行任务 &i=%p %d\n", &i, i++);

        if (i == 10)
        {
            pthread_exit(&i); // 退出线程
        }
        sleep(1);
    }
}

int main()
{
    // 1.创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    printf("等待线程结束回收资源\n");

    void *p = NULL;
    pthread_join(tid, &p); // 一直阻塞等待线程退出 ! 回收线程的栈空间

    printf("线程结束 退出参数:%p\n", p);
    printf("退出参数 %d\n", *((int *)p)); // 访问已经回收后的内存资源,非法访问!!

    exit(0);

}

如果没有pthread_join那么线程根本不会执行完,直接在主线程中就exit(0)退出进程了

线程取消

发送一个取消命令给线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);

thread:需要取消的线程 tid
成功返回 0,失败将返回错误码

pthread_self() 返回主线程的线程号tid

线程取消请求

当线程收到一个取消请求时,他将会如何表现取决于两个东西:一是当前的取消状态,二是当前的取消类型。

线程的取消状态很简单--分别是PTHREADCANCEL ENABLE和 PTHREAD CANCEL DISABLE,前者是缺省的,代表线程可以接受取消请求,后者代表关闭取消请求,不对其响应。

而在线程接受取消请求的情况下,如何停下来又取决于两种不同的响应取消请求的策略一延时响应和立即响应,当采取延时策略时,线程并不会立即退出,而是要遇到所谓的"取消点"之后,才退出。而"取消点",指的是一系列指定的函数。

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);//开启或关闭取消请求

state : 新的取消状态 PTHREAD_CANCEL_ENABLE 开启

PTHREAD_CANCEL_DISABLE 关闭

oldstate:原来的状态

int pthread_setcanceltype(int type, int *oldtype); //设置取消类型

type:取消类型 PTHREAD_CANCEL_DEFERRED 延时取消 (默认类型)

PTHREAD_CANCEL_ASYNCHRONOUS 立即取消

**返回值:**成功 0

失败errno

设置线程取消请求demo:

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 任务线程
void *task(void *arg)
{
    // 关闭取消请求
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    int i = 0;
    while (1)
    {
        printf("线程正在执行任务 %d\n", i++);
        if (i == 10)
        {
            printf("执行完毕\n");
            break;
        }
        sleep(1);
    }
    // 开启取消请求
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

    while (1)
    {
        printf("线程正在运行\n");
        sleep(1);
    }
}

int main()
{
    // 1.创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    while (1)
    {
        printf("输入任意键取消线程\n");
        getchar();
        pthread_cancel(tid);
    }
}

在关闭线程取消请求之后,线程无法被取消,只有在重新打开线程取消请求之后的第二个循环中才能通过pthraed_cancel取消线程

线程取消处理函数

由于线程任何时刻都有可能持有诸如互斥锁、信号量等资源,一旦被取消很有可能导致别的线程出现死锁,因此如果一条线程的确可能被取消,那么在被取消之前必须使用以下API来为将来可能出现的取消请求注册"处理例程",让这些例程自动释放持有的资源。

注册一个取消处理函数,当线程被取消时,会去执行该函数

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);

routine: 函数指针,指向线程被取消后需要执行的函数

arg:传递给取消处理函数使用的参数

void pthread_cleanup_pop(int execute);

execute:0 不执行,线程正常结束不执行取消处理函数

1 执行,线程正确结束执行取消处理函数(只要线程被取消,都会执行取消处理函数)

pthread_cleanup_push必须与 pthread_cleanup_pop 一起使用,且在同一个作用域中

注册一个线程取消处理函数demo:

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
FILE *fp = NULL;

void clear_task(void *arg)
{
    printf("线程被取消,执行取消处理函数\n");
    fclose(fp); // 关闭文件,刷新缓存
}

void *task(void *arg)
{
    int i = 0;
    // 打开文件
    fp = fopen("test.txt", "w+");

    // 注册一个取消处理函数
    pthread_cleanup_push(clear_task, NULL);

    while (1) // 正在运行的时候被别人取消了
    {
        fputc('Q', fp); // 写入文件
        printf("线程正在写入数据到文件中\n");
        sleep(1);
        i++;
        if (i == 10)
        {
            break;
        }
    }

    //pthread_cleanup_pop(0); // 正常结束的时候,不执行
    pthread_cleanup_pop(1); //正常结束的时候, 执行

    printf("线程正常结束\n");
    fclose(fp); // 关闭文件,刷新缓存
}

int main()
{
    // 1.创建一个线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    while (1)
    {
        printf("按任意键取消线程\n");
        getchar();
        pthread_cancel(tid);
    }

    return 0;
}

在这段代码中当pthread_cleanup_pop中的值为1时,线程正常结束时也会执行clean_task中的函数,如果excute的值为0的话,那么只有在线程执行的过程中被打断会执行clean_task函数。

线程属性的设置

线程相关的api

查看系统的资源

bash 复制代码
loading@DESKTOP-R0NLPTR:~$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0              #core文件的最大值为100 blocks。
data seg size               (kbytes, -d) unlimited    #进程的数据段可以任意大。
scheduling priority                 (-e) 0        #调度优先级
file size                   (blocks, -f) unlimited     #文件可以任意大。
pending signals                     (-i) 25348       #最多有25348个待处理的信号。
max locked memory           (kbytes, -l) 64          #一个任务锁住的物理内存的最大值为32KB。
max memory size             (kbytes, -m) unlimited    #一个任务的常驻物理内存的最大值。
open files                          (-n) 1024        #一个任务最多可以同时打开1024的文件。
pipe size                (512 bytes, -p) 8        #管道的最大空间为4096字节。
POSIX message queues         (bytes, -q) 819200     #POSIX的消息队列的最大值为819200字节。
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192        #进程的栈的最大值为10240字节。
cpu time                   (seconds, -t) unlimited    #进程使用的CPU时间。
max user processes                  (-u) 25348    #当前用户同时打开的进程(包括线程)的最大个数为25348。
virtual memory              (kbytes, -v) unlimited    #没有限制进程的最大地址空间。
file locks                          (-x) unlimited    #所能锁住的文件的最大个数没有限制。
设置线程属性的流程

1、初始化线程属性

2、设置线程属性

3、根据设置的线程属性创建线程

4、销毁线程和属性

线程属性也是线程创建的第二个参数

线程属性的创建和销毁

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

线程属性的设置

#include <pthread.h>

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); //设置栈大小
int pthread_attr_getstacksize(const pthread_attr_t *attr,

size_t *stacksize);//获取栈大小

线程栈空间的设置
cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *task(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("线程正在运行 %d\n", i++);
        sleep(1);

        if (i == 10)
        {
            // 退出线程
            pthread_exit(NULL);
        }
    }
}

int main()
{
    // 初始化一个线程属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    int n;
    printf("请输入需要设置的线程大小\n");
    scanf("%d", &n);
    // 设置线程属性

    //分配的栈空间必须比系统分配最小的栈空间要大,
    //本机最小栈空间为16384,下文给出查看最小分配栈空间办法
    int size = 16384 + n;
    // int size = 1024;
    pthread_attr_setstacksize(&attr, size);

    // 根据当前的栈大小创建线程
    pthread_t tid;
    pthread_create(&tid, &attr, task, NULL);

    // 获取当前线程的栈大小
    size_t stacksize = 0;
    pthread_attr_getstacksize(&attr, &stacksize); // 获取栈大小

    printf("当前线程的栈大小 %ld\n", stacksize);

    // 回收线程资源
    pthread_join(tid, NULL);

    // 销毁属性
    pthread_attr_destroy(&attr);
}

获取本机当前需要分配的最小栈空间

stack_min.c

cpp 复制代码
#include <stdio.h>
#include <limits.h>

int main()
{
    printf("%d\n", PTHREAD_STACK_MIN);
}

可以看到本机的栈空间为16384

bash 复制代码
loading@DESKTOP-R0NLPTR:~/ gcc stack_min.c && ./a.out
16384
线程分离属性设置

一条线程如果是可接合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取。

因此,如果不需要某条线程的退出值的话,那么最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。

#include <pthread.h>

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

detachstate: 线程分离属性 PTHREAD_CREATE_DETACHED 分离状态,自动回收资源

PTHREAD_CREATE_JOINABLE 结合状态,手动回收资源

注意:当一个线程设置为分离属性后,pthread_join 函数失效了! 因为该线程已经无需手动回收资源!没有返回值的函数是宏定义函数!

设置分离属性自动回收资源demo:

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *task(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("线程正在运行 %d\n", i++);
        sleep(1);

        if (i == 10)
        {
            // 退出线程
            pthread_exit(NULL);
        }
    }
}

int main()
{
    // 初始化一个线程属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 设置线程分离属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 根据当前的栈大小创建线程
    pthread_t tid;
    pthread_create(&tid, &attr, task, NULL);

    printf("等待线程结束\n");
    // 回收线程资源
    pthread_join(tid, NULL); // 失效,因为线程自己回收资源!
    printf("线程资源回收\n");

    // 销毁属性
    pthread_attr_destroy(&attr);
    
    getchar();

}

主线程与子线程自动分离,子线程结束后,资源自动回收

#include <pthread.h>

int pthread_detach(pthread_t thread)

pthread_join()函数的替代函数,可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。该函数不会阻塞父线程。pthread_join()函数用于只是应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程

快速回收资源demo:

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *task(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("线程正在运行 %d\n", i++);
        sleep(1);

        if (i == 10)
        {
            // 退出线程
            pthread_exit(NULL);
        }
    }
}

int main()
{
    // 根据当前的栈大小创建线程
    pthread_t tid;
    pthread_create(&tid, NULL, task, NULL);

    // 快速设置分离属性
    pthread_detach(tid);
    printf("等待线程结束\n");

    // 回收线程资源
    pthread_join(tid, NULL); // 失效,因为线程自己回收资源!
    printf("线程资源回收\n");
    getchar();
}
相关推荐
vip45110 分钟前
Linux 经典面试八股文
linux
大霞上仙13 分钟前
Ubuntu系统电脑没有WiFi适配器
linux·运维·电脑
weixin_4426434231 分钟前
推荐FileLink数据跨网摆渡系统 — 安全、高效的数据传输解决方案
服务器·网络·安全·filelink数据摆渡系统
Karoku0661 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
为什么这亚子1 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
半桶水专家1 小时前
用go实现创建WebSocket服务器
服务器·websocket·golang
布值倒区什么name1 小时前
bug日常记录responded with a status of 413 (Request Entity Too Large)
运维·服务器·bug
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
孤客网络科技工作室1 小时前
VMware 虚拟机使用教程及 Kali Linux 安装指南
linux·虚拟机·kali linux
。puppy2 小时前
HCIP--3实验- 链路聚合,VLAN间通讯,Super VLAN,MSTP,VRRPip配置,OSPF(静态路由,环回,缺省,空接口),NAT
运维·服务器