iOS并发编程(四)Migrating Away from Threads

迁移远离线程

有许多方法可以调整现有的线程代码以利用Grand Central Dispatch和操作对象。虽然在所有情况下都可能无法远离线程,但在进行切换的地方,性能(以及代码的简单性)可以大大提高。具体来说,使用调度队列和操作队列而不是线程有几个优点:

  • 它减少了应用程序为在应用程序的内存空间中存储线程堆栈而支付的内存损失。

  • 它消除了创建和配置线程所需的代码。

  • 它消除了在线程上管理和调度工作所需的代码。

  • 它简化了您必须编写的代码。

本章提供了有关如何替换现有基于线程的代码以及使用调度队列和操作队列来实现相同类型的行为的一些提示和指南。

 

用Dispatch Queues替换线程

要了解如何用调度队列替换线程,首先要考虑一下今天在应用程序中使用线程的一些方法:

  • 单任务线程。创建一个线程来执行单个任务,并在任务完成时释放线程。

  • 工作线程。创建一个或多个具有特定任务的工作线程。定期向每个线程发送任务。

  • 线程池。创建一个通用线程池并为每个线程设置运行循环。当您要执行任务时,从池中获取一个线程并将任务分派给它。如果没有空闲线程,则排队任务并等待线程变为可用。

虽然这些看起来可能是截然不同的技术,但它们实际上只是基于相同原理的变体。在每种情况下,都使用一个线程来运行应用程序必须执行的某个任务。它们之间的唯一区别是用于管理线程和任务排队的代码。使用调度队列和操作队列,您可以消除所有线程和线程通信代码,而只关注您要执行的任务。

如果您使用上述线程模型之一,那么您应该已经非常了解应用程序执行的任务类型。不要将任务提交到其中一个自定义线程,而是尝试将该任务封装在操作对象或块对象中,并将其分派到适当的队列。对于没有特别争议的任务 - 即不采取锁定的任务 - 您应该能够进行以下直接替换:

  • 对于单个任务线程,将任务封装在块或操作对象中,并将其提交到并发队列。

  • 对于工作线程,您需要决定是使用串行队列还是并发队列。如果使用工作线程同步特定任务集的执行,请使用串行队列。如果确实使用工作线程执行没有相互依赖性的任意任务,请使用并发队列。

  • 对于线程池,将任务封装在块或操作对象中,并将它们分派到并发队列以供执行。

当然,像这样的简单替换可能并不适用于所有情况。如果您正在执行的任务争用共享资源,理想的解决方案是尝试首先删除或最小化该争用。如果有一些方法可以重构或重新构建代码以消除对共享资源的相互依赖性,那当然更可取。但是,如果这样做不可能或效率较低,仍然有办法利用队列。队列的一大优势是它们提供了一种更可预测的方式来执行代码。这种可预测性意味着仍然有办法在不使用锁或其他重量级同步机制的情况下同步代码的执行。您可以使用队列执行许多相同的任务,而不是使用锁:

  • 如果您有必须按特定顺序执行的任务,请将它们提交到串行调度队列。如果您更喜欢使用操作队列,请使用操作对象依赖项以确保这些对象按特定顺序执行。

  • 如果您当前正在使用锁来保护共享资源,请创建一个串行队列来执行修改该资源的任何任务。然后,串行队列将现有锁替换为同步机制。有关消除锁定的更多信息,请参阅消除基于锁定的代码

  • 如果使用线程连接等待后台任务完成,请考虑使用调度组。您还可以使用 NSBlockOperation对象或操作对象依赖项来实现类似的组完成行为。有关如何跟踪执行任务组的更多信息,请参阅替换线程连接

  • 如果使用生产者 - 消费者算法来管理有限资源池,请考虑将实现更改为更改生产者 - 消费者实现中显示的实现

  • 如果使用线程从描述符读取或写入,或监视文件操作,请使用Dispatch Sources中所述的调度源

重要的是要记住,队列不是替换线程的灵丹妙药。队列提供的异步编程模型适用于延迟不是问题的情况。尽管队列提供了配置队列中任务执行优先级的方法,但更高的执行优先级并不能保证在特定时间执行任务。因此,在需要最小延迟的情况下,例如在音频和视频播放中,线程仍然是更合适的选择。

消除基于锁的代码

对于线程代码,锁是同步对线程之间共享的资源的访问的传统方法之一。但是,使用锁是有代价的。即使在无争议的情况下,总是会出现与锁定相关的性能损失。在有争议的情况下,一个或多个线程有可能在等待锁释放时阻塞不确定的时间。

用队列替换基于锁的代码消除了与锁相关的许多消耗,并简化了剩余的代码。您可以改为创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列开销要比锁小很多。例如,排队任务不需要陷入内核以获取互斥锁。

排队任务时,您必须做出的主要决定是同步还是异步。异步提交任务可让当前线程在执行任务时继续运行。同步提交任务会阻止当前线程,直到任务完成。这两个选项都有适当的用途,尽管只要有可能,异步提交任务肯定是有利的。

以下各节介绍如何使用等效的基于队列的代码替换现有的基于锁的代码。

 

实现异步锁

异步锁是一种保护共享资源的方法,而不会阻止任何修改该资源的代码。当您需要修改数据结构作为代码正在执行的其他工作的副作用时,可以使用异步锁定。使用传统线程,通常实现此代码的方式是锁定共享资源,进行必要的更改,释放锁定,并继续执行任务的主要部分。但是,使用调度队列,调用代码可以异步进行修改,而无需等待完成这些更改。

清单5-1显示了异步锁实现的示例。在此示例中,受保护资源定义其自己的串行调度队列。调用代码将块对象提交给此队列,该队列包含需要对资源进行的修改。因为队列本身是按顺序执行块的,所以保证按照接收顺序对资源进行更改; 但是,因为任务是异步执行的,所以调用线程不会阻塞。

清单5-1   异步修改受保护资源

dispatch_async(obj->serial_queue, ^{
   // Critical section
});

同步执行关键部分

如果当前代码在给定任务完成之前无法继续,则可以使用该dispatch_sync功能同步提交任务。此函数将任务添加到调度队列,然后阻止当前线程,直到任务完成执行。调度队列本身可以是串行或并发队列,具体取决于您的需要。但是,因为此函数会阻止当前线程,所以只应在必要时使用它。清单5-2显示了使用包装代码的关键部分的技术dispatch_sync


清单5-2   同步运行关键部分

dispatch_sync(my_queue,^ {
   //关键部分
});

如果您已使用串行队列来保护共享资源,则同步分派到该队列不会像异步调度那样保护共享资源。同步调度的唯一原因是阻止当前代码继续,直到关键部分完成。例如,如果您想从共享资源中获取一些值并立即使用它,则需要同步调度。如果当前代码不需要等待关键部分完成,或者它只是向同一个串行队列提交其他后续任务,则通常首选提交异步。

改进循环码

如果你的代码有循环,并且每次循环完成的工作都与其他迭代中完成的工作无关,你可以考虑使用dispatch_applyor dispatch_apply_f函数重新实现该循环代码。这些函数将循环的每次迭代分别提交给调度队列以进行处理。与并发队列结合使用时,此功能允许您同时执行循环的多次迭代。

dispatch_apply 和 dispatch_apply_f 是同步调用函数,会阻塞当前线程的执行,知道所有的循环迭代完成。当提交到一个并发队列,迭代循环的执行顺序不能得到保证。每个执行迭代的线程可能会阻塞,并引起执行顺序的不一致。因此,用于每个循环迭代的块对象或函数必须是可重入的。

清单5-3显示了如何使用基于调度的等效项替换for循环。传递给dispatch_apply或者dispatch_apply_f的块或函数,必须使用一个整数值来指示当前循环迭代的次数。在此示例中,代码只是将当前循环编号打印到控制台。

清单5-3  替换for循环不使用步幅

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
   printf("%u\n", i);
});

虽然前面的示例是一个简单的示例,但它演示了使用调度队列替换循环的基本技术。虽然这可能是提高基于循环的代码性能的好方法,但您仍然必须清楚地使用这种技术。尽管调度队列的开销非常低,但仍然需要在线程上调度每个循环迭代的成本。因此,您应确保您的循环代码完成足够的工作以保证成本。确切地说,您需要做多少工作就是必须使用性能工具来衡量。

增加每个循环迭代中的工作量的一种简单方法是使用跨步。通过跨步,您可以重写块代码以执行原始循环的多次迭代。然后dispatch_apply,按比例减少指定给函数的计数值。清单5-4显示了如何实现对清单5-3中所示的循环代码的跨越。在清单5-4中,块调用printf语句的次数与stride值相同,在本例中为137.(实际的stride值是你应根据代码所做的工作配置的。)因为当将总迭代次数除以步幅值时剩余剩余部分,任何剩余的迭代都是内联执行的。

 

文档存档开发人员

搜索

 

并发编程指南

  • 目录

一个上一个

迁移远离线程

有许多方法可以调整现有的线程代码以利用Grand Central Dispatch和操作对象。虽然在所有情况下都可能无法远离线程,但在进行切换的地方,性能(以及代码的简单性)可以大大提高。具体来说,使用调度队列和操作队列而不是线程有几个优点:

  • 它减少了应用程序为在应用程序的内存空间中存储线程堆栈而支付的内存损失。

  • 它消除了创建和配置线程所需的代码。

  • 它消除了管理和安排线程工作所需的代码。

  • 它简化了您必须编写的代码。

本章提供了有关如何替换现有基于线程的代码以及使用调度队列和操作队列来实现相同类型的行为的一些提示和指南。

用Dispatch Queues替换线程

要了解如何用调度队列替换线程,首先要考虑一下今天在应用程序中使用线程的一些方法:

  • 单任务线程。创建一个线程来执行单个任务,并在任务完成时释放线程。

  • 工人线程。创建一个或多个具有特定任务的工作线程。定期向每个线程发送任务。

  • 线程池。创建一个通用线程池并为每个线程设置运行循环。当您要执行任务时,从池中获取一个线程并将任务分派给它。如果没有空闲线程,则排队任务并等待线程变为可用。

虽然这些看起来可能是截然不同的技术,但它们实际上只是基于相同原理的变体。在每种情况下,都使用一个线程来运行应用程序必须执行的某个任务。它们之间的唯一区别是用于管理线程和任务排队的代码。使用调度队列和操作队列,您可以消除所有线程和线程通信代码,而只关注您要执行的任务。

如果您使用上述线程模型之一,那么您应该已经非常了解应用程序执行的任务类型。不要将任务提交到其中一个自定义线程,而是尝试将该任务封装在操作对象或块对象中,并将其分派到适当的队列。对于没有特别争议的任务 - 即不采取锁定的任务 - 您应该能够进行以下直接替换:

  • 对于单个任务线程,将任务封装在块或操作对象中,并将其提交到并发队列。

  • 对于工作线程,您需要决定是使用串行队列还是并发队列。如果使用工作线程同步特定任务集的执行,请使用串行队列。如果确实使用工作线程执行没有相互依赖性的任意任务,请使用并发队列。

  • 对于线程池,将任务封装在块或操作对象中,并将它们分派到并发队列以供执行。

当然,像这样的简单替换可能并不适用于所有情况。如果您正在执行的任务争用共享资源,理想的解决方案是尝试首先删除或最小化该争用。如果有一些方法可以重构或重新构建代码以消除对共享资源的相互依赖性,那当然更可取。但是,如果这样做不可能或效率较低,仍然有办法利用队列。队列的一大优势是它们提供了一种更可预测的方式来执行代码。这种可预测性意味着仍然有办法在不使用锁或其他重量级同步机制的情况下同步代码的执行。您可以使用队列执行许多相同的任务,而不是使用锁:

  • 如果您有必须按特定顺序执行的任务,请将它们提交到串行调度队列。如果您更喜欢使用操作队列,请使用操作对象依赖项以确保这些对象按特定顺序执行。

  • 如果您当前正在使用锁来保护共享资源,请创建一个串行队列来执行修改该资源的任何任务。然后,串行队列将现有锁替换为同步机制。有关消除锁定的更多信息,请参阅消除基于锁定的代码

  • 如果使用线程连接等待后台任务完成,请考虑使用调度组。您还可以使用 NSBlockOperation对象或操作对象依赖项来实现类似的组完成行为。有关如何跟踪执行任务组的更多信息,请参阅替换线程连接

  • 如果使用生产者 - 消费者算法来管理有限资源池,请考虑将实现更改为更改生产者 - 消费者实现中显示的实现

  • 如果使用线程从描述符读取或写入,或监视文件操作,请使用Dispatch Sources中所述的调度源

重要的是要记住,队列不是替换线程的灵丹妙药。队列提供的异步编程模型适用于延迟不是问题的情况。尽管队列提供了配置队列中任务执行优先级的方法,但更高的执行优先级并不能保证在特定时间执行任务。因此,在需要最小延迟的情况下,例如在音频和视频播放中,线程仍然是更合适的选择。

消除基于锁的代码

对于线程代码,锁是同步对线程之间共享的资源的访问的传统方法之一。但是,使用锁是有代价的。即使在无争议的情况下,总是会出现与锁定相关的性能损失。在有争议的情况下,一个或多个线程有可能在等待锁定释放时阻塞不确定的时间。

用队列替换基于锁的代码消除了与锁相关的许多惩罚,并简化了剩余的代码。您可以改为创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列不会像锁一样处罚。例如,排队任务不需要陷入内核以获取互斥锁。

排队任务时,您必须做出的主要决定是同步还是异步。异步提交任务可让当前线程在执行任务时继续运行。同步提交任务会阻止当前线程,直到任务完成。这两个选项都有适当的用途,尽管只要有可能,异步提交任务肯定是有利的。

以下各节介绍如何使用等效的基于队列的代码替换现有的基于锁的代码。

实现异步锁

异步锁是一种保护共享资源的方法,而不会阻止任何修改该资源的代码。当您需要修改数据结构作为代码正在执行的其他工作的副作用时,可以使用异步锁定。使用传统线程,通常实现此代码的方式是锁定共享资源,进行必要的更改,释放锁定,并继续执行任务的主要部分。但是,使用调度队列,调用代码可以异步进行修改,而无需等待完成这些更改。

清单5-1显示了异步锁实现的示例。在此示例中,受保护资源定义其自己的串行调度队列。调用代码将块对象提交给此队列,该队列包含需要对资源进行的修改。因为队列本身是按顺序执行块的,所以保证按照接收顺序对资源进行更改; 但是,因为任务是异步执行的,所以调用线程不会阻塞。

清单5-1   异步修改受保护资源

dispatch_async(obj-> serial_queue,^ {
   //关键部分
});

同步执行关键部分

如果当前代码在给定任务完成之前无法继续,则可以使用该dispatch_sync功能同步提交任务。此函数将任务添加到调度队列,然后阻止当前线程,直到任务完成执行。调度队列本身可以是串行或并发队列,具体取决于您的需要。但是,因为此函数会阻止当前线程,所以只应在必要时使用它。清单5-2显示了使用包装代码的关键部分的技术dispatch_sync

清单5-2   同步运行关键部分

dispatch_sync(my_queue,^ {
   //关键部分
});

如果您已使用串行队列来保护共享资源,则同步分派到该队列不会像异步调度那样保护共享资源。同步调度的唯一原因是阻止当前代码继续,直到关键部分完成。例如,如果您想从共享资源中获取一些值并立即使用它,则需要同步调度。如果当前代码不需要等待关键部分完成,或者它只是向同一个串行队列提交其他后续任务,则通常首选提交异步。

改进循环码

如果你的代码有循环,并且每次循环完成的工作都与其他迭代中完成的工作无关,你可以考虑使用dispatch_applyor dispatch_apply_f函数重新实现该循环代码。这些函数将循环的每次迭代分别提交给调度队列以进行处理。与并发队列结合使用时,此功能允许您同时执行循环的多次迭代。

dispatch_applydispatch_apply_f功能是阻止执行的当前线程,直到所有的循环迭代是完全同步的函数调用。当提交到并发队列时,不保证循环迭代的执行顺序。运行每次迭代的线程可能会阻塞并导致给定的迭代在其周围的其他迭代之前或之后完成。因此,用于每个循环迭代的块对象或函数必须是可重入的。

清单5-3显示了如何for使用基于调度的等效项替换循环。传递给的块或函数,dispatch_apply或者dispatch_apply_f必须采用指示当前循环迭代的整数值。在此示例中,代码只是将当前循环编号打印到控制台。

清单5-3for无需跨步  替换循环

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply(count,queue,^(size_t i){
   printf(“%u \ n”,i);
});

虽然前面的示例是一个简单的示例,但它演示了使用调度队列替换循环的基本技术。虽然这可能是提高基于循环的代码性能的好方法,但您仍然必须清楚地使用这种技术。尽管调度队列的开销非常低,但仍然需要在线程上调度每个循环迭代的成本。因此,您应确保您的循环代码完成足够的工作以保证成本。确切地说,您需要做多少工作就是必须使用性能工具来衡量。

增加每个循环迭代中的工作量的一种简单方法是使用跨步。通过跨步,您可以重写块代码以执行原始循环的多次迭代。然后dispatch_apply,按比例减少指定给函数的计数值。清单5-4显示了如何实现对清单5-3中所示的循环代码的跨越。在清单5-4中,块调用printf语句的次数与stride值相同,在本例中为137.(实际的stride值是你应根据代码所做的工作配置的。)因为当将总迭代次数除以步幅值时剩余剩余部分,任何剩余的迭代都是内联执行的。

清单5-4   为调度的for循环添加一个步幅

 

int stride = 137;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count / stride, queue, ^(size_t idx){
    size_t j = idx * stride;
    size_t j_stop = j + stride;
    do {
       printf("%u\n", (unsigned int)j++);
    }while (j < j_stop);
});
 
size_t i;
for (i = count - (count % stride); i < count; i++)
   printf("%u\n", (unsigned int)i);

使用步幅有一些明显的性能优势。特别地,相对于步幅,当原始循环迭代次数较高时,步幅提供了益处。同时调度更少的块意味着执行这些块的代码花费的时间比调度它们多。与任何性能指标一样,您可能必须使用跨越值来为代码找到最有效的值。

替换线程连接

 

线程连接允许您生成一个或多个线程,然后让当前线程等待,直到这些线程完成。要实现线程连接,父级会将子线程创建为可连接线程。如果父节点在没有子线程结果的情况下无法再进行,则它将与子节点连接。此进程会阻止父线程,直到子进程完成其任务并退出,此时父进程可以从子进程收集结果并继续自己的工作。如果父级需要连接多个子线程,则它一次只能连接一个子线程。

Dispatch组提供的语义与线程连接的语义类似,但具有一些额外的优点。与线程连接一样,调度组是线程阻塞的一种方式,直到一个或多个子任务完成执行。与线程连接不同,调度组同时等待其所有子任务。因为调度组使用调度队列来执行工作,所以它们非常有效。

要使用调度组执行可连接线程执行的相同工作,您将执行以下操作:

  1. 使用该dispatch_group_create功能创建新的调度组。

  2. 使用dispatch_group_asyncdispatch_group_async_f功能向组添加任务。您提交给组的每个任务都代表您通常在可连接线程上执行的工作。

  3. 当前线程无法再进行前进时,请调用该dispatch_group_wait函数以等待该组。此功能会阻止当前线程,直到组中的所有任务完成执行。

如果使用操作对象来实现任务,则还可以使用依赖项实现线程连接。您可以将父代码移动到操作对象,而不是让父线程等待一个或多个任务完成。然后,您将在父操作对象和设置为执行通常由可连接线程执行的工作的任意数量的子操作对象之间设置依赖关系。依赖于其他操作对象会阻止父操作对象执行,直到所有操作都完成为止。

改变生产者 - 消费者实施

生产者 - 消费者模型允许您管理有限数量的动态生成的资源。当生产者创建新资源(或工作)时,一个或多个消费者等待这些资源(或工作)准备好并在它们消耗时消耗它们。实现生产者 - 消费者模型的典型机制是条件或信号量。

使用条件,生产者线程通常执行以下操作:

  1. 锁定与条件关联的互斥锁(使用pthread_mutex_lock)。

  2. 生产资源或工作消费。

  3. 发出有消耗的条件变量的信号(使用pthread_cond_signal

  4. 解锁互斥锁(使用pthread_mutex_unlock)。

反过来,相应的消费者线程执行以下操作:

  1. 锁定与条件关联的互斥锁(使用pthread_mutex_lock)。

  2. 设置while循环以执行以下操作:

    1. 检查是否确实有工作要做。

    2. 如果没有工作要做(或没有资源可用),请调用pthread_cond_wait阻塞当前线程,直到出现相应的信号。

  3. 获取生产者提供的工作(或资源)。

  4. 解锁互斥锁(使用pthread_mutex_unlock)。

  5. 处理工作。

使用调度队列,您可以将生产者和消费者实现简化为单个调用:

dispatch_async(queue,^ {
   //处理工作项
});

当您的生产者要完成工作时,它所要做的就是将该工作添加到队列中并让队列处理该项目。前面代码中唯一改变的部分是队列类型。如果生产者生成的任务需要按特定顺序执行,则使用串行队列。如果生产者生成的任务可以同时执行,则将它们添加到并发队列中,让系统同时执行尽可能多的任务。

替换信号量代码

如果您当前正在使用信号量来限制对共享资源的访问,则应考虑使用dispatch信号量。传统的信号量总是需要调用内核来测试信号量。相反,调度信号量在用户空间中快速测试信号量状态,并且仅在测试失败并且需要阻塞调用线程时陷入内核。在无争议的情况下,此行为导致调度信号量比传统信号量快得多。但是,在所有其他方面,调度信号量提供与传统信号量相同的行为。

有关如何使用调度信号量的示例,请参阅使用调度信号量来调节有限资源的使用

替换run-loop代码

如果您使用run loop来管理正在一个或多个线程上执行的工作,您可能会发现队列更容易实现和维护。设置自定义运行循环涉及设置底层线程和run loop本身。运行循环代码包括设置一个或多个运行循环源以及编写回调以处理到达这些源的事件。您可以简单地创建一个串行队列并将任务分派给它,而不是完成所有工作。因此,您可以使用一行代码替换所有线程和运行循环创建代码:

dispatch_queue_t myNewRunLoop = dispatch_queue_create(“com.apple.MyQueue”,NULL);

由于队列自动执行添加到其中的任何任务,因此不需要额外的代码来管理队列。您不必创建或配置线程,也不必创建或附加任何运行循环源。此外,您只需向其添加任务即可在队列上执行新类型的工作。要使用运行循环执行相同的操作,您需要修改现有的运行循环源或创建一个新的运行循环源来处理新数据。

运行循环的一种常见配置是处理在网络套接字上异步到达的数据。您可以将调度源附加到所需的队列,而不是为此类行为配置运行循环。与传统的运行循环源相比,调度源还提供了更多处理数据的选项。除了处理计时器和网络端口事件外,您还可以使用调度源来读取和写入文件,监视文件系统对象,监视进程和监视信号。您甚至可以定义自定义调度源,并从代码的其他部分异步触发它们。有关设置调度源的更多信息,请参阅调度源

与POSIX线程的兼容性

由于Grand Central Dispatch管理您提供的任务与运行这些任务的线程之间的关系,因此通常应避免从任务代码中调用POSIX线程例程。如果您确实需要出于某种原因调用它们,则应该非常小心您调用的例程。本节向您提供可以安全调用哪些例程以及从排队任务调用哪些例程不安全的说明。此列表不完整,但应该告诉您什么是安全的,哪些不是。

通常,您的应用程序不得删除或改变它未创建的对象或数据结构。因此,使用调度队列执行的块对象不得调用以下函数:

  • pthread_detach

  • pthread_cancel

  • pthread_join

  • pthread_kill

  • pthread_exit

虽然在任务运行时修改线程状态是可以的,但必须在任务返回之前将线程返回到其原始状态。因此,只要将线程返回到其原始状态,就可以安全地调用以下函数:

  • pthread_setcancelstate

  • pthread_setcanceltype

  • pthread_setschedparam

  • pthread_sigmask

  • pthread_setspecific

用于执行给定块的底层线程可以从调用更改为调用。因此,您的应用程序不应该依赖以下函数在块的调用之间返回可预测的结果:

  • pthread_self

  • pthread_getschedparam

  • pthread_get_stacksize_np

  • pthread_get_stackaddr_np

  • pthread_mach_thread_np

  • pthread_from_mach_thread_np

  • pthread_getspecific

重要提示:  块必须捕获并抑制其中抛出的任何语言级异常。在执行块期间发生的其他错误应该类似地由块处理或用于通知应用程序的其他部分。

 

有关POSIX线程和本节中提到的函数的更多信息,请参见pthread手册页。

 

有关如何使用调度组的示例,请参阅等待排队任务组。有关在操作对象之间设置依赖关系的信息,请参阅配置互操作依赖关系

原文https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ThreadMigration/ThreadMigration.html#//apple_ref/doc/uid/TP40008091-CH105-SW1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本火锅店点餐系统采用Java语言和Vue技术,框架采用SSM,搭配Mysql数据库,运行在Idea里,采用小程序模式。本火锅店点餐系统提供管理员、用户两种角色的服务。总的功能包括菜品的查询、菜品的购买、餐桌预定和订单管理。本系统可以帮助管理员更新菜品信息和管理订单信息,帮助用户实现在线的点餐方式,并可以实现餐桌预定。本系统采用成熟技术开发可以完成点餐管理的相关工作。 本系统的功能围绕用户、管理员两种权限设计。根据不同权限的不同需求设计出更符合用户要求的功能。本系统中管理员主要负责审核管理用户,发布分享新的菜品,审核用户的订餐信息和餐桌预定信息等,用户可以对需要的菜品进行购买、预定餐桌等。用户可以管理个人资料、查询菜品、在线点餐和预定餐桌、管理订单等,用户的个人资料是由管理员添加用户资料时产生,用户的订单内容由用户在购买菜品时产生,用户预定信息由用户在预定餐桌操作时产生。 本系统的功能设计为管理员、用户两部分。管理员为菜品管理、菜品分类管理、用户管理、订单管理等,用户的功能为查询菜品,在线点餐、预定餐桌、管理个人信息等。 管理员负责用户信息的删除和管理,用户的姓名和手机号都可以由管理员在此功能里看到。管理员可以对菜品的信息进行管理、审核。本功能可以实现菜品的定时更新和审核管理。本功能包括查询餐桌,也可以发布新的餐桌信息。管理员可以查询已预定的餐桌,并进行审核。管理员可以管理公告和系统的轮播图,可以安排活动。管理员可以对个人的资料进行修改和管理,管理员还可以在本功能里修改密码。管理员可以查询用户的订单,并完成菜品的安排。 当用户登录进系统后可以修改自己的资料,可以使自己信息的保持正确性。还可以修改密码。用户可以浏览所有的菜品,可以查看详细的菜品内容,也可以进行菜品的点餐。在本功能里用户可以进行点餐。用户可以浏览没有预定出去的餐桌,选择合适的餐桌可以进行预定。用户可以管理购物车里的菜品。用户可以管理自己的订单,在订单管理界面里也可以进行查询操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值