Controlling How NSThread and NSRunLoop Exit

本文详细介绍了如何在iOS应用中合理地启动和退出线程,包括使用NSThread创建线程、设置线程生命周期、实现线程的等待与唤醒机制、确保线程资源的正确释放等关键步骤,旨在帮助开发者高效管理并发任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:http://shaheengandhi.com/controlling-thread-exit/

While most concurrency can be dealt with by using GCD dispatch queues, there are some things for which you will want to use a separateNSThread. For instance, concurrent network programming remains mostly in this area. You may consider using the popularCocoaAsyncSocket library, which abstracts away much of the complexity of socket programming on iOS. But, for the sake of this post, assume some work requires a separate thread, and we desire to cleanly start and stop the thread. That is, when we decide to start or stop the thread, we want to ensure it has been initialized or destroyed before moving on.

You can get the final version of the code example below at github.

Thread Setup

Remember that threads are an artefact of the operating system and not the objective-c runtime. That means all the nice stuff like anNSAutoreleasePool and an NSRunLoop need to be managed by the thread's top level code. Here is a code snippet of a thread's main method that sets up an autorelease pool as well as starts pumping NSRunLoop.

- (void)start
{
  if (_thread) {
    return;
  }

  _thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadProc:) object:nil];
}

- (void)threadProc:(id)ignored
{
  @autoreleasepool {
    // Startup code here

    // Just spin in a tight loop running the runloop.
    do {
      [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]
    } while (TRUE);
  }
}


This code has many problems. Note that each run of the runloop has a timeout of 1 second. When the runloop has nothing to do for extended periods of time, the thread will still wake up every second and go through the while loop's code, only to end up sitting in the runloop's wait condition. That burns CPU and battery. Also, this thread has no exit condition. Even if we want the thread to live until the end of the application's life, we probably want a way to shut it down cleanly. Here's an enumeration of the problems we want to fix:

  1. Unsure when the thread is ready for work
  2. Never goes to sleep
  3. No way to exit the thread cleanly

Solving all three problems at once is tricky. Fixing #2 means keeping the runloop asleep, preferrably waking only when there is work to be done (being notified by a runloop source). But, that makes it difficult to exit the thread in a timely manner.

Wait for Infinity

How can we make NSRunLoop wait for infinity? The goal is to make sure that the thread goes to sleep. Looking at the documentation forrunUntilDate:, this is not the case:

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate: until the specified expiration date.

The second sentence means this code keeps spinning on the CPU, which is the exact opposite of our desired behavior. A better alternative isrunMode:beforeDate:, for which the documentation reads:

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached.

At least there's a chance the thread will sleep when using this method. However, the thread still runs in a continuous loop when there are no sources or timers. If you are only using a run loop to dispatchperformSelector: calls, you need to add a runloop source of your own to make the thread sleep. As a side effect, it would be effective to use this source to send work to the thread, but that is an exercise left to the reader.

One last thing: what value of NSDate should be used? Any large value is fine, as waking the thread once a day is a large enough to keep the thread silent. +[NSDate distantFuture] is a convenient factory for such a date.

static void DoNothingRunLoopCallback(void *info)
{
}

- (void)threadProc:(id)ignored
{
  @autoreleasepool {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    do {
      [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                               beforeDate:[NSDate distantFuture]];
    } while (TRUE);

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);
  }
}

The exit condition

Now that the thread sleeps forever, how can we ensure a clean shutdown of the thread? It's entirely possible to use +[NSThread exit] to kill the currently running thread. But, this does not clean up stack references to heap objects (like the runloop source that is created), drain the top level autorelease pool, nor allows the thread to flush any remaining work. Instead, we need to make the runloop stop sleeping in order to exitrunMode:beforeDate:, and we need a condition the thread can check in order to know it's time to shut down.

NSRunLoop's conditions for exiting runMode:beforeDate: are somewhat limited. Its return values being YES (when the runloop processed an input source or if the timeout was reached) or NO (when the runloop could not be started) aren't great. Given that the documentation states that NO is returned only when there are no sources or timers, our code will never see NO returned since we added an input source to keep the runloop from spinning!

Luckily, NSRunLoop controls the same object that CFRunLoop APIs do. The CoreFoundation alternative is to use CFRunLoopRunInMode, which provides a more specific reason for the runloop's exit. Specifically,kCFRunLoopRunStopped indicates the runloop was stopped byCFRunLoopStop. This is also the reason that CFRunLoopRun exits (other than having no sources or times, but that will not occur due to our fake source), so we don't need to bother with CFRunLoopRunInMode, nor check a condition.

It is best to execute CFRunLoopStop on the target thread itself. We can do this by using performSelector:onThread:withObject:waitUntilDone:.

The new threadProc: looks like this:

- (void)threadProc:(id)ignored
{
  @autoreleasepool {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    // Keep processing events until the runloop is stopped.
    CFRunLoopRun();

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);
  }
}
We can use code like the following to exit the thread quickly from any thread, including the target thread itself:
- (void)stop
{
  [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  _thread = nil;
}

- (void)_stop
{
  CFRunLoopStop(CFRunLoopGetCurrent());
}

Synchronizing thread startup and exit

Two more problems to solve: when starting the thread, can we ensure that it is ready? When shutting down a thread, can we ensure it is gone?

I would note that there are better threading patterns than attempting to ensure the state of the target thread. For example, a thread should be able to accept work, but simply not process it until it is ready. Resources outside of the thread's runloop should be minimal such that ensuring the thread is no longer executing should be above and beyond the desired knowledge of the thread's state.

It is tempting to simply add waitUntilDone:YES to the performSelectorstatement in order to wait until the target thread has exited, but that would only wait until the _stop selector was invoked, and not wait for the thread's cleanup code to be run. In order to do that, we need to make a new assumption: the target thread is being shut down from another thread. It would be impossible for a thread to wait for itself to shut down.

In order for the target thread to signal to the control thread that it is done, a condition must be shared between them. NSCondition provides convenient semantics for our purposes.

The thread management code is below. This pattern keeps a thread with no work asleep for long periods of time, while allowing for a fast and clean exit. It also allows for startup and shutdown to be synchronized.

- (void)start
{
  if (_thread) {
    return;
  }

  _thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadProc:) object:nil];

  // _condition was created in -init
  [_condition lock];
  [_thread start];
  [_condition wait];
  [_condition unlock];
}

- (void)stop
{
  if (!_thread) {
    return;
  }

  [_condition lock];
  [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  [_condition wait];
  [_condition unlock];
  _thread = nil;
}

- (void)threadProc:(id)object
{
  @autoreleasepool {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    [_condition lock];
    [_condition signal];
    [_condition unlock];

    // Keep processing events until the runloop is stopped.
    CFRunLoopRun();

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);

    [_condition lock];
    [_condition signal];
    [_condition unlock];
  }
}

Ensuring Destruction of Thread Resources

There is still another issue with this code: when the thread has signaled its exit, the autorelease pool has not yet been drained. Without ensuring that the thread's memory resources have been released, the purpose of synchronizing the thread's exit becomes much less appealing.

There's a bit of a catch-22. NSCondition makes no promise that it is free from using -autorelease in its implementations of -lock-signal, and-unlock. That means there should be a valid NSAutoreleasePool when using these APIs. We have two solutions available to us. We can either manually drain the autorelease pool, or use a different way to synchronize the thread's exit that waits until threadProc: has exited. The first is somewhat messy. The second has two variants of its own.

Manual Autorelease

In order to use NSAutoreleasePool directly, you must disable ARC.

Remember that -[NSAutoreleasePool drain] is effectively the same as-[NSAutoreleasePool release] and that the pool is no longer valid after draining it. So, manually draining an autorelease pool means creating another one to ensure the NSCondition APIs have the right environment.

- (void)threadProc:(id)object
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    [_condition lock];
    [_condition signal];
    [_condition unlock];

    // Keep processing events until the runloop is stopped.
    CFRunLoopRun();

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);

    // Release all accumulated resources, but make sure NSCondition has the
    // right environment.
    [pool drain];
    pool = [[NSAutoreleasePool alloc] init];

    [_condition lock];
    [_condition signal];
    [_condition unlock];
  }
  [pool drain];
}

Using NSThreadWillExitNotification

NSThreadWillExitNotification is a notification sent by NSThread when the thread's main function has finished and the thread is about to finish execution. This must happen after threadProc:, so this ensures the thread's top level autorelease pool has been drained. Since the notification fires on the exiting thread, NSCondition is still used to synchronize the state of the thread.

- (void)stop
{
  if (!_thread) {
    return;
  }

  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  [nc addObserver:self selector:@(_signal) name:NSThreadWillExitNotification object:_thread];

  [_condition lock];
  [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  [_condition wait];
  [_condition unlock];

  [nc removeObserver:self name:NSThreadWillExitNotification object:_thread];
  _thread = nil;
}

- (void)threadProc:(id)object
{
  @autoreleasepool {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    [_condition lock];
    [_condition signal];
    [_condition unlock];

    // Keep processing events until the runloop is stopped.
    CFRunLoopRun();

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);
  }
}

- (void)_signal
{
  [_condition lock];
  [_condition signal];
  [_condition unlock];
}

Using pthreads

All of the above solutions have one slight problem: the thread hasn't quite exited when the control thread believes it has. The target thread is very close to being done, but that is not the same as done.

To ensure that, we must go outside the realm of NSThread and use pthreads instead. pthread_join guarantees the target thread has terminated. Using pthreads makes the code a bit more verbose, and some of the memory management must be done carefully. In particular,self is retained when specifying it as an argument of NSThread's initializer, but will not be retained when using it as an argument topthread_create. Note that we still need an NSThread reference to useperformSelector:onThread:withObject:waitUntilDone:, but there is no way to convert a pthread_t to an NSThread. Luckily,+[NSThread currentThread] can obtain the correct object reference.

NSCondition is still used to synchronize thread startup. Because it is not used for any other purpose, it is not necessary to lock the condition before starting the thread. However, to be consistent with previous code, we will follow the previous pattern of creating the thread in a suspended state and resuming it with the condition's lock held.

static void *ThreadProc(void *arg)
{
  ThreadedComponent *component = (__bridge_transfer ThreadedComponent *)arg;
  [component threadProc:nil];
  return 0;
}

- (void)start
{
  if (_thread) {
    return;
  }

  if (pthread_create_suspended_np(&_pthread, NULL, &ThreadProc, (__bridge_retained void *)self) != 0) {
    return;
  }

  // _condition was created in -init
  [_condition lock];
  mach_port_t mach_thread = pthread_mach_thread_np(_pthread);
  thread_resume(mach_thread);
  [_condition wait];
  [_condition unlock];
}

- (void)stop
{
  if (!_thread) {
    return;
  }

  [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  pthread_join(_pthread, NULL);
  _thread = nil;
}

- (void)threadProc:(id)object
{
  @autoreleasepool {
    CFRunLoopSourceContext context = {0};
    context.perform = DoNothingRunLoopCallback;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    // Obtain the current NSThread before signaling startup is complete.
    _thread = [NSThread currentThread];

    [_condition lock];
    [_condition signal];
    [_condition unlock];

    // Keep processing events until the runloop is stopped.
    CFRunLoopRun();

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);
  }
}

Code Samples

Head on over to github to download code samples for all four working versions of the code above.









内容概要:本文深入解析了扣子COZE AI编程及其详细应用代码案例,旨在帮助读者理解新一代低门槛智能体开发范式。文章从五个维度展开:关键概念、核心技巧、典型应用场景、详细代码案例分析以及未来发展趋势。首先介绍了扣子COZE的核心概念,如Bot、Workflow、Plugin、Memory和Knowledge。接着分享了意图识别、函数调用链、动态Prompt、渐进式发布及监控可观测等核心技巧。然后列举了企业内部智能客服、电商导购助手、教育领域AI助教和金融行业合规质检等应用场景。最后,通过构建“会议纪要智能助手”的详细代码案例,展示了从需求描述、技术方案、Workflow节点拆解到调试与上线的全过程,并展望了多智能体协作、本地私有部署、Agent2Agent协议、边缘计算插件和实时RAG等未来发展方向。; 适合人群:对AI编程感兴趣的开发者,尤其是希望快速落地AI产品的技术人员。; 使用场景及目标:①学习如何使用扣子COZE构建生产级智能体;②掌握智能体实例、自动化流程、扩展能力和知识库的使用方法;③通过实际案例理解如何实现会议纪要智能助手的功能,包括触发器设置、下载节点、LLM节点Prompt设计、Code节点处理和邮件节点配置。; 阅读建议:本文不仅提供了理论知识,还包含了详细的代码案例,建议读者结合实际业务需求进行实践,逐步掌握扣子COZE的各项功能,并关注其未来的发展趋势。
标题基于Django+Vue的双相情感障碍交流平台研究AI更换标题第1章引言介绍双相情感障碍交流平台的背景、意义、研究现状以及论文的研究方法和创新点。1.1研究背景与意义阐述双相情感障碍交流平台的重要性和实际需求。1.2国内外研究现状分析国内外在双相情感障碍交流平台方面的研究进展。1.3研究方法与创新点介绍论文采用的研究方法和平台的创新之处。第2章相关理论技术介绍Django、Vue以及双相情感障碍相关理论知识。2.1Django框架概述简述Django框架的特点、优势及其在Web开发中的应用。2.2Vue框架概述阐述Vue框架的核心思想、特点及其在前端开发中的应用。2.3双相情感障碍理论知识介绍双相情感障碍的基本概念、症状表现及其治疗方法。第3章平台需求分析与设计详细分析用户需求,设计双相情感障碍交流平台的整体架构和功能模块。3.1用户需求分析通过调研了解用户需求,为平台设计提供参考。3.2平台整体架构设计根据用户需求,设计平台的整体架构和技术路线。3.3功能模块设计详细介绍平台各个功能模块的设计思路和实现方法。第4章平台实现与测试阐述双相情感障碍交流平台的实现过程,包括前端和后端的实现,以及平台的测试。4.1前端实现利用Vue框架实现平台的前端界面和交互功能。4.2后端实现采用Django框架实现平台的后端逻辑和数据处理功能。4.3平台测试与优化对平台进行全面测试,发现并解决潜在问题,优化平台性能。第5章平台应用与评价将双相情感障碍交流平台应用于实际场景,并对其进行综合评价。5.1平台应用情况介绍平台在实际应用中的情况,包括用户反馈和使用效果。5.2平台评价指标与方法确定平台的评价指标和方法,对平台进行全面评估。5.3评价结果与分析根据评价指标和方法,得出平台的评价结果,并进行详细分析。第6章结论与展望总结论文的研究成果,分析双相情感障碍交流平台的优缺点,并展望未来的研
资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 安捷伦IO Libraries Suite 15.5是由安捷伦科技公司推出的一款专业软件工具,广泛应用于科学研究、工程开发和数据采集等领域。该版本专为Windows XP系统设计,旨在满足仍在使用XP系统的用户需求。以下是该套件的主要功能、使用场景及研究开发应用的详细介绍。 仪器控制:该套件的核心功能是通过GPIB、USB、以太网等多种接口,实现对安捷伦品牌仪器设备(如示波器、逻辑分析仪、频谱分析仪等)的远程操作。 驱动程序支持:包含大量驱动程序,覆盖安捷伦广泛的硬件产品线,确保用户能轻松与不同仪器通信。 数据采集与处理:提供数据采集工具,支持实时数据获取、存储和分析,便于用户管理实验数据。 编程接口:支持VISA、Visual Basic、C++、LabVIEW等多种编程语言,允许用户自定义仪器控制逻辑,实现自动化测试和测量。 教育实验室:在高校物理、电子、通信等实验室,该套件可帮助学生高效进行实验操作。 产品研发:工程师在产品开发阶段,可利用该套件快速验证原型,测试性能指标。 生产测试:在生产线上,可通过自动化脚本控制多台仪器进行批量测试,提高生产效率。 信号分析:在电子工程领域,该套件可与安捷伦频谱分析仪配合,进行复杂信号分析和频谱监测。 嵌入式系统测试:在嵌入式系统开发中,可用于调试硬件接口,验证系统功能。 软件定义无线电(SDR):在SDR项目中,该套件可提供实时数据采集和处理能力,用于信号接收和发送控制。 该版本虽然专为Windows XP设计,但随着操作系统的发展,用户可能需要升级到更高版本以适应新环境。需要注意的是,部分较新的安捷伦仪器可能不再支持XP系统,因此在选择仪器时需谨慎。尽管如此,安捷伦IO Libraries Suite 15.5仍是一个强大且全面的工具,尤
<think>好的,我现在需要帮助用户解决如何用C++控制海康威视工业相机和伺服电机的问题。首先,我得确定用户的具体需求是什么。他们可能是在做自动化项目,需要同时控制相机和电机,比如在视觉引导的机械臂系统中,相机捕捉位置,然后电机调整位置。 接下来,我需要考虑海康威视相机的控制方式。根据之前的引用,海康威视的相机通常使用ONVIF协议或SDK。用户可能需要通过SDK来获取图像或控制相机参数。而伺服电机通常通过Modbus、EtherCAT或者特定的控制器指令来控制,比如使用RS485通信。 然后,我得回忆是否有相关的C++示例代码。海康威视的官网或开发者平台可能会有SDK和示例,用户可能需要先下载SDK,并按照文档配置环境。对于伺服电机,可能需要第三方库或者厂商提供的API,比如Modbus的库如libmodbus。 接下来,我需要组织一个基本的示例结构。可能需要分开相机控制和电机控制两部分。相机部分可能包括初始化、捕获图像、释放资源;电机部分包括连接、移动、断开。同时,需要处理错误情况,比如连接失败或指令执行错误。 需要注意的是,用户可能需要具体的SDK函数和电机协议细节,但如果没有具体型号和文档,代码只能是框架性的。因此,在回答中应该建议用户查阅官方文档,并确保包含必要的头文件和链接库。 另外,用户可能遇到的问题包括环境配置、SDK版本兼容性、通信协议的选择等。可能需要提醒用户注意这些方面,并提供获取帮助的途径,如官方支持或社区论坛。 最后,生成相关问题,帮助用户深入解决可能遇到的后续问题,比如性能优化、多线程处理、错误处理等。</think>[用户可见层] # 海康威视工业设备控制C++代码框架 ## 一、工业相机控制基础 海康威视工业相机需通过官方SDK进行控制,开发流程包含: 1. 下载MVS开发套件(含Windows/Linux版) 2. 配置开发环境(需安装OpenCV等视觉库) 3. 使用SDK中的设备发现、图像采集API 示例代码框架: ```cpp #include "MvCameraControl.h" void CameraControl() { MV_CC_DEVICE_INFO_LIST stDeviceList; MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList); // 创建相机实例 void* handle = nullptr; MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]); // 连接设备 MV_CC_OpenDevice(handle); // 设置触发模式(示例为连续采集模式) MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_OFF); // 开始采集 MV_CC_StartGrabbing(handle); // 获取图像数据 MV_FRAME_OUT stImageInfo; MV_CC_GetImageBuffer(handle, &stImageInfo, 1000); // 处理图像... // 释放资源 MV_CC_StopGrabbing(handle); MV_CC_CloseDevice(handle); MV_CC_DestroyHandle(handle); } ``` ## 二、伺服电机控制基础 常见伺服控制方式(以Modbus-RTU为例): ```cpp #include <modbus.h> void ServoControl() { modbus_t* ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1); modbus_set_slave(ctx, 1); // 设置从站地址 if(modbus_connect(ctx) == -1) { std::cerr << "Connection failed: " << modbus_strerror(errno); return; } // 写入目标位置(示例地址40001) uint16_t target_position = 1000; modbus_write_register(ctx, 0, target_position); // 读取当前位置 uint16_t current_pos; modbus_read_registers(ctx, 1, 1, &current_pos); modbus_close(ctx); modbus_free(ctx); } ``` ## 三、系统集成建议 1. 线程管理:建议使用独立线程处理图像采集和电机控制 2. 同步机制:通过共享内存/队列实现视觉坐标到电机位置的转换 3. 错误处理:增加超时重试和异常中断机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值