165-Linux的多线程(中)

上期疑问(为什么打印出6个fun)

在这里插入图片描述
在这里插入图片描述
本应该只能打印五次fun run,但是本次程序运行未能正常结束,程序退出,未处理这些线程,粗暴解决,主线程和子线程可能存在对缓冲区多刷了一次,很可能是主程序最后对此刷新了一次,我们对此不做处理,因为此程序就是异常结束。本次异常情况很少出现。

在这里插入图片描述
线程函数相同或不同根据自己的需求

线程用到的头文件和接口
在这里插入图片描述

从操作系统来讲线程

在这里插入图片描述
纯粹的用户级:底下是内核空间,上层是用户空间,在内核空间,就认为创建1条执行路径,在用户空间通过线程库创建出不同的线程,属于用户级的线程,用户自己去创建的线程,内核没有参与也并不知道创建了多少个线程。这种特点是创建开销小,栈空间可以自由来控制,可以创建很多个线程,不需要内核的参与
缺点是无法处理多处理器的资源因为在内核的表现只有一条执行路径,操作系统无法放在不同处理器上执行。

纯粹的内核级:操作系统内核本身要支持创建线程,内核必须留出创建线程的接口,内核帮用户去创建线程,对个线程的处理和切换均由内核去完成,内核感知每个线程的存在,可以分别把多个线程放在多个不同的处理器上并行!缺点是创建开销大,成本高。
优点是可以利用多处理器的资源,内核直接管理

如果操作系统本身不支持创建线程,那么我们只能使用纯粹的用户级

组合:前两者的组合。可以在用户空间创建多个线程,内核空间也可以创建多个路径,多对多的关系,可以做到并行,处理器总是有限的,创建内核的线程数与处理器的数接近就可以了,再多还是时间片的轮转

不同平台,不同语言,处理方式的

Linux系统支持创建内核级线程
Go语言支持类似组合线程的方法去实现

不同场景下各自有各自的优点

在这里插入图片描述
task_struct (PCB)
在这里插入图片描述
把pid理解为线程的id
主线程(第一个线程)id作为对外呈现的pid,子线程pid理解为线程id

查看多线程的id

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
主线程id作为pid
第三列数字是线程id

ps -eLf查看线程的id

在这里插入图片描述
在这里插入图片描述
Linux内核级线程

在各个平台,调动该平台创建线程的方法

线程同步

在这里插入图片描述

指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。线程同步的方法有四种:互斥锁、信号量、条件变量、读写锁。

信号量进行线程同步
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
打印多次结果都是5000,因为已经进行了限制操作

互斥锁

在这里插入图片描述

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);//互斥锁进行初始化,属性一般为空

int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁,锁住资源,阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁

如同 试衣间一样 是互斥性的
在这里插入图片描述
在这里插入图片描述
先加锁,然后对val执行++,如果别人加锁了正在++,则你的加锁就阻塞住了,保证同一时刻只有一个线程对val++

在这里插入图片描述
运行很多次,均为5000

多线程中要使用线程安全的函数

线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么就说这些线程是安全的。
要保证线程安全需要做到:
1、对线程同步,保证同一时刻只有一个线程访问临界资源。
2、在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
在这里插入图片描述

举个例子:
对字符串进行分割
两个线程分别对两个字符串进行分割

正常期望应该是 主线程打印a 子线程打印1 打印的值是有序的

strtok方法 分割字符串
在这里插入图片描述
在这里插入图片描述

参数为 分割的字符串,分割的符号
在这里插入图片描述
沿着原来分割后的位置继续分割

在这里插入图片描述
在这里插入图片描述
运行程序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

为什么是这种情况?

调动strtok,循环在使用,执行一次分割一次,下一次再分割,没有传任何参数,还是沿着上一次切割后的位置继续切割,说明strtok里有记录分割的位置,strtok内部是使用全局变量或者静态变量去实现来记录当前分割的位置。在多线程环境,如果都调用strtok ,是共享的!只有一份。两个线程都去访问调用。主线程第一次分割完a出来,然后指针指向b,下次分割,从b位置开始,可是,现在是两个线程都在使用,当主线程将a打印出来的时候,指针指向b,然后睡眠1秒钟,此时线程fun也要去进行分割,它是重新传参数(arr)了,允许重新传。更新了strtok指针的值 ,一分割,返回1,然后更新指针,指针指向2的位置,注意:strtok只有一份,这里的指针都是同一个指针哦,现在指针指向的是2位置,然后主线程进行strtok分割的时候,是对2的位置进行分割了,分割出2,也有可能主线程正在分割2,fun线程也进行分割,都得出2。并发运行,打印出2个2

strtok不适应多线程的环境

可以在多线程中正常使用的版本:
在这里插入图片描述
增加 指向的地址 的 参数
带下划线_r的就是可以在多线程中安全使用的函数

解析strtok方法
在这里插入图片描述
传入的空格是分隔符
buff起始位置指向a,遇见空格,然后把空格换成\0,然后把a的地址返回,s=strtok();s指向a的地址,遇到\0,就是一个字符串了,然后循环再次调用strtok,又遇到s=strtok();strtok内部有一个指针,指向的是b的起始位置,然后参数没有传buff,就是沿着b的位置进行分割,然后遇到空格,把空格改为\0,buff在函数中是不能消失的哦。

两个线程有可能都要去改空格为\0,一个线程指向b的位置了,另一个线程也调动strtok了,strtok被执行2遍,如果是同时去执行,都打算去改,访问同一块空间b,都要分割b,第一个线程把b后的空格改为\0,另一个线程进去发现空格已经是\0了,认为已经结束了,不更改了,打印了一个b,退出了,指针置空。所以打印了两个b。此时的strtok的指针已经置空了,不处理了,另一个线程访问已经是空了,都退出了,打印2个b都退出了
有的线程可能先打印完buff,重置为空指针,另一个线程就访问为空指针

解决方案

定义2个ptr,分别记录分割到哪里,不共用strtok内部的那个指针
线程安全的版本:

在这里插入图片描述
在这里插入图片描述
运行程序
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值