线程学习笔记

线程

1. 线程概念

1.1 什么是线程

线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。譬如某应用程序设计了两个需要并发运行的任务 task1 和 task2,可将两个不同的任务分别放置在两个线程中。

1.2 线程如何创建而来?

程序启动,被操作系统OS创建->从入口main开始执行主线程->pthread_create创建子线程
主线程的重要性体现在两方面:
⚫ 其它新的线程(也就是子线程)是由主线程创建的;
⚫ 主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程。

1.3 线程特点

线程是程序运行的最小单位,真正运行的是线程。
系统新创建一个进程仅仅是一个容器,包含了线程运行所需要的数据结构、环境变量等信息。

多个线程共享全部系统资源,虚拟空间,文件描述符,信号处理等
各自又有自己的调用栈,寄存器环境,线程本地存储
在这里插入图片描述

1.4 线程与进程

  • 多进程和多线程需求分析
    多进程劣势:在这里插入图片描述

    多线程优势:在这里插入图片描述

1.5 并发和并行

  • 串行
    在这里插入图片描述

  • 并行

    在某一个时间段上存在多个任务被多个执行单元同时在运行着
    在这里插入图片描述
    在这里插入图片描述

  • 并发

    特点:时分复用
    不必等待上一个线程执行完之后再做下一个,它可以打断当前执行的任务.
    在同一个执行单元上,将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行),这就是并发运行
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

像IMX6ULL是单核,只能采用并发运行系统中的线程,而肯定不可能是串行,而事实上确实如此,并发运行依次轮询速度非常快,可以当作同时运行

2. 线程ID

  • 获取线程ID函数
#include <pthread.h>
pthread_t pthread_self(void);
  • 检查线程ID是否相等
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

3. 创建线程

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

在这里插入图片描述

示例:

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


static void *new_pthread(void *arg)
{
    printf("新线程 进程pid:%d,tid:%lu\n",getpid(),pthread_self());// pthread_self返回值是unsigned long int 
    return (void*)0;
}
int main(){
    pthread_t tid;
    int ret;

    ret =  pthread_create(&tid, NULL,  new_pthread, NULL);
    if(ret){
        perror("pthread error\n");
        _exit(-1);
    }

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

在这里插入图片描述

4. 终止线程

方法多种:
return、pthread_exit()、pthread_cancel()

#include <pthread.h>
void pthread_exit(void *retval);
//pthread_exit()函数将终止调用它的线程
//exit,_exit,_Exit函数将终止整个进程

测试子线程exit终止整个进程:

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

static void *new_pthread(void *arg){
    printf("starting new pthread\n");
    sleep(1);
    printf("end new pthread\n");
    exit(0);
}

int main(){

    pthread_t tid;
    int ret;

    ret = pthread_create(&tid, NULL, new_pthread, NULL);
    if(ret){
        perror("pthread_create error\n");
        exit(-1);
    }
    sleep(3);
    
    printf(" 子线程exit没有终止进程 \n");
    exit(0);
}

在这里插入图片描述

测试主线程结束,子线程继续执行:

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

static void *new_pthread(void *arg){
    printf("starting new pthread\n");
    sleep(1);
    printf("end new pthread\n");
    //exit(0);
}

int main(){

    pthread_t tid;
    int ret;

    ret = pthread_create(&tid, NULL, new_pthread, NULL);
    if(ret){
        perror("pthread_create error\n");
        exit(-1);
    }
    sleep(1);
    printf("main pthread end\n");
    pthread_exit(NULL);
    
    //printf(" 子线程exit没有终止进程 \n");
    exit(0);
}

在这里插入图片描述

5. 回收线程

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

主要由主线程回收子线程的资源,如果子线程还在运行,主线程的pthread_join就会阻塞等待子线程执行完毕,每调用一次只回收一个,有多个需要循环回收。

测试程序说明了子线程调用pthread_exit函数会将参数保存,pthread_join可以子线程传出的参数提出来

#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
关于解决临时变量t无法传出问题
一、struct Time t改为全局变量
二、t在主线程创建,创建子线程过程中t传入
*/
struct Time
{
    int time;
    int data; 
};
//struct Time t
static void *callback(void *arg){
    printf("callback run\n");
    
    struct Time *t = (struct Time*)arg;
    t->time = 110;
    t->data = 10;
    pthread_exit(t);
}

int main(){
    pthread_t tid;
    int ret;
    struct Time t;
    ret = pthread_create(&tid, NULL,callback,&t);
    if(ret)
    {
        perror("pthread_create");
        exit(-1);
    }

    void *ptr;
    pthread_join(tid, &ptr);
    //struct Time *pt = (struct Time*)ptr;
    printf("time:%d,data:%d\n",t.time,t.data);//这里用t,仍然需要join因为他会阻塞等待子线程传出参数
    exit(0);
}

在这里插入图片描述

6. 取消线程

向指定线程发送请求要求立即终止、退出
一组线程正在执行一个运算,一旦某个线程检测到错误发生,需要其它线程退出,取消线程这项功能就派上用场了

6.1 取消一个线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);
//只是提出请求,线程可以自己设置不被取消或者控制如何被取消
//被取消的话会立即退出,不会等待终止
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static void *callback(void *arg){
    printf("new pthread run\n");
    for(;;)
       sleep(1);
    return (void*)0;
}

int main(){
    pthread_t tid;
    void *ptr;
    int ret;


    ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret){
        perror("creat error\n");
        exit(-1);
    }

    sleep(1);

    ret = pthread_cancel(tid);
    if(ret){
        perror("cancel error\n");
        exit(-1);
    }

    ret = pthread_join(tid, &ptr);
    if(ret){
        perror("jion error\n");
        exit(-1);
    }
    printf("child thread : %ld\n",(long)ptr);//PTHREAD_CANCELED
    exit(0);
}

6.2 取消状态以及类型

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
/*会将调用线程的取消性状态设置为参数 state 中给定的值,并将线程之前的取消性状态保存
在参数 oldstate 指向的缓冲区中*/
int pthread_setcanceltype(int type, int *oldtype);
/*如果线程的取消性状态为 PTHREAD_CANCEL_ENABLE,那么对取消请求的处理则取决于线程的
取消性类型,该类型可以通过调用 pthread_setcanceltype()函数来设置,它的参数 type 指
定了需要设置的类型,而线程之前的取消性类型则会保存在参数 oldtype 所指向的缓冲区中*/
  • pthread_setcancelstate
    在这里插入图片描述
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static void *callback(void *arg){
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    for(;;)
    {
        printf("new pthread run\n");
        sleep(1);
    }
       
    return (void*)0;
}

int main(){
    pthread_t tid;
    void *ptr;
    int ret;


    ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret){
        perror("creat error\n");
        exit(-1);
    }

    sleep(1);

    ret = pthread_cancel(tid);
    if(ret){
        perror("cancel error\n");
        exit(-1);
    }

    ret = pthread_join(tid, &ptr);//子线程不处理取消请求,直到state状态改变
    if(ret){
        perror("join error\n");
        exit(-1);
    }
    printf("child thread : %ld\n",(long)ptr);//PTHREAD_CANCELED
    exit(0);
}
  • pthread_setcanceltype

在这里插入图片描述

6.3 取消点

在这里插入图片描述

如果一个线程没有可取消点,那么很容易不能被取消就会出现问题

6.4 线程可取消性的检测

#include <pthread.h>
void pthread_testcancel(void);

只要线程中调用此函数,就会产生一个取消点,当有取消请求被挂起,那么就可以被取消

7. 分离线程

如果不关心线程的返回状态、回收线程资源,可以是用pthread_detach函数,在线程终止时能够自动回收线程资源并将其移除

#include <pthread.h>
int pthread_detach(pthread_t thread);
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void *callback(void *arg){
    int ret;
    ret = pthread_detach(pthread_self());
    if(ret){
        perror("detach error\n");
        exit(-1);
    }
    printf("starr new thread\n");
    sleep(2);
    printf("end\n");
    pthread_exit(NULL);
}

int main(){
    pthread_t tid;
    int ret;

    ret = pthread_create(&tid, NULL, callback, NULL); 
    if(ret){
        perror("create error\n");
        exit(-1);
    }  
    sleep(1);
    void *ptr;
    if(pthread_join(tid, &ptr))
    {
        perror("join error\n");
    }
    pthread_exit(NULL);
}

在这里插入图片描述

8. 注册线程清理处理函数

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

当线程执行以下动作时,清理函数栈中的清理函数才会被执行:
⚫ 线程调用 pthread_exit()退出时;
⚫ 线程响应取消请求时;
⚫ 用非 0 参数调用 pthread_cleanup_pop()

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

static void cleanup(void *arg){
    printf("cleanup:%s\n",(char *)arg);
}

static void *callback(void *arg){
    printf("新线程执行·······\n");
    pthread_cleanup_push(cleanup, "one");
    pthread_cleanup_push(cleanup, "two");
    pthread_cleanup_push(cleanup, "three");

    pthread_exit((void *)0);

    pthread_cleanup_pop(0);//参数为0表示不执行清理函数
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);//push必须有对应的pop
}

int main(){
    pthread_t tid;
    int ret;
    void *ptr;

    ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret){
        perror("crest error\n");
        exit(-1);
    }

    sleep(1);
    ret = pthread_join(tid, &ptr);
    if(ret){
        perror("join error\n");
        exit(-1);
    }

    printf("main pthread over :%ld",(long)ptr);
    exit(0);
}

在这里插入图片描述

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

static void cleanup(void *arg){
    printf("cleanup:%s\n",(char *)arg);
}

static void *callback(void *arg){
    printf("新线程执行·······\n");
    pthread_cleanup_push(cleanup, "one");
    pthread_cleanup_push(cleanup, "two");
    pthread_cleanup_push(cleanup, "three");

    pthread_cleanup_pop(1);//参数为1表示移除顶层函数外,执行清理函数
    printf("~~~~~~~~~~~~~~~~~~\n");
    sleep(2);
    pthread_exit((void *)0);

    
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
}

int main(){
    pthread_t tid;
    int ret;
    void *ptr;

    ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret){
        perror("crest error\n");
        exit(-1);
    }

    sleep(1);
    ret = pthread_join(tid, &ptr);
    if(ret){
        perror("join error\n");
        exit(-1);
    }

    printf("main pthread over :%ld\n",(long)ptr);
    exit(0);
}

在这里插入图片描述

9.线程与信号

9.1 信号如何映射到线程

在这里插入图片描述

9.2 线程的信号掩码

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

9.3 向线程发送信号

#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
#include <signal.h>
#include <pthread.h>
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value)

9.4 异步信号安全函数

应用程序中涉及信号处理函数时必须要非常小心,因为信号处理函数可能会在程序执行的任意时间点被调用,从而打断主程序。接下来介绍一个概念—异步信号安全函数(async-signal-safe function)

所谓可重入,常见的情况是,程序执行都某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入,此时如果foo()能够正确的运行,而且处理完成之后,之前暂停的foo()也能正确运行,则说明它是可重入的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值