08多线程

1、线程

概念:

  • 线程(Thread)是操作系统能够进行运算调度的最小单位,是进程中的一个实体,是 CPU 调度和分派的基本单位。与进程相比,线程更轻量级,线程之间可以共享进程的资源,但每个线程拥有自己的独立栈空间。线程是应用层的概念,它可以与其他线程共享同一进程的地址空间、文件描述符等资源,也可以有自己的一套资源。
    在这里插入图片描述
  • 左边是一个标准的进程,它自己拥有一套完整的资源(内存空间、文件等),这些资源全部由PCB统一管理,这一整套的数据结构,以及他们的动态变化就是一个进程
  • 右边是两个PCB共享很多资源,一个PCB对应着系统中的一个任务,是系统调度的对象。系统在调度的时候并不关心这个PCB是独立的还是共享的。因此右边的进程可以获得更多的CPU资源相比左边的进程

2、线程的创建

在这里插入图片描述

  • 1、线程例程指的是:如果线程创建成功,该线程就会立即去执行的函数
  • 2、POSIX线程库所有的API接口对返回值的处理原则都是一致的:成功返回0,失败返回errno
  • 3、线程属性如果为NULL,则会创建一个标准的线程

练习:创建一个标准的线程:

注意:编译时需要链接pthread的静态库gcc pthread.c -o pthread -lpthread,否则会报错,如下:
在这里插入图片描述

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

void* func(void *arg)
{
    while(1)
    {
        printf("这是一个线程\n");
        sleep(1);
    }
}

int main()
{
    //线程ID
    pthread_t pid;
    //创建新的线程
    pthread_create(&pid, NULL, func, NULL);

    while(1)
    {
        printf("这是主函数\n");
        sleep(1);
    }
        
    return 0;
}

3、获取和设置线程的属性

获取线程的属性:
在这里插入图片描述
设置线程的属性:
在这里插入图片描述
以上API接口都是针对线程属性的

4、初始化与销毁线程属性

头文件:
		#include <pthread.h>
函数原型:
		int pthread_attr_init(pthread_attr_t *attr);//初始化
		int pthread_attr_destroy(pthread_attr_t *attr);//销毁该线程属性变量
参数:
		attr:需要初始化或者销毁的属性变量
返回值:
		成功返回:0
		失败返回:1
#include <pthread.h>

// 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);

// 销毁线程属性
int pthread_attr_destroy(pthread_attr_t *attr);

5、如何设置分离属性

一条线程如果是接合的,意味着这条线程在退出时不会自动释放自身的资源,而会成为僵尸进程,同时意味着该进程的退出值可以被其他线程获取。因此不需要线程退出值的话,最好把线程设置为分离状态,以保证线程不成为僵尸进程(默认情况下线程是接合的)

在这里插入图片描述

线程跟进程类似,在缺省的状态下退出之后,会变成僵尸进程,并且保留退出值。其他线程可以通过相关的API接和该线程,使其资源被回收,还可以获取其退出值。在这里插入图片描述
接和指定线程:

在这里插入图片描述
注意:

  • 1、如果线程退出时没有退出值,那么retval可以指定为NULL
  • 2、pthread_join()指定的线程如果还在运行,那么会阻塞等待
  • 3、pthread_tryjoin()指定的线程如果还在运行,会立即出错返回

练习:
1、创建线程,再去接和该线程获取其退出值

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

void* func(void *arg)
{
    while(1)
    {
        printf("这里是线程\n");
        sleep(3);
        break;
    }
    int *p = malloc(sizeof(int));
    *p = 1024;

    //推出本线程,并设置返回值的地址
    pthread_exit((void *)p);//返回的内存地址应该是一个堆空间的内存
    return 0;
}

int main()
{
    //定义线程的ID;
    pthread_t pid;

    //创建新线程
    pthread_create(&pid, NULL, func, NULL);

    int *retval;

    //接合ID为pid的线程,阻塞等待其退出
    pthread_join(pid, (void *)&retval);
    printf("接和成功,退出值为:%d\n", *retval);

    return 0;
}

2、设置该线程为分离属性,看看能否在接合该线程

#include "stdio.h"
#include "pthread.h"
#include "unistd.h"
#include "stdlib.h"

void* func(void *arg)
{
    while(1)
    {
        printf("这是线程\n");
        sleep(3);
        break;
    }
    int *p = malloc(sizeof(int));
    *p = 1024;
    //线程退出值
    pthread_exit((void *)p);

    return 0;
}


int main()
{

    pthread_attr_t attr;
    //初始化线程属性
    pthread_attr_init(&attr);
    //设置线程为分离属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    pthread_t pid;
    //创建线程
    pthread_create(&pid, &attr, func, NULL);

    int *retval;
    //接合线程
    if(pthread_join(pid, (void *)&retval) == 0)
    {
        printf("接合成功\n");
        return 0;
    }
    else
    {
        printf("接合失败\n");
    }

    return 0;
}

如果线程被设置为分离属性之后,是不可以在去接合该线程获取退出值

6、线程的调度策略:

在这里插入图片描述

  • SCHED_FIFO:先进先出策略,线程一直运行直到自愿放弃 CPU,适用于实时任务。
  • SCHED_RR:轮转策略,与 FIFO 类似,但是线程有时间片的概念,适用于实时任务。
  • Linux默认调度策略为SCHED_OTHER

    • 其静态优先级必须设置为0。
    • 处于0优先级的线程,按照动态优先级被调度,而动态优先级起始于线程的nice值。每当一个线程处于就绪状态但被调度器无视时,其优先级会增加一个单位,这样能保证线程间竞争CPU的公平性
    • 动态优先级:“动态”表示此优先级会随着线程的表现而变化。如果一条线程是CPU消耗型,这种线程就会一直粘着CPU,这样的线程的动态优先级就会被降低。另一类线程被称为IO消耗型,这类线程大部分时间都在睡眠,调度器就会慢慢提高它的优先级,让他有更加高的响应速度。

获取和设置线程栈和警戒区的大小:

在这里插入图片描述

  • 如果发现一条线程的栈可能会溢出,那么也许需要使用该函数来增大栈空间,但事实上不需要这么做。因为警戒区指的是没有任何访问权限的内存,用来保护相邻的两个线程的栈空间不会相互践踏

7、发送请求给线程

在某个时刻不能等待线程自然死亡,而需要勒令其马上结束,此时可以给线程发送一个取消请求,让其中断执行退出。
在这里插入图片描述

  • 当线程收到一个取消请求时,它的表现将会取决于两个东西:

  • 1、当前取消的状态:

    • PTHREAD_CANCEL_ENABLE 使能 (允许取消。默认值)
    • PTHREAD_CANCEL_DISABLE 失能 (不允许取消)
  • 2、当前取消的类型:

    • 延时响应:等待线程遇到取消点响应取消请求
    • 立即响应

注册取消函数:

在这里插入图片描述
练习:

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

void test(void * arg )
{
    printf("收到取消请求, 参数arg : %d \n" , *(int *)arg);
}

void* func(void *arg)
{
    int *p = malloc(sizeof(int));
    *p = 1024;

    pthread_cleanup_push(test, p);

    while(1)
    {
        printf("这里是线程\n");
        sleep(3);
        break;
    }

    //线程退出处理函数弹出 0不执行  非01执行
    pthread_cleanup_pop(0);
    //推出本线程设置退出值
    pthread_exit((void *)p);
}

int main()
{
    //创建线程
    pthread_t pid;
    pthread_create(&pid, NULL, func, NULL);

    //退出线程
    sleep(1);
    pthread_cancel(pid);
    printf("等待线程退出\n");
    
    int *retval;
    
    if(pthread_join(pid, (void *)&retval))
    {
        printf("结合失败\n");
        return 0;
    }
    printf("结合成功");
    return 0;
}

以上是关于线程的基本概念、创建、属性设置、调度策略以及取消操作的介绍和示例代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值