Linux线程

线程

线程概念

在这里插入图片描述
进程: 传统操作系统上的进程是pcb,操作系统通过pcb控制程序运行
线程: 但是Linux下线程使用pcb实现,Linux下pcb是线程,也叫轻量级进程(相较于传统进程更加轻量化— 同一个进程中的线程公用同一个虚拟地址空间);进程是线程组
进程是资源分配的基本单位—资源是分配给整个线程组的
线程是cpu调度的基本单位—cpu调度通过pcb调度程序的运行

线程之间的独有与共享:

独有共享
虚拟地址空间(代码段、数据段):mm_struct
寄存器文件描述符表:files_struct
信号屏蔽字信号处理方式:sighand_struct
errno当前工作路径
线程标识符用户ID,组ID

线程和进程优缺点对比及使用场景:

优点:

  • 同一个进程中的线程共享同一个虚拟地址空间
  • 线程间通信更加方便
  • 线程的创建与销毁相比于进程成本更低
  • 线程的调度成本要更低一点
  • 线程的执行力度要更加细致

缺点:

  • 线程间缺乏访问控制,编码难度更高
  • 线程健壮性更低
  • 性能损失

注意:除非对主程序安全性、稳定性要求特别高的程序使用多进程处理多任务;其它大多都是多线程

线程控制:

yum -y install man-pages  //我使用的centos7,无法使用man手册就运行前面的命令,安装支持posix函数的man手册

操作系统并没有提供创建线程的系统调用接口,因此大佬们封装了一个线程的接口库实现线程控制;意味着用户创建线程都使用的库函数(所以有时候我们说创建的线程是一个用户态线程,但是在内核中对应有一个轻量级进程实现线程程序的调度)

线程创建:线程间不存在父子关系

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

pthread_create   每个线程都有自己独有的一块空间,并且这块空间在进程的虚拟地址空间中
  pcb->pid   pcb->tgid     tid
  LWP      PID
轻量级进程id   进程ID   线程空间首地址

进程ID-线程组ID–tgid等于主线程的pid
在这里插入图片描述

  • 线程是库函数创建的,如果用户想要操作线程就必须拥有线程的操作句柄,而这个tid是线程地址空间的首地址,线程地址空间中保存了线程的数据,用户可以通过这个tid实现线程控制
  • 每个线程都有自己的独立空间,并且这些空间也是在进程的虚拟地址空间内
/*线程创建:                                                      
 * int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*stact_routine) (void *), void *arg);
 *  thread:用于获取被创建的新线程id
 *  attr:用于设置线程属性,通常置NULL
 *  start_routine:线程入口函数
 *  arg:传递给线程的参数,作为start_routine的实参传入
 * 返回值:0    失败:!=0-----errno*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thr_start(void *arg)
{
    while(1) {
        printf("i am normal thread---ptr:%s\n", (char*)arg);
        sleep(1);
    }
    return NULL;
}
int main (int argc, char *argv[])
{
    pthread_t tid;
    int ret;
    char *ptr = "leihoua~~~";
    ret = pthread_create(&tid, NULL, thr_start, (void*)ptr);
    if (ret != 0) {
        printf("thread create error\n");
        return -1;
    }
    
    while(1) {
        printf("i am main thread-----new thread id:%p\n", tid);
        sleep(1);
    }
    return 0;
}

线程终止:进程退出

在任意线程中不能使用exit退出,因为exit退出的也是整个进程
在普通线程中使用return退出(在 main函数中不能使用return退出,因为退出的是进程)

pthread_exit     主动退出
int pthread_cancel(pthread_t thread)   取消线程,退出的线程被动取消 thread:要取消的线程id

线程退出也是会称为(僵尸进程)的

线程等待:

等待指定的线程退出–获取退出线程的返回值,允许操作系统回收退出线程的资源

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.

一个资源创建成功后,默认有一个属性是joinable属性,处于joinable属性的线程退出后,不会自动回收资源,需要被其他线程等待
只有处于joinable属性的线程才能被等待,也必须被等待,因为这种线程退出后资源不会自动回收

线程分离:

#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.

分离一个线程,将线程的属性从joinable设置为detach属性;

处于detach属性的线程,退出后,自动回收资源;
处于detach属性的线程,退出后,不需要被等待;
分离一个线程的前提就是用户对线程的退出返回值,根本不关心;

pthread_detach
线程分离可以是任意线程在任意时刻进行分离(线程一旦被创建直接分离;线程入口函数中分离自己)


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

void *thr_start(void *arg)
{
    //pthread_self(void)
    //  获取调用线程ID
    pthread_detach(pthread_self());
    sleep(5);
    pthread_exit("nihaoa~~");
    while(1) {
        printf("i am ordinary thread\n");
        sleep(1);
        //void pthread_exit(void *retval);
        //退出调用线程,谁调用,谁退出
        //retval:   线程退出返回值
        //pthread_exit(NULL);
    }
    return NULL;
}
int main (int argc, char *argv[])
{
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, thr_start, NULL);
    if (ret != 0) {
        printf("thread create error\n");
        return -1;
    }
    //int pthread_cancel(pthread_t thread);
    //  取消线程,退出的线程被动取消
    //  thread:    要取消的线程id
    //pthread_cancel(tid);
    
    //int pthread_join(pthread_t thread, void **retval);
    //  阻塞等待指定线程退出
    //  thread:    线程id
    //  retval:    用于获取线程退出返回值
    
    //int pthread_detach(pthread_t thread);
    //  分离指定的线程
    pthread_detach(tid);

    char *ptr = NULL;
    pthread_join(tid, (void**)&ptr);
    printf("ordinary thread exit val:%s\n", ptr);
    while(1) {
        printf("i am main thread\n");
        sleep(1);
    }
    return 0;
}

线程安全:

多个线程是否对同一个临界资源进行了不受保护的非原子操作
线程安全就是多个线程对临界资源进行操作,而不会造成数据二义性,反之是不安全

如何实现线程安全(同步与互斥)

同步: 临界资源访问的时序可控—保证访问的合理性
互斥: 临界资源同一时间的唯一访问—保证访问的安全性

如何实现互斥

互斥锁mutex

模拟一个黄牛买票程序
原理: 原子操作的一个只具有0/1的计数器
互斥锁使用步骤:
pthread_mutex_t     定义互斥锁变量
pthread_mutex_init    初始化互斥锁
pthread_mutex_lock    加锁
pthread_mutex_unlock   解锁
pthread_mutex_destroy   销毁互斥锁

死锁:

因为多方抢夺锁资源,但是因为抢夺推进顺序不当引起死锁导致相互僵持,产生程序无法推进卡死
产生必要条件:

  • 互斥条件—指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。(我加了锁,别人就不能再加)
  • 不可剥夺条件—指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。(我加的锁,别人不能解)
  • 请求与保持条件—指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。(拿到第一个锁后,去请求第二个锁,但是第二个锁获取不到,第一个锁也不释放)
  • 环路等待条件—指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
    在这里插入图片描述
    如何预防出现一个死锁: 破坏必要条件
    如何避免/处理产生一个死锁: 银行家算法死锁检测算法

同步的实现:条件变量

同步的实现:等待与唤醒功能
条件不满足则陷入等待,其它线程促使条件满足后,唤醒等待
我想吃面—>没有面则需要等待做出来
厨师做面—>做出面后唤醒我的等待
模拟实现吃面做面
在这里插入图片描述
在这里插入图片描述
pthread_cond_t     定义条件变量
pthread_cond_init    初始化条件变量
pthread_cond_wait     等待
pthread_cond_signal    唤醒
pthread_cond_destroy   销毁条件变量

#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);//限时等待,超时后报错返回
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);//阻塞死等
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有等待
int pthread_cond_signal(pthread_cond_t *cond);//至少唤醒一个等待
int pthread_cond_destroy(pthread_cond_t *cond);

cond:条件变量
attr:条件变量属性,通常置NULL
mutex:互斥锁
abstime:超时等待时间

条件变量是搭配互斥锁一起使用的:

  1. 因为条件变量实现同步只提供了等待与唤醒功能,并没有提供条件判断的功能,因此条件判断需要用户实现,但是条件的操作是一个临界资源的操作,因此需要受保护,需要在条件判断之前加锁;
  2. 如果加锁成功后,因为条件不满足而陷入休眠,就会导致卡死(另一方因为无法获取锁,而导致无法促使条件满足),因此需要在休眠之前解锁,并且解锁与休眠必须是原子操作
  3. 被唤醒之后,即将对临界资源进行了操作(吃面的人被唤醒就即将开始吃面)。但是吃面之前需要进行保护加锁
  4. 所以pthread_cond_wait集合了三步原子操作:解锁—>休眠等待—>被唤醒后加锁

条件判断需要循环判断 —防止多次消费/多次生产导致数据二义逻辑混乱
不同的角色需要等待在不同的条件变量上 —防止角色唤醒错误导致阻塞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值