10操作系统——线程的相关属性

Posted weixin_45981798

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10操作系统——线程的相关属性相关的知识,希望对你有一定的参考价值。

目录

一、线程调度

1、三种调度算法

(1)SCHED_FIFO(先进先出的排队方式调度)

(2)SCHED_RR实时调度策略,时间片轮转

(3)SCHED_OTHER 分时调度策略(linux默认)

 2、pthread_attr_setschedpolicy/pthread_attr_getschedpolicy(获取、设置线程的调度策略)

 二、线程栈合警戒区

1、pthread_attr_setstacksize/pthread_attr_getstacksize(获取、设置线程栈大小、警戒区大小)

 2、各个线程相对独立的栈

三、线程退出

1、pthread_exit(线程自己退出)​编辑

         2、pthread_cancel(线程取消)(请线程退出)

3、创建一个线程,在1s后发送取消线程的请求,线程终止

一、线程调度

1、三种调度算法

(1)SCHED_FIFO(先进先出的排队方式调度)

静态优先级设置为1-99,则线程如果处于就绪态,就能立即抢占静态优先级为0的普通线程。

a、就绪态时,放入优先级队列的队尾位置

b、被更高优先级的线程抢占之后,会被放入优先级队列的队头位置,当所有优先级比他高的线程不再运行后,就恢复运行

c、调用sched_yield()后,会被在优先级队列的队尾位置

总结:该线程会一直运行直到发送I/O请求,或者被更高优先级线程抢占,或者调用sched_yield()主动让出CPU

(2)SCHED_RR实时调度策略,时间片轮转

与SCHED_FIFO类似,区别在于设置了时间片,当时间片耗光时,会被在优先级队列的队尾位置,可以用sched_rr_get_interval( )来获得时间片的具体数值。

(3)SCHED_OTHER 分时调度策略(linux默认)

静态优先级必须设置为0

处于 0 优先级别的这些线程按照所谓的动态优先级被调度,而动态优先级起始于线程的 nice 值,且每当一个线程已处于就绪 态但被调度器调度无视时,其动态优先级会自动增加一个单位,这样能保证这些线程竞争 CPU 的公平性。

 2、pthread_attr_setschedpolicy/pthread_attr_getschedpolicy(获取、设置线程的调度策略)

 二、线程栈合警戒区

1、pthread_attr_setstacksize/pthread_attr_getstacksize(获取、设置线程栈大小、警戒区大小)

 2、各个线程相对独立的栈

线程的栈可能溢出,需要增大栈空间,因为有警戒区,故不需要这么做,警戒区是没有任何访问权限的内存,用来保护相邻的两条线程的栈空间不被彼此践踏。

三、线程退出

1、pthread_exit(线程自己退出)

 2、pthread_cancel(线程取消)(请线程退出)

 某个时刻不能等某个线程“自然死亡”(例如在while(1)中),需要令其马上结束,可以给线程发送一个取消请求,让其中断执行退出。

而当线程收到一个取消请求时,他将会如何表现取决于两个东西:

一是当前的取消状态:

         PTHREAD_CANCEL_ENABLE 使能 (允许取消。默认值)         PTHREAD_CANCEL_DISABLE 失能 (不允许取消)

二是当前的取消类型:

        延时响应 等待线程遇到取消点时响应取消的请求

        立即响应

3、创建一个线程,在1s后发送取消线程的请求,线程终止

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

void * func (void * arg)

    int * p = calloc(1,4);
    while(1)
    
        printf("这里是func线程,线程ID :%ld \\n" , pthread_self() );
        sleep(1);
    

    *p = 1024 ;
    // 退出本线程并设置返回值的地址(返回了num 的地址)
    pthread_exit((void *)p); //返回的内存地址应该时一个堆空间


int main(int argc, char const *argv[])

    
    // 创建线程
    pthread_t t_id  = -1 ;
        
    pthread_create( &t_id , //新线程ID号
                    NULL , // 线程属性, NULL 默认属性
                    func,  // 线程需要执行的例程(新线程需要执行的任务《函数》) 
                    NULL ); // 线程的参数

    printf("t_id : %ld\\n" , t_id) ;

    printf("这里是主函数,线程ID :%ld \\n" , pthread_self() );

    int * retval ;
    int ret_val = 0 ;

    sleep(1);
    pthread_cancel( t_id );

    // 阻塞等待接合线程
    printf("等待 function 线程退出:\\n");
    if( ret_val = pthread_join( t_id , (void*)&retval))
    
        fprintf(stderr , "接合失败:%s\\n" , strerror(ret_val));
    
    printf("结合线程成功, 退出值为:%d\\n" , *retval);

    // 尝试接合线程  (非阻塞)
    // int ret_val = 0 ;
    // if( ret_val = pthread_tryjoin_np( t_id , (void*)&retval))
    // 
    //     fprintf(stderr , "接合失败:%s\\n" , strerror(ret_val));
    // 

    return 0;

上述例子可以取消线程,但是没办法结合线程的退出值

复习多线程相关知识

多线程

操作系统相关

冯诺依曼体系结构

输⼊单元:包括键盘,⿏标,扫描仪,写板等
寄存器:内存
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等

总结:所有设备都只能直接和内存打交道

进程

概念

系统分配资源的最⼩单位,⼀个任务就是⼀个进程

进程的组成

  1. PID:进程的唯⼀身份标识。
  2. 进程状态包括: 新建状态 就绪状态 运⾏状态 阻塞状态 销毁状态
  3. 优先级:决定进程的执⾏顺序。
  4. 记账信息:为了保证进程执⾏的相对公平。
  5. 上下⽂:保存本次的执⾏状态,以便下次继续执⾏,整个过程就称之为⼀个上下⽂。
  6. ⼀组内存:指定进程需要使⽤的资源。

进程状态

  • 就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
  • 运行:进程处于可运行的状态,且CPU时间片轮转到该进程,该进程正在执行代码,则该进程处于运行状态。
  • 阻塞:进程不具备运行条件,正在等待某个事件的完成

时间片

任务执⾏的⼀⼩段时间叫做时间⽚,任务正在执⾏的状态叫做运⾏状态,任务暂停叫做就绪状态,就绪状态的任务要等待下⼀个时间⽚。

并发与并行

  • 并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进, 称之为并发(只有一个资源,轮流执行就叫并发运行)
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(所有的应用一起运行,就叫并行运行)

内核态与用户态

  • 操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。
  • 用户程序的权限最低,称为用户态

线程

概念

系统执⾏的最⼩单位,它被包含在进程之中,是进程中的实际运作单位。

线程优势

线程的创建和消耗⽐进程成本低很多,效率更⾼,⽽且同⼀个进程中的多个线程是可以共享资源⽂件的,⽽进 程和进程之间是不能共享资源的,因此这就是线程的诞⽣的意义。

共享资源

线程间可以共享的资源:

  1. 内存可以共享
  2. 打开的⽂件可以共享

线程间不可⽤共享的资源:

  1. 线程的上下⽂、状态、优先级、记账信息不共享。
  2. 每个线程有⼀个栈空间不共享。

进程 vs 线程

1.进程是资源分配的最小单位,线程是系统调度的最小单位
2.一个进程中至少包含一个线程,线程是依附线程存在的,而进程的实际执行单位是线程
3.进程之间不能共享资源,而线程之间可以共享资源

线程的创建方式

  1. 继承 Thread 重写 run ⽅法实现;
  2. 实现 Runnable 接⼝重写 run ⽅法实现;
  3. 实现 Callable 接⼝重写 call ⽅法实现。

线程的构造函数

  1. 定义线程名称
  2. 定义线程分组
  3. 定义线程的任务

常见属性

  1. 定义优先级(5) 1-10
  2. 设置线程名称
  3. 定义线程类型
    a)守护线程
    b)用户线程

常用方法

  1. 启动 start / run
  2. 休眠 sleep
  3. 等待执行完 join
  4. 线程通讯 wait / notify / notifyAll | LockSupport park / unpark
  5. 出让cpu执行权:yiled

注意事项:
a)wait 要配合 synchronized 使用
b)wait 和synchorized 必须要保证是一个对象

start VS run

  1. run 为普通⽅法,start 启动线程的⽅法;
  2. run 可以执⾏多次,⽽ start 只能执⾏⼀次;
  3. start 会开启新线程执⾏,⽽ run 使⽤当前线程执⾏。

wait VS sleep
⼆者的相同点:

  1. 都可以让线程休眠;
  2. 都可以响应 interrupt 的响应。

⼆者的不同点:

  1. wait 必须在 synchronized 中使⽤,⽽ sleep 却不⽤;
  2. sleep 是 Thread 的⽅法,⽽ wait 是 Object 的⽅法;
  3. sleep 不释放锁,wait 释放锁;
  4. sleep 有明确的终⽌等待时间,⽽ wait 有可能⽆限期的等待下去;
  5. sleep 和 wait 产⽣的线程状态是不同的,sleep 是 TIMED_WAITING 状态,⽽ wait 是 WAITING 状态。

线程终止

  1. 使用全局变量
  2. 使用 interrupte() 终止线程
  3. stop 【已过时】

isInterrupted VS interrupted

  • interrupted:静态⽅法,判断当前线程的中断标志位是否设置,调⽤后清除标志位;
  • isInterrupted:实例⽅法,判断对象关联的线程的标志位是否设置,调⽤后不清除标志位。

线程状态图

线程安全问题

导致线程不安全的原因

  • ① 抢占式执⾏
  • ② ⾮原⼦操作
  • ③ 内存可⻅性
  • ④ 指令重排序
  • ⑤多个线程同时修改同⼀个变量

线程安全解决方案

  1. volatile :解决内存可见性问题和禁止指令重排序
  2. 加锁
    a)synchronized
    b)Lock
  3. ThreadLocal

synchronized

实现原理

  1. 在操作系统层面来说,它是使用互斥锁来实现的。
  2. 在JVM层面来说,使用 synchronized 修饰代码的时候会在编译之后在代码的前面和后面加上监视器锁。
  3. 在 Java 层面来说,他就是将锁信息存在对象头中,每次判断线程 id 和对象头中的锁 id 是否相同,相同则表示为拥有者。

执行流程 / 锁优化

(JDK1.6 后)
四个阶段:

  1. 无锁:在没有人访问的时候是无锁的
  2. 偏向锁:第一次有人访问,升级为偏向锁,在对象头中把线程 id 记录到对象头中
  3. 轻量级锁:如果再次访问的对象不是锁的拥有者,则自旋变为轻量级锁
  4. 重量级锁:多次自旋还没有得到锁,升级为重量级锁

Lock

lock.lock(); 
try 
 
 finally  
	lock.unlock() 

  1. lock 放在 try 上⾯;
  2. finally ⾥⾯⼀定要释放锁。

synchronized VS Lock

  1. synchronized 是 JVM 提供的解决方案
  2. synchronized 和 Lock 修饰的代码块范围不同,Lock 只能修饰代码块,⽽ Synchronized 可以修饰⽅法、静态⽅法、代码块;
  3. synchronized 无需是手动加锁和释放锁,而 Lock 需要
  4. synchronized 是非公平锁,而 Lock 既可以是公平锁也可以是非公平锁

死锁

定义

死锁是指两个或两个以上的线程在执⾏过程中,由于竞争资源或者由于彼此等待⽽造成的⼀种阻塞的现 象。

条件

  1. 互斥条件:⼀个资源只能被⼀个线程占有,当这个资源被占⽤之后其他线程就只能等待。
  2. 请求拥有条件:线程已经拥有了⼀个资源之后,有尝试请求新的资源。
  3. 不可剥夺条件:当⼀个线程不主动释放资源时,此资源⼀直被拥有线程占有。
  4. 环路等待条件:产⽣死锁⼀定是发⽣了线程资源环形链。

解决方案

通过破坏条件 ② 或者 条件 ④ 来解决死锁的问题

线程池

使用池化技术来管理线程

优点

  1. 线程数量和任务数量可控
  2. 线程池可以友好的拒绝任务
  3. 可以复用线程
  4. 线程池拥有更多功能,比如执行定时任务。

创建方式

  1. 创建固定线程数的线程池;
  2. 创建带缓冲的线程池;
  3. 创建延迟任务线程池;
  4. 创建单个延迟任务线程池;
  5. 创建单个线程池;
  6. 根据当前 CPU 和任务创建线程池(JDK 1.8);
  7. 原⽣创建线程池的⽅式 ThreadPoolExecutor;

ThreadPoolExecutor

七大参数

  1. 核心线程数
  2. 最大线程数
  3. 最大存活使劲啊
  4. 存活时间的时间单位
  5. 任务队列
  6. 线程工厂(设置优先级、命名规则等)
  7. 拒绝策略

执行流程

添加一个任务

  1. 核心线程数未满 —— 创建一个线程执行任务
  2. 检测任务队列未满 —— 将任务存放到队列
  3. 最大线程数是未满 —— 创建线程执行任务
  4. 拒绝策略

拒绝策略

JDK 提供 4 种

  1. 默认执行策略,不执行任务抛出异常
  2. 使用主线程执⾏来执行任务
  3. 放弃任务
  4. 放弃最老任务

自定义拒绝策略

线程池的状态

  • RUNNING -> SHUTDOWN:当调⽤了 shutdown() 后,会发⽣这个状态转换,这也是最重要的;
  • (RUNNING or SHUTDOWN) -> STOP:当调⽤ shutdownNow() 后,会发⽣这个状态转换,这下 要清楚 shutDown() 和 shutDownNow() 的区别了;
  • SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING;
  • STOP -> TIDYING:当任务队列清空后,发⽣这个转换; TIDYING -> TERMINATED:这个前⾯说了,当 terminated() ⽅法结束后。

ThreadLocal

线程级别的私有变量

常⽤⽅法

  1. set
  2. get
  3. remove
  4. initialValue
  5. withInitial

典型使⽤场景

  1. 解决线程安全问题;
  2. 线程级别的数据传递。

注意事项

  1. ThreadLocal 使用的时候必须要进行 remove ,否则会造成脏读、内存溢出的问题
  2. 如果 ThreadLocal 默认情况下线程是得不到父线程的数据的【解决⽅案:InheritableThreadLocal】

实现原理

Thread -> ThreadLocal -> ThreadLocalMap -> Entry -> key,value

ThreadLocal 哈希处理

ThreadLocalMap 使⽤的是开放地址法来解决哈希冲突的,不像 HashMap使⽤的是链表法来解决哈希冲突。

单例模式

  1. 将构造函数设置成私有
  2. 创建一个私有的对象
  3. 创建一个公共提供单例对象的方法

DCL双重效验锁

锁策略

乐观锁

它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突

CAS

”比较并交换“,是乐观锁的一种机制

组成:
V:内存值
A:旧值
B:新值

机制:
V == A ?
true(没有并发冲突) -> V = B :
false(并发冲突) -> A = B , continue
如果内存中的值等于旧值,则没有并发冲突,直接将旧值替换为新值即可
如果内存中的值不等于旧值,则进行自旋,将自己的旧值修改为内存值,然后再次进行比较

ABA问题


解决ABA问题
增加版本号,每次修改之后更新版本号

悲观锁

它认为通常情况下会出现并发冲突,所以它一开始就会加锁

synchronized就是悲观锁

共享锁/非共享锁(独占锁)

共享锁:一把锁可以被多个程序拥有,这就叫共享锁

非共享锁:一把锁只能被一个线程拥有,这就叫非共享锁

共享锁有哪些?读写锁中的读锁

非共享锁有哪些?synchronized锁

读写锁

定义:就是将一把锁分为两个,一个用于读数据的锁(也叫做读锁),另一把锁叫作写锁。

特点:读锁是可以被多个线程同时拥有的,而写锁则只能被一个线程拥有

java中的读写锁:ReentrantReaderWriterLock(默认是非公平的,true-公平)

读写锁的优势:锁的粒度更加小,性能也更高

读写锁中的读锁和写锁是互斥的

公平锁和非公平锁

公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致,就叫做公平锁

非公平锁:锁的获取顺序和线程获取锁的前后顺序无关,就叫做非公平锁(默认所策略)

非公平锁的优点:性能比较高

公平锁的优点:执行是有序的,所以结果也是可以预期的

自旋锁

通过死循环一直尝试获取锁

synchronized (轻量级锁) 是自旋锁

可重入锁

当一个线程获取到一个锁之后,可以重复的进入

代表:synchronized,Lock

JUC 包

  1. 可重入互斥锁 ReentrantLock
  2. 信号量 Semaphore -》 实现限流 acquire() 和 release()
  3. 计数器 CountDownLatch -》 保证一组线程同时完成某个任务 await() 和 countDown()
  4. 循环屏障 CyclicBarrier -》 可以多次进行计数

HashMap、ConcurrentHashMap 、Hashtable 区别

  1. HashMap 是非线程安全的容器,他在 JDK1.7 会造成死循环,JKD1.8 会造成数据覆盖;Hashtable 和 ConcurrentHashMap 都是线程安全的。
  2. Hashtable 实现线程安全的手段比较简单,它是再 put 方法上整体加了一把锁,使用 synchronized 修饰,因此性能不高,所以使用频率比较低;而 ConcurrentHashMap 是 HashMap 在多线程下的替代方案,它在 JDK 1.7 的时候使用的 Lock 加分段锁的方案来实现线程安全问题的保障的,而在 JKD 1.8 的时候使用了大量的 CAS、volatile 来实现线程的,并且在 JDK 1.8 的时候读取的时候不加锁(读取的数据可能不是最新的,因为读取和写入可以同时进行),只有在写的时候才加锁。

以上是关于10操作系统——线程的相关属性的主要内容,如果未能解决你的问题,请参考以下文章

9操作系统——线程的相关属性

Linux之线程线程控制线程属性

Linux多任务编程——线程

linux服务器开发二(系统编程)--线程相关

Linux相关问题整理

Linux线程操作以及相关知识