Clozure CL中文版011:用线程编程

  • 0

Clozure CL中文版011:用线程编程

Category:帮助手册 Tags : 

用线程编程

线程概述

Clozure CL提供了支持多线程执行的工具(线程,有时叫 轻量级流程 要不就 流程虽然后一个术语不应该与操作系统的进程概念相混淆,但在lisp会话中。本文档描述了与Clozure CL中多线程编程相关的设施和问题。

只要有可能,我会尝试使用术语“thread”来表示lisp线程,即使API中的许多函数在其名称中都有“process”一词。一个 Lisp的过程 是一个lisp对象(类型为CCL:PROCESS),用于控制底层对象并与之通信 本机线程。有时,这两个(完全不同的)对象之间的区别可能会模糊; 其他时候,保持这一点非常重要。

Lisp线程共享相同的地址空间,但维护自己的执行上下文(堆栈和寄存器)以及它们自己的动态绑定上下文。

传统上,Clozure CL的主题是 合作安排:通过编译器和运行时支持的组合,当前正在执行的lisp线程被安排在其执行中的某些离散点处被中断(通常在进入函数时和任何循环结构的开头)。这个中断每秒发生几十次; 作为响应,处理函数可能会观察到当前线程已用完其时间片和另一个函数(lisp调度程序)将被调用以查找处于可运行状态的其他线程,暂停当前线程的执行,并继续执行新执行的线程。在传出和传入线程之间切换上下文的过程发生在Lisp和汇编语言代码的混合中; 就操作系统而言,在Lisp镜像中运行了一个本机线程,其堆栈指针和其他寄存器恰好偶尔会发生变化。

在Clozure CL的协作调度模型下,可以(通过使用CCL:WITHOUT-INTERRUPTS结构)推迟处理调用lisp调度程序的周期性中断; 使用WITHOUT-INTERRUPTS来获得对全局数据结构的安全,独占访问并不罕见。在一些代码中(包括Clozure CL本身的大部分内容)这个习惯用法非常普遍:它被认为是一种在短时间内禁止执行其他线程的有效方法。

驱动协作调度程序的计时器中断只能(伪)抢占lisp代码:如果任何线程称为阻塞OS I / O函数,则在该线程恢复执行lisp代码之前不能调度其他线程。Lisp库函数通常适应这种约束,并且在尝试解决它时进行了轮询和“定时阻塞”的复杂混合。不用说,这段代码很复杂,效率也低于它可能; 这意味着当它“无所事事”(等待I / O成为可能)时,它会比它应该更加繁忙。

出于各种原因 – 在单处理器系统和多处理器系统上更好地利用CPU资源以及更好地与OS集成–Clozure CL 0.14及更高版本中的线程是 预先安排。 在此模型中,lisp线程是本机线程,涉及它们的所有调度决策都是由OS内核完成的。(这些决定可能涉及在SMP系统上的多个处理器上同时调度多个lisp线程。)此更改具有许多微妙的影响:

  • 两个(或更多)lisp线程可能同时执行,可能尝试访问和/或修改相同的数据结构。无论调度建模效果如何,这种访问确实应该通过使用同步对象来协调; 预先计划的线程增加了在错误的时间出错的可能性,并且不提供使用这些同步对象的轻量级替代方案。
  • 即使在单处理器系统上,上下文切换也可能发生在任何指令边界上。由于(通常)其他线程可能会分配内存,这意味着GC可以在任何指令边界有效地发生。这主要是编译器和运行时系统需要注意的问题,但这意味着现在不鼓励那些始终不鼓励的某些实践(例如尝试将lisp对象的地址传递给外部代码)……激烈地说。
  • 没有简单有效的方法来“禁止调度程序”或以其他方式获得对整个CPU的独占访问权限。
  • 有多种简单有效的方法可以同步对特定数据结构的访问。

作为一个广泛的概括:可能需要重新设计协调调度程序约束的代码,以便与抢占式调度程序一起使用(编写为在Clozure CL的本机调度程序接口下运行的代码可能不太容易移植到其他CL实现,其中许多提供协作调度程序和类似于Clozure CL(<0.14)的API。)同时,两个调度模型的功能有很大的重叠,并且希望有可能写有趣且有用的MP代码,它在很大程度上独立于底层的调度细节。

关键字:OPENMCL-NATIVE-THREADS在0.14及更高版本中的* FEATURES *上,可以在需要时用于条件化。

(故意)缺少功能

上面描述的大部分功能类似于Clozure CL的协作调度程序提供的功能,其中一些其他部分在本机线程实现中没有意义。

  • 流程 – 运行 – 原因和流程 – 阻止 – 原因是SETFable流程属性; 每个只是一个任意令牌列表。如果一个线程的逮捕原因列表为空且其运行原因列表不是,则该线程有资格进行调度(大致相当于“启用”)。我不认为鼓励编程风格是合适的,否则定期启用和禁用可运行的线程(如果线程无法有效地占用时间,则最好等待某种同步事件发生。)
  • 有许多用于维护进程队列的原语;这就是操作系统的工作。
  • 协作线程基于与STACK-GROUP类型的对象相关联的协同处理原语。STACK-GROUPs没有长寿。

实施决定和开放式问题

线程堆栈大小

使用MAKE-PROCESS创建线程时,可以指定堆栈大小。Clozure CL不会对您选择的堆栈大小施加限制,但有一些证据表明,选择大于操作系统限制的堆栈大小会导致过多的分页活动,至少在某些操作系统上是这样。

最大堆栈大小取决于操作系统。您可以使用shell命令确定平台上的内容。在bash中,使用“ulimit -s -H”来查找限制; 在tcsh中,使用“limit -h s”。

此问题不会影响使用默认堆栈大小创建线程的程序,您可以通过为MAKE-PROCESS的:stack-size参数指定no值,或者通过指定值CCL :: * default-control-stack来执行此操作-尺寸*。

如果程序创建具有指定堆栈大小的线程,并且该大小大于OS指定的限制,则可能需要考虑减小堆栈大小以避免可能的过多分页活动。

截至2003年8月:

  • 目前尚不清楚暴露PROCESS-SUSPEND / PROCESS-RESUME是一个好主意:目前尚不清楚它们是否提供了获胜方式,而且很明显它们提供了失败的方法。
  • 传统上可以重置并启用“耗尽”的过程。(这里使用的术语“耗尽”意味着进程的初始函数已经运行并返回,并且底层本机线程已被释放。)PROCESS-RESET的一个主要用途是“循环”线程; 启用耗尽的过程涉及创建一个新的本机线程(以及堆栈和同步对象……),这就是这种回收方案试图避免的开销。可能值得尝试收紧并声明将PROCESS-ENABLE应用于耗尽的线程是错误的(并使PROCESS-ENABLE检测到此错误。)
  • 当Clozure CL未创建的本机线程首先调用lisp时,会创建一个“外部进程”,并为该进程提供自己的一组初始绑定,并设置为看起来像MAKE创建的进程-处理。外部进程的生命周期肯定不同于lisp创建的生命周期:重置/预设/启用外部进程没有意义,应该检测尝试执行这些操作并将其视为错误。

从旧线程模型移植代码

较旧版本的Clozure CL使用了通常称为“用户模式线程”的东西,这是一种不太通用的线程模型,不需要操作系统的特定支持。本节讨论如何移植为该模式编写的代码。

很难给出逐步说明; 当然应该仔细研究一些事情:

  • 明智的做法是怀疑大多数没有中断的用途; 可能有例外,但WITHOUT-INTERRUPTS通常用作WITH-APPROPRIATE-LOCKING的简写。确定适当的锁定类型并编写代码来实现它可能在大多数情况下都是简单明了的。
  • 我只看过一个案例,其中一个进程的“运行原因”用于传递信息以及控制执行; 我不认为这是一个常见的习语,但可能会误解为此。
  • 对于长期可靠运行的协同调度lisps编写的程序当然可能是偶然的:资源争用问题往往是时序敏感的,并且将线程调度与lisp程序执行分离会影响时序。我知道Clozure CL和商业MCL中都存在或者是代码是在明确假设某些开放编码操作序列不可中断的情况下编写的。应用程序开发人员可能会(明确地或以其他方式)做出相同的假设。

背景终端输入

概观

除非Clozure CL提供替代方案(通过窗口流,telnet流或其他机制),否则所有lisp进程共享一个公共的* TERMINAL-IO *流(因此共享* DEBUG-IO *,* QUERY-IO *和其他标准和内部互动流。)

预计除“初始”流程之外的大多数lisp流程主要在后台运行。如果后台进程写入* TERMINAL-IO *的输出端,这可能有点混乱并且对用户来说有点混乱,但它不应该是真正的灾难性的。Clozure CL的缓冲流的所有I / O都通过锁定机制来防止最严重的资源争用问题。

尽管与来自多个过程的终端输出相关联的问题可能主要是装饰性的,但是哪个过程从终端接收输入的问题可能是非常重要的。流锁定机制可能使令人困惑的情况更加糟糕:竞争进程可能会“窃取”彼此的终端输入,除非锁被保持的时间长于其他需要的时间,并且锁可以保持比他们需要的更长的时间(如同进程只是等待输入在底层文件描述符上可用)。

即使后台进程很少需要故意从终端读取输入,他们仍可能需要这样做以响应错误或其他意外情况。在解决这个问题的任何解决方案中都存在权衡。下面描述的协议允许跟随它的后台进程可靠地提示和接收终端输入。尝试在不遵循该协议的情况下接收终端输入的后台进程可能在尝试这样做时无限期地挂起。这当然是一个严厉的权衡,但由于尝试在不遵循此协议的情况下读取终端输入仅在某些时候工作,所以它似乎不是一个不合理的。

在这里描述的解决方案中(并在Clozure CL 0.9中引入),用于提供终端输入的内部流总是被某个进程(“拥有”进程)锁定。初始进程(通常运行read-eval-的进程) print loop)在第一次创建时拥有该流。通过使用宏WITH-TERMINAL-INPUT,后台进程可以暂时获得终端的所有权,并在完成后将所有权交给前一个所有者。

在Clozure CL,BREAK,ERROR,CERROR,Y-OR-NP,YES-OR-NO-P和CCL:GET-STRING-FROM-USER都是根据WITH-TERMINAL-INPUT定义的,如下所示: TTY用户界面与STEP和INSPECT。

一个例子

? Welcome to Clozure CL Version (Beta: linux) 0.9!

?

 

? (process-run-function “sleeper” #'(lambda () (sleep 5) (break “broken”)))

#<PROCESS sleeper(1) [Enabled] #x3063B33E>

 

?

;;

;; Process sleeper(1) needs access to terminal input.

;;

这个例子是在ILISP下运行的; 如果试图输入输入并且“点”不遵循提示,则ILISP经常会混淆。此时输入“简单”表达式使其恢复同步; 否则与此示例无关。

()NIL? (:y 1);;;; process sleeper(1) now controls terminal input;;> Break in process sleeper(1): broken> While executing: #<Anonymous Function #x3063B276>> Type :GO to continue, :POP to abort.> If continued: Return from BREAK.Type 😕 for other options.1 > :b(30C38E30) : 0 “Anonymous Function #x3063B276” 52(30C38E40) : 1 “Anonymous Function #x304984A6” 376(30C38E90) : 2 “RUN-PROCESS-INITIAL-FORM” 340(30C38EE0) : 3 “%RUN-STACK-GROUP-FUNCTION” 7681 > :pop;;;; control of terminal input restored to process Initial(0);;?

一个更详细的例子。

如果后台进程(“A”)需要访问终端输入流并且该流由另一个后台进程(“B”)拥有,则进程“A”宣布该事实,然后等待直到初始进程重新获得控制。

? Welcome to Clozure CL Version (Beta: linux) 0.9!

?

 

? (process-run-function “sleep-60” #'(lambda () (sleep 60) (break “Huh?”)))

#<PROCESS sleep-60(1) [Enabled] #x3063BF26>

 

? (process-run-function “sleep-5” #'(lambda () (sleep 5) (break “quicker”)))

#<PROCESS sleep-5(2) [Enabled] #x3063D0A6>

 

?       ;;

;; Process sleep-5(2) needs access to terminal input.

;;

()

NIL

 

? (:y 2)

;;

;; process sleep-5(2) now controls terminal input

;;

> Break in process sleep-5(2): quicker

> While executing: #x3063CFDE>

> Type :GO to continue, :POP to abort.

> If continued: Return from BREAK.

Type 😕 for other options.

1 >     ;; Process sleep-60(1) will need terminal access when

;; the initial process regains control of it.

;;

()

NIL

1 > :pop

;;

;; Process sleep-60(1) needs access to terminal input.

;;

;;

;; control of terminal input restored to process Initial(0)

;;

 

? (:y 1)

;;

;; process sleep-60(1) now controls terminal input

;;

> Break in process sleep-60(1): Huh?

> While executing: #x3063BE5E>

> Type :GO to continue, :POP to abort.

> If continued: Return from BREAK.

Type 😕 for other options.

1 > :pop

;;

;; control of terminal input restored to process Initial(0)

;;

 

?

概要

这个方案当然不是防弹的:过程中断和类似功能的富有想象力的使用可能能够击败它并使lisp死锁,并且几个后台进程同时要求访问共享终端输入流的任何场景都可能令人困惑和混乱。(另一种方案,由于技术限制,输入焦点被神奇地授予用户正在考虑的任何线程,因此被考虑和拒绝。)

长期修复可能涉及使用网络或窗口系统流为每个进程提供* TERMINAL-IO *的唯一实例。

尝试从后台进程读取* TERMINAL-IO *的现有代码需要更改为使用WITH-TERMINAL-INPUT。由于该代码在以前版本的Clozure CL中可能无法可靠地运行,因此这个要求似乎并不太繁琐。

请注意,WITH-TERMINAL-INPUT都请求终端输入流的所有权,并承诺在完成后将该所有权恢复到初始进程。临时使用READ或READ-CHAR并不能实现这一承诺; 这是限制:Y命令的基本原理。

Clozure CL用于其自身目的的线程

在“tty world”中,Clozure CL以2个lisp级别的线程开始:

? :proc1 : -> listener     [Active]0 :    Initial      [Active]

如果你看一个正在运行的Clozure CL和一个调试工具,比如GDB或Apple的Thread Viewer.app,你会在Darwin上看到一个额外的内核级线程; 这是由Mach异常处理机制使用的。

初始线程,方便地命名为“initial”,是操作系统在启动Clozure CL时创建的线程。它将堆映像映射到内存中,进行一些Lisp级初始化,并且,当不使用Cocoa IDE时,创建线程“listener”,它运行读取输入,评估它并打印的顶级循环。结果。

在创建监听器线程之后,初始线程执行“内务处理”:它处于循环中,大部分时间处于休眠状态并偶尔唤醒以执行“定期任务”。这些任务包括强制在指定的交互流上输出,检查和处理控制-C中断等。目前,这些任务还包括轮询外部进程的退出状态以及处理与这些进程之间的某些I / O.

在这种环境中,初始线程会根据需要执行这些“内务”活动,直到ccl:quit被调用为止 ; quitting中断初始线程,然后以尽可能有序的方式结束所有其他线程并调用C函数#_exit。

短期计划是在专用线程中处理每个外部流程; 在某些情况下等待外部进程终止时,当前方案的最坏情况行为可能涉及忙等待和过多的CPU利用率。

Cocoa功能使用更多线程。添加Cocoa侦听器会创建两个线程:

? :proc      3 : -> Listener     [Active]      2 :    housekeeping  [Active]      1 :    listener     [Active]      0 :    Initial      [Active]

Cocoa事件循环必须在初始线程中运行; 当事件循环启动时,它会创建一个新线程来执行“内务”任务,初始线程将在仅终端模式下执行该任务。然后,初始线程成为从窗口服务器接收所有Cocoa事件的线程; 它是唯一可以的线程。

它还为每个监听器窗口创建一个“监听器”(大写L)线程,其生命周期与线程一样长。所以,如果你打开第二个监听器,你会看到五个线程:

? :proc      4 : -> Listener-2   [Active]      3 :    Listener     [Active]      2 :    housekeeping  [Active]      1 :    listener     [Active]      0 :    Initial      [Active]

Unix信号,如SIGINT(control-C),调用Lisp内核安装的处理程序。虽然操作系统没有对哪个线程接收信号做出任何具体保证,但实际上,它似乎是最初的线程。处理程序只是设置一个标志并返回; 管家线程(可能是初始线程,如果没有使用Cocoa)将检查该标志并采取适合该信号的任何动作。

在SIGINT的情况下,操作是通过调用被中断的线程来进入中断循环。当有多个Lisp监听器处于活动状态时,并不总是清楚应该是什么线程,因为它实际上取决于用户的意图,这是无法以编程方式进行神圣的。为了做出最好的猜测,处理程序首先检查值ccl:*interactive-abort-process*是否为线程,如果是,则使用它。如果失败,则选择当前“拥有”默认终端输入流的线程; 看。

在基于Hemlock(一种类似Emacs的编辑器)的最新版本的Cocoa支持中,每个编辑器窗口都有一个与之关联的专用线程。当一个按键事件进入影响该特定窗口时,初始线程将其发送到窗口的专用线程。专用线程负责尝试将按键解释为Hemlock命令,将这些命令应用于活动缓冲区; 它在一个循环中重复这个,直到窗口关闭。初始线程处理所有其他事件,例如鼠标单击和拖动。

这种每个窗口的线程方案使许多事情变得更简单,包括在“增量搜索转发”等命令中输入“递归命令循环”的过程等。(可能有可能处理Cocoa事件线程中的所有Hemlock命令,但是这些“递归命令循环”必须维护大量的上下文/状态信息;线程是维护该信息的直接方式。)

目前(2004年8月),当专用线程需要改变缓冲区或选择的内容时,它通过调用初始线程中的方法来实现同步目的,但这可能是过度的,很可能会被更多未来的有效方案。

对于绘制和处理屏幕,每窗口线程可能比现在更负责; – 在某些情况下,需要做一些事情来缓冲屏幕更新:你不需要看到缩进之类的事情; 你需要看到结果……

当使用Hemlock时,监听器窗口是编辑器窗口,因此除了每个“监听器”线程之外​​,您还应该看到一个处理Hemlock命令处理的线程。

Cocoa运行时可以在某些特殊情况下创建额外的线程; 这些线程通常不运行lisp代码,并且很少运行大部分代码。

线程词典

all-processes[功能]

返回Clozure CL已知的所有lisp进程(线程)的新列表,以及它所调用的精确瞬间。由于其他线程可以随时创建和终止线程,因此无法获得所有线程的完美准确列表。

make-process name &key persistent priority class initargs stack-size vstack-size tstack-size initial-bindings use-standard-initial-bindings           [功能]

创建并返回一个新进程。

名称

一个字符串,用于标识进程。

一贯

如果为true,则请求SAVE-APPLICATION保留有关进程的信息,以便在运行保存的映像时可以重新启动等效进程。默认值为nil。

优先

忽略。当然不应该忽略它,但在某些平台上存在复杂性。默认值为0。

要创建的进程对象的类; 应该是CCL的子类:PROCESS。默认值为CCL:PROCESS。

initargs

要传递给MAKE-INSTANCE的任何其他initarg。默认值为()。

堆栈大小

新创建的进程控制堆栈的大小(以字节为单位); 用于外部函数调用和保存函数返回地址上下文。默认值为CCL:* DEFAULT-CONTROL-STACK-SIZE *。

vstack大小

新创建的进程的值栈的大小(以字节为单位); 用于lisp函数参数,局部变量和其他堆栈分配的lisp对象。默认值为CCL:* DEFAULT-VALUE-STACK-SIZE *。

T堆大小

新创建的进程临时堆栈的大小(以字节为单位); 用于动态范围对象的分配。默认值为CCL:* DEFAULT-TEMP-STACK-SIZE *。

使用标准初始绑定

当为true时,全局“标准初始绑定”在之前的新线程中生效。参见DEF-STANDARD-INITIAL-BINDING。“标准”初始绑定在由以下内容指定的任何绑定之前生效:initial-bindings。默认值为t。

不推荐使用此选项:许多Clozure CL组件的正确行为取决于许多特殊变量的线程局部绑定。

初始绑定

的(一个ALIST 符号valueform)对,其可用于在新的线程来初始化特殊变量绑定。每个valueform用于计算新创建的线程的执行环境中符号的新绑定的值。默认值为nil。

处理

新创建的流程。

创建并返回具有指定属性的新lisp进程(线程)。进程不会立即开始执行; 它需要 预置(给定一个初始函数来运行,如 process-preset)和 启用 (在允许执行process-enable之前)允许执行任何操作。

如果valueform是一个函数,则在新创建的线程的执行环境中调用它,不带参数; 它返回的主值用于绑定相应的符号

否则,在新创建的线程的执行环境中评估valueform,并使用结果值。

process-presetprocess-enableprocess-run-function

process-suspend process             [功能]

暂停指定的进程。

处理

一个lisp进程(线程)。

结果

如果流程已经可以运行并且现在已暂停; 否则没有。也就是说,T如果 过程process-suspend-count 转变由0到1。

暂停进程,阻止其运行,并在已运行时停止进程。这是一个相当昂贵的操作,因为它涉及对操作系统的一些调用。如果使用不当,它也有可能造成死锁,例如,如果暂停的进程拥有另一个进程将等待的锁或其他资源。

每次调用process-suspend必须相应地调用逆转process-resume 之前进程能够运行。什么 process-suspend实际上做的是递增process-suspend-count过程

一个过程不能暂停,虽然这曾经有效,而且该文档声称它已经声明了。

process-resumeprocess-suspend-count

process-suspend之前被称为 process-disable。 process-enable 现在命名一个没有明显反转的函数,因此process-disable 不再定义。

process-resume process         [功能]

恢复先前由进程挂起暂停的指定进程。

处理

一个lisp进程(线程)。

结果

如果流程已暂停且现在可以运行,则为T ; 否则没有。也就是说,T如果 处理process-suspend-count 从0转换。

撤消先前调用的效果 process-suspend; 如果撤消所有此类调用,则使该进程可运行。如果该过程未被暂停,则无效。什么 process-resume实际上做的是递减process-suspend-count过程,到最低的0。

process-suspendprocess-suspend-count

这之前称为PROCESS-ENABLE; process-enable现在做的事略有不同。

process-suspend-count process           [功能]

返回适用于给定进程的当前挂起的挂起数。

处理

一个lisp进程(线程)。

结果

流程process-suspend上的“未完成” 调用次数 ,如果流程已过期,则为NIL。

一个“未完成”的process-suspend电话是一个尚未被电话拨打的电话 process-resume。一个进程在其初始函数返回时到期,但稍后可能会重置。

一个过程是 可运行当它的a process-suspend-count为0时,已被预设为process-preset,并且已被启用为process-enable。新创建的进程的a process-suspend-count为0。

process-suspendprocess-resume

process-preset process function &rest args         [功能]

设置指定进程的初始函数和参数。

处理

一个lisp进程(线程)。

功能

一个函数,由其自身或由命名它的符号指定。

ARGS

值的列表,适合作为参数传递给 函数

结果

未定义。

通常用于初始化新创建或新重置的进程,进行设置,以便在 启用进程时,通过将函数应用于args开始执行。 process-preset不启用进程,但必须process-preset 先启用进程才能启用 进程。进程通常由启用 process-enable

make-processprocess-enableprocess-run-function

process-enable process &optional timeout         [功能]

开始执行指定进程的初始函数。

处理

一个lisp进程(线程)。

时间到

以秒为单位的时间间隔。可以是任何非负实数floor,其中32位适合。默认值为1。

结果

未定义。

试图开始执行流程。如果从未进行过程,则会发出错误信号process-preset。否则,process调用其初始函数。

process-enable尝试与进程同步,该进程被假定为重置或重置自身。如果此尝试在超时指定的时间间隔内未成功 ,则会发出可持续错误信号,从而提供继续等待的机会。

流程无法有意义地尝试启用自身。

make-processprocess-presetprocess-run-function

如果能够更多地讨论与流程同步意味着什么,那就太好了。

process-run-function process-specifier function &rest args[功能]

创建流程,预设流程并启用流程。

流程说明符

name | (&key namepersistentpriorityclassinitargsstack-sizevstack-sizetstack-size)

名称

一个字符串,用于标识进程。传递给make-process

功能

一个函数,由其自身或由命名它的符号指定。传递给 process-preset

一贯

一个布尔值,传递给make-process

优先

忽略。

CCL的子类:PROCESS。传递给 make-process

initargs

要传递给的任何其他initarg的列表 make-process

堆栈大小

大小,以字节为单位。传递给 make-process

vstack大小

大小,以字节为单位。传递给 make-process

T堆大小

大小,以字节为单位。传递给 make-process

处理

新创建的流程。

创建一个lisp进程(线程),通过 make-process它预设process-preset,并通过它启用它process-enable。这意味着流程将立即开始执行。 process-run-function是创建和运行流程的最简单方法。

make-processprocess-presetprocess-enable

process-interrupt process function &rest args        [功能]

安排目标进程在不久的将来某个时刻调用指定的函数,然后返回它正在做的事情。

处理

一个lisp进程(线程)。

功能

一个功能。

ARGS

值的列表,适合作为参数传递给 函数

结果

如果进程 是,则将函数 应用于args的结果,否则为NIL。*current-process*

安排工艺 应用功能ARGS在不久的将来的某个时候(地中断 过程 在做什么。)如果功能正常返回,进程继续在在它被中断的点执行。

进程必须处于启用状态才能响应process-interrupt请求。对于一个process-interrupt自我调用的过程来说,这是完全合法的。

process-interrupt 使用异步POSIX信号来中断线程。如果被中断的线程正在执行lisp代码,它几乎可以立即响应中断(一旦完成伪原子操作,如consing和堆栈帧初始化)。

如果被中断的线程在系统调用中被阻塞,则该信号将中止该系统调用,并在返回时处理该中断。

仍然很难可靠地中断任意外来代码(可能是有状态的或不可重入的); 当这些外部代码返回或进入lisp时,处理中断请求。

without-interrupts

结果 总是NIL 可能会更好,因为目前的行为是不一致的。

process-interrupt通过C函数在线程之间发送信号 #_pthread_signal。可以说,它应该在达尔文的几种可能的其他方式中完成,以使异步中断大量使用Mach超微内核的事情变得切实可行。

*current-process*[变量]

在每个过程中单独绑定到该过程本身。当lisp代码需要找出它正在执行的进程时,可以使用它。它不应该由用户代码设置。

process-reset process &optional kill-option[功能]

此函数使进程干净地退出任何正在进行的计算,并进入可能的状态process-preset

这是通过发信号通知PROCESS-RESET类型的条件来实现的; 用户定义的条件处理程序通常应避免尝试处理此类条件。

杀选项参数仅供内部使用,不应由用户代码指定。

一个过程可以有意义地重置自己。

通常无法确切地知道过程何时完成了重置或自杀的行为; 一个进入重置状态或退出的过程几乎没有任何方式来传达这两个事实。

该函数process-enable可以可靠地确定进程何时进入重置状态的限制,但无法预测正在进行的计算的干净退出可能需要多长时间:这取决于unwind-protect清理表单和OS调度程序的行为。

重置除了*current-process* 涉及使用该功能之外的过程process-interrupt

process-reset-and-enable process          [功能]

重置并启用指定的进程,该进程可能不是当前进程。

处理

一个lisp进程(线程),它可能不是当前进程。

结果

未定义。

相当于调用(进程重置进程)和(进程启用进程)。

process-resetprocess-enable

process-kill process       [功能]

导致进程从任何正在进行的计算中干净地退出,然后退出。请注意,unwind-protect 清除表单将在禁用中断的情况下运行。

process-abort process &optional condition        [功能]

使指定的进程处理中止条件,就像它已被调用一样 abort。

处理

一个lisp进程(线程)。

条件

一个lisp条件。默认值为NIL。

完全等同于调用(process (()(condition)))。导致进程将控制权转移到适用的处理程序或重新启动。process-interruptlambdaabortabort

如果condition是非NIL,process-abort则不考虑任何明确绑定到条件以外的条件的处理程序。

process-resetprocess-kill

*ticks-per-second*[变量]

绑定到OS调度程序的时钟分辨率。

正整数。

OS调度程序的时钟分辨率。目前,LinuxPPC和DarwinPPC都产生了100的初始值。

这个值通常只是边际利益,但是,为了向后兼容,一些函数接受以“ticks”表示的超时值。该值给出每秒的滴答数。

process-wait-with-timeout

process-whostate process[功能]

返回描述指定进程状态的字符串。

处理

一个lisp进程(线程)。

whostate

一个描述过程 “状态”的字符串。

此信息主要用于调试工具。 whostate是关于过程正在做什么或不做什么以及为什么做的简洁报告。

如果进程当前正在等待调用 process-waitor process-wait-with-timeout,则 process-whostate它将是作为whostate传递给该函数的

process-waitprocess-wait-with-timeoutwith-terminal-input

这应该可以说是SETFable,但似乎从来没有。

process-allow-schedule[功能]

用于协同多任务处理; 可能永远不必要。

向操作系统调度程序建议当前线程没有任何用处,并且它应该尝试找到一些其他线程来安排它的位置。几乎总有一个更好的选择,例如等待某些特定事件发生。例如,您可以使用锁或信号量。

make-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

这是合作多任务时代的延续。所有现代通用操作系统都使用抢先式多任务处理。

process-wait whostate function &rest args[功能]

导致当前lisp进程(线程)等待给定谓词返回true。

whostate

一个字符串,它将是process-whostate 进程等待时的值。

功能

一个函数,由其自身或由命名它的符号指定。

ARGS

值的列表,适合作为参数传递给 函数

结果

零。

导致当前lisp进程(线程)重复将函数应用于 args直到调用返回true结果,然后返回NIL。在每次失败的调用之后,产生CPU就好像 process-allow-schedule

与此process-allow-schedule同样,等待某些特定事件发生几乎总是更有效率; 这并不是在忙碌等待,但如果给出相关信息,OS调度程序可以更好地进行调度。例如,您可以使用锁或信号量。

process-whostateprocess-wait-with-timeoutmake-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

process-wait-with-timeout whostate ticks function args[功能]

导致当前线程等待给定谓词返回true,或者超时到期。

whostate

一个字符串,它将是process-whostate 进程等待时的值。

表示持续时间为“ticks”(参见*ticks-per-second*)的正整数,或NIL。

功能

一个函数,由其自身或由命名它的符号指定。

ARGS

值的列表,适合作为参数传递给 函数

结果

如果process-wait-with-timeout 由于其函数返回true而返回T,如果返回则返回NIL,因为已超过持续时间 滴答

如果是NIL,行为完全一样 process-wait,除了返回T.否则,函数将被反复测试,在同一种测试/产量环的作为process-wait ,直到函数返回true,或持续时间已被超过。

已经阅读了process-allow-schedule和 的描述 process-wait,精明的读者毫无疑问地预见到应该尽可能使用更好的替代品的观察。

*ticks-per-second*process-whostateprocess-waitmake-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

without-interrupts &body body         [宏]

在延迟进程中断请求的环境中评估其主体。

身体

隐含的预测。

结果

body返回的主要值。

在 请求被延迟的环境中执行正文process-interrupt。如描述中所述 process-interrupt,这与其他线程的调度无关; process-interrupt当(例如)以某种不可重入的方式修改某些数据结构(当前线程持有适当的锁)时,可能需要禁止 处理。

process-interrupt

with-interrupts-enabled &body body          [宏]

在过程中断请求立即生效的环境中评估其主体。

身体

隐含的预测。

结果

body返回的主要值。

在 请求立即生效的环境中执行正文process-interrupt

make-lock &optional name        [功能]

创建并返回一个锁对象,该对象可用于线程之间的同步。

名称

任何lisp对象; 保存为锁的一部分。通常是一个字符串或符号,它可能出现在process-whostate等待锁定的线程中。

一个新分配的CCL类型的对象:LOCK。

创建并返回一个锁对象,该对象可用于同步对某些共享资源的访问。 最初处于“自由”状态; 锁也可以由线程“拥有”。

with-lock-grabbedgrab-lockrelease-locktry-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

with-lock-grabbed (lock) &body body              [宏]

等待直到获得给定的锁,然后在持有锁的情况下评估其身体。

CCL类型的对象:LOCK。

身体

隐含的预测。

结果

body返回的主要值。

等待锁定为空闲或由调用线程拥有,然后使用调用线程拥有的锁执行主体。如果 在调用时锁定是空闲的with-lock-grabbed,则在 执行主体后它将恢复到自由状态。

make-lockgrab-lockrelease-locktry-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

grab-lock lock        [功能]

等待直到获得给定的锁,然后获得它。

CCL类型的对象:LOCK。

阻塞直到锁定由调用线程拥有。

with-lock-grabbed可以在来定义grab-lockrelease-lock,但它是在稍低的水平实际实现。

make-lockwith-lock-grabbedrelease-locktry-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

release-lock lock       [功能]

放弃给定锁的所有权。

CCL类型的对象:LOCK。

如果锁定 尚未被调用线程拥有,则表示CCL类型的错误:LOCK-NOT-OWNER ; 否则,撤消之前的效果 grab-lock。如果这意味着 release-lock现在已经 锁定了与锁定相同的次数,grab-lock锁定 变为空闲。

make-lockwith-lock-grabbedgrab-locktry-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

try-lock lock           [功能]

获得给定的锁,但只有在没有必要等待它时才能获得。

CCL类型的对象:LOCK。

结果

如果已获得锁定,则为T ,否则为NIL。

是否测试 可以不受阻塞获得-即,或者 已经是免费的,或者它已经拥有*current-process*。如果可以,则使其由调用lisp进程(线程)拥有并返回T.否则,锁已经被另一个线程拥有,并且无法在不阻塞的情况下获得; 在这种情况下返回NIL。

make-lockwith-lock-grabbedgrab-lockrelease-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

make-read-write-lock-write-lock[功能]

创建并返回读写锁,可用于线程之间的同步。

读写锁

一个新分配的CCL类型的对象:READ-WRITE-LOCK。

创建并返回CCL :: READ-WRITE-LOCK类型的对象。在任何给定时间,读写锁可以属于充当“读取器”的任何数量的lisp进程(线程); 或者,它最多可能属于一个充当“作家”的过程。读写器可能永远不会与作者同时持有读写锁。最初,读写锁没有读者也没有编写者。

with-read-lockwith-write-lockmake-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

可能应该有某种方式原子地“促进”读者,使其成为一个作家而不释放锁定,否则可能导致延迟。

with-read-lock (read-write-lock) &body body             [宏]

等待给定的锁可用于只读访问,然后在保持锁的情况下评估其主体。

读写锁

CCL类型的对象:READ-WRITE-LOCK。

身体

隐含的预测。

结果

body返回的主要值。

等待read-write-lock没有writer,确保它*current-process*是读者,然后执行body

在执行body之后,如果 在调用之前 *current-process*不是读写锁读取器,with-read-lock则释放锁。如果它已经是读者,它仍然是一个。

make-read-write-lockwith-write-lockmake-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

with-write-lock (read-write-lock) &body body          [宏]

等待给定的锁可用于写访问,然后在保持锁的情况下执行其主体。

读写锁

CCL类型的对象:READ-WRITE-LOCK。

身体

隐含的预测。

结果

body返回的主要值。

等到read-write-lock没有读者,除了之外没有编写*current-process*,然后确保它*current-process*是它的编写者。保持锁定,执行身体

在执行body之后,如果 在调用之前 *current-process*不是 读写锁的编写器with-write-lock,则释放锁。如果它已经是作家,它仍然是作家。

make-read-write-lockwith-read-lockmake-lockmake-semaphoreprocess-input-waitprocess-output-waitwith-terminal-input

make-semaphore[功能]

创建并返回一个信号量,该信号量可用于线程之间的同步。

信号

一个新分配的CCL类型的对象:SEMAPHORE。

创建并返回CCL类型的对象:SEMAPHORE。信号量具有相关的“计数”,其可以原子递增和递减; 递增它表示发送信号,递减它表示处理该信号。 信号量的初始计数为0。

signal-semaphorewait-on-semaphoretimed-wait-on-semaphoremake-lockmake-read-write-lockprocess-input-waitprocess-output-waitwith-terminal-input

signal-semaphore semaphore          [功能]

以原子方式递增给定信号量的计数。

信号

CCL类型的对象:SEMAPHORE。

结果

表示底层OS调用返回的错误标识符的整数。

原子上将信号量的“计数”增加1; 这可以使等待线程恢复执行。

make-semaphorewait-on-semaphoretimed-wait-on-semaphoremake-lockmake-read-write-lockprocess-input-waitprocess-output-waitwith-terminal-input

结果应该被解释和执行signal-semaphore,因为它不太可能对lisp程序有意义,并且最常见的失败原因是类型错误。

wait-on-semaphore semaphore        [功能]

等到给定的信号量具有可以原子递减的正计数。

信号

CCL类型的对象:SEMAPHORE。

结果

表示底层OS调用返回的错误标识符的整数。

等到信号量 具有可以原子递减的正数; 对于SIGNAL-SEMAPHORE的每次相应调用,这将成功一次。

make-semaphoresignal-semaphoretimed-wait-on-semaphoremake-lockmake-read-write-lockprocess-input-waitprocess-output-waitwith-terminal-input

结果应该被解释和执行wait-on-semaphore,因为它不太可能对lisp程序有意义,并且最常见的失败原因是类型错误。

timed-wait-on-semaphore semaphore timeout          [功能]

等待直到给定的信号量具有可以原子递减的正计数,或者直到超时到期为止。

信号

CCL类型的对象:SEMAPHORE。

时间到

以秒为单位的时间间隔。可以是任何非负实数floor,其中32位适合。默认值为1。

结果

T如果timed-wait-on-semaphore 返回,因为它能够减少信号量的数量 ; 如果由于超过持续时间超时而返回NIL。

等待,直到信号量 具有可以原子递减的正计数,或者直到持续时间超时为止。

make-semaphorewait-on-semaphoremake-lockmake-read-write-lockprocess-input-waitprocess-output-waitwith-terminal-input

process-input-wait fd &optional timeout         [功能]

等待直到给定文件描述符上的输入可用。

FD

文件描述符,是操作系统用来指代打开文件,套接字或类似I / O连接的非负整数。见stream-device

时间到

NIL或以毫秒为单位的时间间隔。必须是非负整数。默认值为NIL。

等到fd上的输入可用。这使用select()系统调用,并且在等待输入时通常是一种相当有效的阻塞方式。更准确地说,process-input-wait 等待直到可以从fd读取而不阻塞,或者直到超时(如果它不是NIL)已被超过。

请注意,如果文件位于其末尾,则可以不阻塞地进行读取 – 当然,读取将返回零字节。

make-lockmake-read-write-lockmake-semaphoreprocess-output-waitwith-terminal-input

process-input-wait有一个超时参数,process-output-wait但没有。应该纠正这种不一致。

process-output-wait fd &optional timeout              [功能]

等待直到在给定文件描述符上输出为止。

FD

文件描述符,是操作系统用来指代打开文件,套接字或类似I / O连接的非负整数。见stream-device

时间到

NIL或以毫秒为单位的时间间隔。必须是非负整数。默认值为NIL。

等到fd可以输出或者超过超时(如果不是NIL)。这使用select()系统调用,并且在等待输出时通常是一种相当有效的阻塞方式。

如果process-output-wait在尚未建立连接的网络套接字上调用,它将等待直到建立连接。这是一个重要的用途,经常被忽视。

make-lockmake-read-write-lockmake-semaphoreprocess-input-waitwith-terminal-input

process-input-wait有一个超时参数,process-output-wait但没有。应该纠正这种不一致。

with-terminal-input &body body       [宏]

在具有对终端的独占读访问权的环境中执行其主体。

身体

隐含的预测。

结果

body返回的主要值。

请求对标准终端流的独占读访问,*terminal-io*。在具有该访问权限的环境中执行 正文

*request-terminal-input-via-break*“:Y”make-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-wait

*request-terminal-input-via-break*[变量]

控制如何获得终端输入的所有权。

布尔值。

零。

控制如何获得终端输入的所有权。当NIL时,* TERMINAL-IO *上会打印一条消息; 预计用户稍后将通过:Y toplevel命令控制终端。当T时,在拥有过程中发出BREAK条件信号; 从中断循环继续将终端发送到请求进程(除非在中断循环中已经使用:Y命令执行此操作。)

with-terminal-input“:Y”make-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-wait

( y p)[Toplevel Command]

产生对指定的lisp进程(线程)的终端输入的控制。

p

口齿不清进程(线程),通过该匹配其的整数即被指定 process-serial-number,或由一个字符串,它是equal到其process-name。

:Y是顶级命令,而不是函数。因此,它只能以交互方式使用,并且只能从初始过程中使用。

该命令产生对进程p的终端输入的控制,该进程必须用于 with-terminal-input请求访问终端输入流。

with-terminal-input*request-terminal-input-via-break*make-lockmake-read-write-lockmake-semaphoreprocess-input-waitprocess-output-wait

join-process process &optional default         [功能]

等待指定的进程完成并返回该进程的初始函数返回的值。

处理

一个过程,通常由process-run-function或由其创建make-process

默认

如果指定的进程未正常退出,则返回默认值。

如果该函数返回,则由指定进程的初始函数返回的值,否则为默认参数的值。

等待指定的进程终止。如果进程“正常”终止(如果其初始函数返回),则返回该初始函数返回的值。如果进程没有正常终止(例如,如果它通过process-kill并且提供了默认参数,则返回该默认参数的值。如果进程没有正常终止并且没有提供默认参数,则表示错误。

进程无法成功加入自身,只有一个进程可以成功接收另一个进程终止的通知。

http://mip.i3geek.com

Leave a Reply

搜索

分类目录

公 告

本网站学习论坛:

www.zhlisp.com

lisp中文学习源码:

https://github.com/zhlisp/

欢迎大家来到本站,请积极评论发言;

加QQ群学习交流。