「OC」多线程的学习——NSThread

news2025/1/11 11:56:54

「OC」多线程的学习——NSThread

文章目录

  • 「OC」多线程的学习——NSThread
    • 线程(process) 和 进程(thread) 的区别
    • 多线程
    • NSThread
      • NSThread的创建
      • NSThread的方法
        • 常见API
        • 线程状态控制方法
      • NSThread线程的状态
    • NSThread的多线程隐患
      • 售票窗口例子
    • @synchronize关键字
    • NSThread的线程通信
      • 线程通信的常用方法
    • 参考文章

线程(process) 和 进程(thread) 的区别

image-20240924100747652

  1. 进程包含线程,一个进程里面可以有一个线程,也可以有多个线程
  2. 进程和线程都是为了处理并发编程这样的场景,但是进程存在问题,频繁的创建和释放的时候效率低,相比之下线程更轻量,创建和销毁的效率更高
  3. 操作系统创建进程,要给进程分配资源,进程是操作系统进行资源分配的基本单位,操作系统创建的进程,是要在CPU上执行.而线程是操作系统调度执行的基本单位
  4. 进程具有独立性,每个经常了个有各自的虚拟地址空间,一个进程挂了不会影响其他进程,而同一个进程中的线程共用同一个内储能空间,一个线程挂了,可能影响到其他线程,甚至导致整个进程崩溃.

在iOS之中App一旦运行,默认就会开启一条线程。这条线程,通常称作为“主线程”。

在iOS应用中主线程的作用一般是:

  1. 刷新UI;

  2. 处理UI事件,例如点击、滚动、拖拽。

如果主线程的操作太多、太耗时,就会造成App卡顿现象严重。所以,通常我们都会把耗时的操作放在子线程中进行,获取到结果之后,回到主线程去刷新UI。

多线程

同一时间,单核的CPU只能处理一条线程,也就是只有一条线程在工作。所谓多线程并发(同时)执行,其实是CPU快速的在多线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

当然现在随着多核的CPU的出现,真正的多线程并行也成为了能够真正实现的。

在这里插入图片描述

iOS多线程有四种方法:pthread,NSThread,GCD, NSOperation,今天我们主要介绍NSThread的用法,剩下的三种会另外进行介绍

NSThread

NSThread是苹果官方提供面向对象操作线程的技术,简单方便,可以直接操作线程对象,不过需要自己控制线程的生命周期,管理所有的线程活动,如生命周期、线程同步、睡眠等。

NSThread的创建

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@"Hello, World"];
	//设置线程的名字,方便查看
	[thread setName:@"firstThread"];
	//启动线程
	[thread start];

- (void)firstThread:(id)arg
{
    NSLog(@"Task %@ %@", [NSThread currentThread], arg);//会将object的内容打印出来
    NSLog(@"Thread Task Complete");
}

使用这个Target这个方法需要使用start进行开启

以下这个方法是实现创建并且开启线程

[NSThread detachNewThread Selector:@selector(run) toTarget:self withObject:(@"NSTread2")];

// 新线程调用方法,里边为需要执行的任务
 - (void)run {
     NSLog(@"%@", [NSThread currentThread]);
}

  • 隐式创建
// 创建好之后也是直接启动
[self performSelectorInBackground:@selector(doSomething3:) withObject:(@"NSTread3")];

NSThread的方法

常见API
// 获得主线程
+ (NSThread *)mainThread;    

// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;

// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;    

// 获得当前线程
NSThread *current = [NSThread currentThread];

// 线程的名字——setter方法
- (void)setName:(NSString *)name;    

// 线程的名字——getter方法
- (NSString *)name;

 // 结束/退出当前线程
 + (void)exit;
  // 发送线程取消信号的 最终线程是否结束 由 线程本身决定
 - (void)cancel NS_AVAILABLE(10_5, 2_0);
 // 启动线程
 - (void)start NS_AVAILABLE(10_5, 2_0);

 
  // 获取当前线程优先级
 + (double)threadPriority;
 // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 
 // 1.0优先级最高
 // 设置优先级
 + (BOOL)setThreadPriority:(double)p;
 // 获取指定线程的优先级
 - (double)threadPriority NS_AVAILABLE(10_6, 4_0);
 - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);


线程状态控制方法
// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
- (void)start;

// 线程进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

//强制停止线程  线程进入死亡状态
+ (void)exit;

NSThread线程的状态

线程在程序之中一共有五个状态,具体关系下图所示

img

总结:

  • 如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态。
  • 如果CPU在运行当前线程对象的时候调用了sleep方法\等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时\得到同步锁,则回到就绪状态。
  • 如果CPU在运行当前线程对象的时候线程任务执行完毕\异常强制退出,则当前线程对象进入死亡状态。

当我们创建完NSThread,然后将其使用start方法进行操作,就会将创建在内存之中的NSThread对象放在可调度的线程池之中

NSThread的多线程隐患

多线程安全隐患的原因:同一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。
那么当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

我们可以通过下面一系列的图片来,看出NSThread的多线程隐患

img

通过上图我们发现,当线程A访问数据并对数据进行操作的同时,线程B访问的数据还是没有更新的数据,线程B同样对数据进行操作,当两个线程结束返回时,就会发生数据错乱的问题。

img

我们可以看出,当线程A访问数据并对数据进行操作的时候,数据被加上一把锁,这个时候其他线程都无法访问数据,知道线程A结束返回数据,线程B此时在访问数据并修改,就不会造成数据错乱了。

售票窗口例子

接下来我们用售票的情景来还原NSThread的多线程隐患,并且在接下来给出解决方案

self.tickets = 20;
NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票员A";
[t1 start];

NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票员B";
[t2 start];

 
- (void)saleTickets{
    while (self.tickets > 0) {
            NSThread *thread = [NSThread currentThread];// 获取当前线程
            [NSThread sleepForTimeInterval:2];
            self.tickets--;
            NSLog(@"当前线程:%@\n剩余票数为:%d ",thread.name, self.tickets);
       }
 }

我们可以看到

这个售票系统在售票的时候会发生错误。这其实是因为在程序运行过程中,如果存在多线程,那么各个线程读写资源就会存在先后、同时读写资源的操作,因为是在不同线程,CPU调度过程中我们无法保证哪个线程会先读写资源,哪个线程后读写资源。因此为了防止数据读写混乱和错误的发生,我们要将线程在读写数据时加锁,这样就能保证操作同一个数据对象的线程只有一个,当这个线程执行完成之后解锁,其他的线程才能操作此数据对象。这就引出我们需要学习的锁

@synchronize关键字

@synchronized(锁对象) { 
// 需要锁定的代码  
}

我们使用@synchronize关键字实现互斥锁,当在新的线程访问时,如果发现其他线程正在执行锁定的代码,新线程进入休眠

- (void)sellTicket:(NSThread*) thread {
    while (self.ticketsCount > 0) {
        @synchronized(self) {
        NSThread *thread = [NSThread currentThread];
        [NSThread sleepForTimeInterval:2];
        self.ticketsCount -- ;
        NSLog(@"当前线程:%@\n剩余票数为:%d ",thread.name, self.ticketsCount);
        }
    }
}

但这段代码还是会出现一些问题,就是我们的票数会出现负数,这是因为当多个线程同时检查剩余票数大于0时,都进入了临界区内执行售票操作,但在售票操作执行之前,票数已经被其他线程减少到0以下。

为了解决这个问题,我们可以在关键字@synchronize之中加入一个判断

- (void)saleTickets{
    while (true) {
        @synchronized(self) {
            if (self.tickets <= 0) {
                break; // 没有剩余票了,退出循环
            }
            NSThread *thread = [NSThread currentThread];
            [NSThread sleepForTimeInterval:2];
            self.tickets--;
            NSLog(@"当前线程:%@\n剩余票数为:%d ", thread.name, self.tickets);
        }
    }
}

我们在检查票数前先获取了锁,确保每次只有一个线程能够进入临界区。同时,我们在临界区内部再次检查剩余票数,以确保在售票时票数仍然大于0。

NSThread的线程通信

线程通信的常用方法

// 返回主线程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定线程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

在多线程的操作当中,我们时常需要在子线程之中获取并下次图片,然后通知主线程更新UI

以下是一个例子

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    [self.view addSubview:self.imageView];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [NSThread detachNewThreadSelector:@selector(donwLoadImage) toTarget:self withObject:nil];
}
-(void)donwLoadImage
{
    NSURL *url = [NSURL URLWithString:@"https://inews.gtimg.com/om_bt/OE8piEBa-tbqn-wNvWZl8coi4AlzoUD43upEkoAnIkYL8AA/641"];
    // 下载图片二进制文件
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 将图片二进制文件转化为image;
    UIImage *image = [UIImage imageWithData:data];
    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
-(void)showImage:(UIImage *)image
{
    self.imageView.image = image;
}

Sep-29-2024 20-36-51

参考文章

2、NSThread介绍.md

iOS多线程–深度解析

多线程四部曲之NSThread

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2179141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【保姆级教程】UMLS工具——MetaMap安装及使用

专家词典 https://lhncbc.nlm.nih.gov/LSG/Projects/lexicon/current/web/index.html SPECIALIST 词典是一个大型的生物医学和通用英语句法词典&#xff0c;旨在提供 SPECIALIST 自然语言处理系统 (NLP) 所需的词汇信息&#xff0c;其中包括 MetaMap 和词汇工具等。它旨在成为…

docker快速安装ELK

一、创建elk目录 创建/elk/elasticsearch/data/目录 mkdir -p /usr/local/share/elk/elasticsearch/data/ 创建/elk/logstash/pipeline/目录 mkdir -p /usr/local/share/elk/logstash/pipeline/ 创建/elk/kibana/conf/目录 mkdir -p /usr/local/share/elk/kibana/conf/ 二、创建…

软考论文《论大数据处理架构及其应用》精选试读

论文真题 模型驱动架构设计是一种用于应用系统开发的软件设计方法&#xff0c;以模型构造、模型转换和精化为核心&#xff0c;提供了一套软件设计的指导规范。在模型驱动架构环境下&#xff0c;通过创建出机器可读和高度抽象的模型实现对不同问题域的描述&#xff0c;这些模型…

算法:按既定顺序创建目标数组

力扣1389 提示&#xff1a; 1 < nums.length, index.length < 100nums.length index.length0 < nums[i] < 1000 < index[i] < i 题解&#xff1a; class Solution {public int[] createTargetArray(int[] nums, int[] index) {int[] target new int[num…

The legacy JS API is deprecated and will be removed in Dart Sass 2.0

The legacy JS API is deprecated and will be removed in Dart Sass 2.0 更新了sass版本后&#xff0c;启动项目控制台一直在报错&#xff0c;影响开发效率&#xff0c;强迫症表示忍受不了。 字面意思是&#xff1a;Sass在2.0版本将会移除legacy JS API&#xff0c;所以现在使…

Python | Leetcode Python题解之第446题等差数列划分II-子序列

题目&#xff1a; 题解&#xff1a; class Solution:def numberOfArithmeticSlices(self, nums: List[int]) -> int:ans 0f [defaultdict(int) for _ in nums]for i, x in enumerate(nums):for j in range(i):d x - nums[j]cnt f[j][d]ans cntf[i][d] cnt 1return a…

Vue发送邮件攻略:从搭建到实现详细步骤?

vue发送邮件功能实现方法&#xff1f;Vue前端如何实现发送邮件&#xff1f; 随着应用功能的不断扩展&#xff0c;用户交互的复杂性也在增加。其中&#xff0c;发送邮件功能是许多Web应用中不可或缺的一部分。AokSend将详细介绍如何使用Vue.js实现发送邮件功能。 Vue发送邮件&…

fastadmin搜索刷新列表,怎么限制用户频繁点击?

文章目录 fastadmin搜索刷新列表&#xff0c;怎么限制用户频繁点击&#xff1f;解决方案fastadmin事件方法实现完结 fastadmin搜索刷新列表&#xff0c;怎么限制用户频繁点击&#xff1f; fastadmin目前有个很致命的问题&#xff0c;就是用户可以频繁的点击搜索等按钮&#xf…

基于SPRINTBOOT+VUE文献资料检索系统

文未可获取一份本项目的java源码和数据库参考。 1 选题背景 随着世界一体化和经济全球化席卷世界,越来越多的高校认识到&#xff0c;利用信息技术的发展来改变对文档、文献的运作方法和管理模式,提高高校的管理效益和生产效益&#xff0c;从而提高高校经济效益&#xff0c;增强…

大模型中的提示学习——情感预测示例项目

提示学习&#xff08;Prompting&#xff09;是一种自然语言处理&#xff08;NLP&#xff09;中的训练技术&#xff0c;它利用预训练的语言模型&#xff08;如BERT、GPT等&#xff09;来解决各种下游任务&#xff0c;如文本分类、命名实体识别、问答等。这种方法的关键思想是通过…

智能红外抄表系统的设计与实现(论文+源码)_kaic

摘 要 随着我国现代社会经济的快速发展&#xff0c;工厂居民用电量剧增。人工抄表&#xff0c;费时费力&#xff0c;效率低下。人工抄表会造成漏抄、误抄、估抄等数据不准确。抄表人员可能与用电户合伙作弊&#xff0c;给资产管理者带来损失。在很多智能仪表的应用中需要现场人…

麦克纳姆轮

一、B站: 一、运动原理 保留前进,平移和自转的车轮方向箭头,把反向变成红色 二、运动公式 如果已知需要的前后、左右、自转量。如何指示每个车轮旋转,用数学的方法表示反转,就是用负数,以平面直角坐标系为参照,向前是正,向后是负,向右是正,向左是负,自转…

云服务架构与华为云架构

目录 1.云服务架构是什么&#xff1f; 1.1 云服务模型 1.2 云部署模型 1.3 云服务架构的组件 1.4 云服务架构模式 1.5 关键设计考虑 1.6 优势 1.7 常见的云服务架构实践 2.华为云架构 2.1 华为云服务模型 2.2 华为云部署模型 2.3 华为云服务架构的核心组件 2.4 华…

【MySQL】视图、用户和权限管理

目录 视图创建视图数据修改影响删除视图视图优点 用户和权限管理查看当前的数据库拥有用户信息创建用户修改密码删除用户权限授权回收权限 视图 视图就是相当于创建一个表&#xff0c;将查询到的结果集给存储起来。像使用复杂的多表查询查询到的结果集就不可以对结果集操作。而…

Go语言中的深拷贝:概念、实现与局限

前不久&#xff0c;在“Gopher部落”知识星球[1]上回答了一个Gopher关于深拷贝(Deep Copy)的问题&#xff0c;让我感觉是时候探讨一下深拷贝技术了。 在日常开发工作中&#xff0c;深拷贝的使用频率相对较低&#xff0c;可能有80%的时间不需要使用深拷贝&#xff0c;只有在特定…

【PHP源码】匿名来信系统H5版本V1.0免费开源

你的匿名来信H5一封你的来信源码/表白祝福短信程序/往来信/传话短信源码支持邮件发信与手机短信发信“你的匿名来信”是最近某音上爆火的一个活动话题&#xff0c;可以通过H5网站&#xff0c;编辑自己想要对某人说的话或者祝福&#xff0c;网站会把您想说的发给您预留的号码&am…

免费语音转文字软件全览:开启高效记录新时代

在当今快节奏的信息时代&#xff0c;高效地处理和记录信息变得至关重要。语音转文字技术的出现&#xff0c;为我们带来了极大的便利&#xff0c;今天&#xff0c;就让我们一同探讨这些语音转文字免费的软件的使用方法。 1.365在线转文字 链接直达&#xff1a;https://www.pdf…

el-table添加fixed后错位问题

1 方案1 return {isShow:false, }mounted() {this.isShowtrue},watch: {$route(newRoute) {this.monitoredRoute newRoute; // 将新的路由信息保存到组件的monitoredRoute属性中// 执行其他操作或调用其他方法},//或$route(newRoute) {this.monitoredRoute newRoute; // 将新…

如何通过视频美颜SDK实现高效的直播美颜API开发?

很多小伙伴疑问短视频和直播平台中的主播美颜工具是如何开发的&#xff0c;今天我们就来探讨一下如何通过视频美颜SDK&#xff0c;高效实现直播美颜API开发&#xff0c;助力开发者快速打造出高质量的直播美颜功能。 一、视频美颜SDK的核心功能 要了解如何高效开发直播美颜API…

Golang | Leetcode Golang题解之第446题等差数列划分II-子序列

题目&#xff1a; 题解&#xff1a; func numberOfArithmeticSlices(nums []int) (ans int) {f : make([]map[int]int, len(nums))for i, x : range nums {f[i] map[int]int{}for j, y : range nums[:i] {d : x - ycnt : f[j][d]ans cntf[i][d] cnt 1}}return }