Skip to content

Latest commit

 

History

History
251 lines (211 loc) · 24.9 KB

File metadata and controls

251 lines (211 loc) · 24.9 KB

十七、答案

第一章

  1. 应用程序和系统编程有什么区别? 应用程序编程的重点是为最终用户解决一个问题,而系统编程则是创建其他软件使用的软件。

  2. 什么是 API?为什么 API 如此重要? API 是软件公开的接口,用于控制对其控制的资源的访问。它描述了其他应用程序应如何与软件通信。

  3. 您能解释一下保护环是如何工作的吗? 保护环是用于防止故障和提高安全性的系统。它们将安全性安排在具有不断增长的限制的分层级别上,并允许通过使用特定网关对更强大级别的功能进行中间访问。

  4. 您能否提供一些在用户空间中无法执行的示例? 用户空间中的应用程序不能将其当前空间更改为内核,不能忽略文件系统访问硬盘,也不能更改页表。

  5. 什么是系统调用? 系统调用是操作系统提供的访问机器资源的 API。

  6. Unix 中使用哪些调用来管理进程? Unix 用于管理进程的调用如下:forkexitwait

  7. 为什么 POSIX 有用? 各种 POSIX 标准定义了过程控制、信号、分段、非法指令、文件和目录操作、管道、I/O 控制和 C 库、shell 和实用程序以及实时和多线程扩展。它对于开发人员在构建应用程序时非常有用,因为它有助于构建与共享此标准的不同操作系统协同工作的应用程序。

  8. Windows POSIX 兼容吗? Windows 不兼容 POSIX,但正在尝试提供 POSIX 框架,如 Windows Linux 子系统。

第二章

  1. 现代操作系统使用哪个文件系统? 现代操作系统使用不同的文件系统:Windows 和 macOS 使用各自的专有格式 NTFS 和 APFS,而 Linux 系统主要使用 EXT4。
  2. 什么是 inode?Unix 中的 inode0是什么? inode 是表示文件的文件系统数据结构。它存储有关文件的信息,不包括名称和数据。 inode0是为/文件夹保留的。
  3. PID 和 PPID 有什么区别? PID 是现有流程的唯一标识符,而 PPID 是父流程的标识符。当现有流程创建另一个流程时,新流程的 PPID 等于现有流程的 PID。
  4. 如何终止后台运行的进程? 虽然可以通过按Ctrl+CSIGINT信号发送到前台处理,但对于后台处理,需要使用kill命令发送信号,在这种情况下为kill -2 PID
  5. 用户和组之间有什么区别? 用户识别可以拥有文件和进程的帐户,而组是共享文件权限的机制。
  6. What's the scope of the Unix permission model? The Unix permission model enables the restriction of access to a file with three different levels of power: owner, group, and all other users.
  7. Can you explain the difference between signals and exit codes? Signals and exit codes are both methods of communication between processes, but whereas a signal is from any process to another, exit codes are used to communicate from a child to its parent.
  8. What's a swap file? A swap file is an extension of the physical memory that is used to store pages that are not required in order to free up the main memory.

第三章

  1. What's the difference between an exported and an unexported symbol? Exported symbols can be used by other packages, whereas unexported symbols cannot. The first group has an identifier that starts with a capital letter, while the second group does not.
  2. Why are custom types important? Custom types allow methods to be defined and interfaces to be used effectively, or the data structure of another type to be inherited, but getting rid of its methods.
  3. What is the main limit of a short declaration? Short declarations do not allow the variable type inferred by the value to be defined. A type casting of the value enables this limitation to be overcome.
  4. What is scope and how does it affect variable shadowing? The scope of a variable represents its lifetime and visibility that can be package, function, or blocked. Shadowing is when the same identifier gets used in an inner scope, preventing access to the symbol that shares that identifier by an outer scope.
  5. How can you access a method? Methods are special types of functions that have a namespace linked to the type they belong to. They can be accessed as a property of an instance of their type, or as a property of the type itself, passing the instance as a first argument.
  6. Explain the difference between a series of if/else statements and a switch statement. A series of if and else statements allow the execution of a short declaration for each if statement and will execute only one of the cases, skipping the following declarations. A switch statement allows only one declaration, and can modify the flow using continue and break statements.
  7. In a typical use case, who is generally responsible for closing a channel? Channels should always be closed by the sender, because that party is responsible for communicating that there is no more information to send. Also, sending to a closed channel throws a panic, while receiving from it is a non-blocking operation.
  8. What is escape analysis? Escape analysis is an optimization process performed by the Go compiler that attempts to reduce the variables allocated in the heap by verifying whether they outlive the function where they are defined.

第四章

  1. What's the difference between absolute and relative paths? An absolute path starts with the / (root) path, while a relative path does not. To obtain an absolute path from a relative one, it must be joined to the current working directory.
  2. How do you obtain or change the current working directory? To find out the current working directory, the os package offers the Getwd function, which returns the current working directory. To change the current working directory, the Chdir function must be used. It accepts both relative and absolute paths.
  3. What are the advantages and downfalls of using ioutil.ReadAll? The ioutil.ReadAll function places the entire file contents in a byte slice, so the size of file influences the amount of memory allocated, and then released. Since there is no recycling of the memory allocated this way, these slices get garbage-collected when they are no longer used.
  4. Why are buffers important for reading operations? Byte buffers limit the amount of memory allocated by the reading operations, but they also require a certain number of read operations, each one with a little overhead that impacts speed and performance.
  5. When should you use ioutil.WriteFile? The ioutil.WriteFile function can be used if the size of the content is not too big, because the entire content needs to be in memory. It is also preferable to use it in short-lived applications and avoid it for recurrent writing operations.
  6. Which operations are available when using a buffered reader that allows peeking? The peeking operation allows the content of the next bytes to be checked without advancing the cursor of the current reader, and this enables us to have contextual operations, such as read word, read line, or any custom token-based operation.
  7. When is it better to read content using a byte buffer? Using a reading buffer is a way of lowering the memory usage of your application. It can be used when there's no need to have all the content at once.
  8. How can buffers be used for writing? What's the advantage of using them? In writing operations, the application already handles the bytes that are about to be written, so an underlying buffer is used to optimize the number of system calls, only when the buffer is full, so as to avoid the addition of system call overheads when the data passed to the writer is not enough.

第五章

  1. What's a stream? A stream is an abstraction that represents a generic flow of incoming or outgoing data.
  2. What interfaces abstract the incoming streams? The io.Reader interface is an abstraction for incoming streams.
  3. Which interface represents the outgoing streams? The io.Writer interface is an abstraction for outgoing streams.
  4. When should a byte reader be used? When should a string reader be used instead? A byte reader should be used when the raw data is a slice of bytes, while a string reader should be used with strings. Converting from one type of data to another causes a copy and is inconvenient.
  5. What's the difference between a string builder and a byte buffer? A byte buffer can be reused and overwritten. A string builder is used to create a string without a copy, so it uses a byte slice and converts it to a string without copying, using the unsafe package.
  6. Why should reader and writer implementations accept an interface as input? Accepting an interface as an input means to be open to different types with the same behavior. This enables existing readers and writers, such as buffers and files, to be used.
  7. How does a pipe differ from TeeReader? A pipe connects a writer to a reader. Whatever gets written in the writer gets read by the reader. A TeeReader does the opposite, connecting a reader to a writer, so what gets read is also written somewhere else.

第六章

  1. What is a Terminal, and what is a pseudo-terminal? A terminal is an application that behaves like a teletype, by displaying a 2 x 2 matrix of characters. Pseudo terminals are applications that run under a terminal and emulate its behavior by being interactive.

  2. What should a pseudo terminal be able to do? A pseudo terminal application should be able to receive input from a user, execute an action according to the instruction received, and display the result back to the user.

  3. What Go tools did we use in order to emulate a terminal? To manage user input, we used a buffered scanner in standard input, which will read user input line by line. Each command has been implemented using the same interface. To understand the command invoked, we used a comparison between the first argument and the commands available. A writer is passed to command to print their output.

  4. 我的应用程序如何从标准输入获取指令? 应用程序可以将标准输入与扫描仪结合使用,该扫描仪将在每次遇到新行时返回一个新令牌。

  5. 使用命令接口的优势是什么? 使用命令接口允许我们和包的用户通过实现自己版本的接口来扩展行为。

  6. Levenshtein 距离是多少?为什么它在伪终端中有用? Levenshtein 距离是将一个字符串转换为另一个字符串所需的更改数。当用户指定一个不存在的命令时,它可用于向用户建议其他命令。

第七章

  1. Go 应用程序中当前流程有哪些应用程序可用? 流程可用的应用程序有 PID(流程 ID)、PPID(父 PID)、UID 和 GID(用户和组 ID)以及工作目录。

  2. 如何创建子流程? 可以使用exec.Cmd数据结构定义子流程。当调用RunStartOutputCombinedOutput方法之一时,将创建流程。

  3. 您如何确保子进程在其父进程中存活? 在 Unix 系统中,默认情况下,如果父系统终止,子系统将继续生存。此外,您可以更改子进程的进程组和会话 ID,以确保它在父进程中生存。

  4. 您可以访问子属性吗?如何使用它们? 最大的优点之一是访问子 PID 以将其保存在某个位置,例如磁盘上。这将允许应用程序的另一个实例或任何其他应用程序知道哪个是子应用程序的标识符,并验证它是否仍在运行。

  5. Linux 中的守护进程是什么?它们是如何处理的? Linux 中的守护进程是在后台运行的进程。为了创建守护进程,进程可以创建自身的 fork 并终止,将init进程设置为 fork 的父进程,将当前工作目录设置为 fork 的 root,将子进程的输入设置为null,并使用日志文件进行输出和出错。

第八章

  1. 什么是导出代码?谁利用它? 退出码是一个整数值,从一个进程传递给 is 父进程,在进程结束时发出信号。它代表过程的结果,如果没有错误,则为0。父进程可以使用此值来决定下一步要做什么,例如在出现错误时再次运行该进程。
  2. 当应用程序恐慌时会发生什么?返回什么退出代码? 如果panic未恢复,应用程序将执行所有延迟的功能,并以2状态退出。
  3. Go 应用程序在接收所有信号时的默认行为是什么? 带有信号的 Go 应用程序的默认行为是提前退出。
  4. 您如何截取信号并决定应用程序必须如何运行? 可以使用signal.Notify方法在信道上截获接收到的信号,指定要处理的信号类型。通道接收到的值可以与信号值进行比较,应用程序可以相应地进行操作。
  5. 您能向其他进程发送信号吗?如果是,怎么做? 可以向 Go 应用程序内的另一个进程发送信号。为此,应用程序需要使用查找函数获取os.Process结构的实例,然后可以使用该结构的Signal方法发送信号。
  6. 什么是管道?为什么管道很重要? 管道是两条流,一条是输出,另一条是输入,连接在一起。输出中写入的内容可供输入使用,这有助于将一个流程输出连接到另一个流程输入。

第九章

  1. 使用沟通模式的优势是什么? 通信模型允许您抽象模型处理的数据类型,从而简化不同端点之间的通信。
  2. TCP 和 UDP 连接有什么区别? TCP 是面向连接的,这使得它更加可靠,因为它在发送新数据之前验证目的地是否正确接收数据。UDP 连接连续发送数据,而不确认目标接收到包。这可能会导致数据包丢失,但会加快连接速度,并且不会累积延迟。
  3. 发送请求时谁关闭请求主体? 在进行 HTTP 调用时关闭请求是应用程序的责任。
  4. 谁在服务器中接收请求时关闭身体? 当连接关闭时,请求主体会自动关闭,但如果服务器愿意,可以更早地关闭请求主体。

第十章

  1. 文本编码和二进制编码之间的权衡是什么? 基于文本的编码对于人类来说更容易阅读,也更容易调试和编写,但它们占用了更多的空间。二进制编码对于人来说很难编写、读取和调试,但尺寸较小。

  2. 编码时,默认情况下 Go 如何处理数据结构? Go 的默认行为是使用反射来读取字段及其值。

  3. 如何改变这种行为? 可以通过实现您正在使用的编码的封送器接口来更改此行为,例如用于 JSON 的json.Marshaller

  4. 如何在 XML 属性中对结构字段进行编码? 结构字段需要在其标签中指定,attr值。

  5. 解码gob接口值需要什么操作? 实现接口的数据类型需要使用gob.Register功能在gob包中注册。

  6. 什么是协议缓冲区编码? 协议缓冲区是 Google 制作的一种编码协议,它使用定义文件来定义数据结构和服务。该文件用于生成数据模型、客户机和服务器存根,只将服务器的实现留给开发人员。

第十一章

  1. 什么是线,谁负责? 线程是进程的一部分,可以由特定的内核或 CPU 分配。它像进程一样携带有关应用程序状态的信息,并由操作系统调度程序管理。

  2. goroutine 和线程有什么不同? 与线程相比,goroutine 的大小很小,比例为 1:100,并且不受操作系统的管理。Go 运行时负责 goroutine 的调度。

  3. 启动 goroutine 时,何时评估参数? 创建 goroutine 时,将计算传递给启动 goroutine 的函数的所有参数。这意味着,如果参数的值在调度程序实际获取并启动 goroutine 之前发生更改,则更改不会反映在 goroutine 中。

  4. 缓冲通道和非缓冲通道有何不同? 如果未指定容量,或者如果是0,则make功能会创建非缓冲通道。对这样一个通道的每个发送操作都将阻塞当前的 goroutine,直到另一个 goroutine 执行接收操作为止。缓冲通道可以支持与其容量相等的许多非阻塞发送操作。这意味着,如果信道的容量为n,则任何接收操作都不匹配的第一个n-1发送操作将不会被阻塞。

  5. 为什么单向通道有用? 它们只允许操作的一个子集,向用户明确通道的范围。仅接收通道不允许发送或关闭数据,这是完全合理的,因为这不是接收器的责任。仅发送通道不允许接收数据,但允许发送数据并关闭通道,并隐式声明由发送方关闭通道以表示没有更多数据。

  6. nil或封闭通道上执行操作时会发生什么情况? 发送到nil通道或从nil通道接收信息会永远阻塞,关闭该通道会造成恐慌。从封闭通道接收时立即返回零值,false,而发送到封闭通道会引起恐慌,如果我们再次尝试关闭它,同样的情况也会发生。

  7. 计时器和计时器的用途是什么? 计时器和计时器都创建一个只接收的通道。计时器可以在带有select语句的循环中使用,而不是使用default,以减少选择的频率,并降低空闲时应用程序的 CPU 使用率。在每一个固定时间段执行一个操作时,ticker 非常有用,而一个实际用法是速率限制器,它限制在应用程序的某个特定部分的一个设定时间段内执行的次数。

第十二章

  1. 什么是比赛条件? 竞态条件是指一个应用程序试图在同一资源上同时执行两个操作,而该资源的性质一次只允许执行一个操作。
  2. 当您尝试与映射同时执行读写操作时会发生什么? 当地图上的读写操作同时发生时,会导致运行时错误:concurrent map writes
  3. MutexRWMutex之间有什么区别? 常规互斥锁允许锁定和解锁资源,并且每个操作具有相同的优先级。读/写互斥锁有两种类型的锁,每个操作一种锁(读/写)。读锁允许一次执行多个操作,但它是独占的。如果资源上有许多连续读取操作,则写锁可能会受到延迟。这就是所谓的写饥饿。
  4. 为什么等待组有用? 等待组是同步不同 goroutine 执行的完美工具。这为经典设置提供了一个干净而优雅的解决方案,其中存在多个并发操作,主 goroutine 必须等待它们结束后才能继续。
  5. sync.Once的主要用途是什么? sync.Once可以一次执行并发操作。例如,它可以用来关闭一个通道,避免恐慌。另一个用例是延迟初始化变量,以实现单例设计模式的线程安全版本。

**6. 游泳池怎么用? 池允许重复使用短期项目。池的一个很好的用例是字节片和字节缓冲区,因为池将阻止垃圾收集器回收此资源,同时阻止分配新池。 7. 使用原子操作有什么好处? 对数值变量使用互斥锁会带来大量开销。原子操作允许减少此类开销,并对数值变量执行线程安全操作。它主要用于整数,但是,通过一些转换,我们可以对其他类型(如布尔型和浮点型)执行相同的操作。

第十三章

  1. Go 的上下文是什么? Context 是一个包含通用接口和一些辅助函数的包,用于返回 Context 实例。它用于同步应用程序各个部分之间的操作,并携带值。
  2. 取消、截止日期和超时之间有什么区别? 上下文取消有三种不同类型的过期,即应用程序显式调用取消函数,截止日期是上下文超过指定时间,超时是上下文在特定持续时间内生存。
  3. 通过上下文传递值的最佳实践是什么? 随上下文传递的值应与当前范围或请求相关。它们不应用作传递对应用程序至关重要的可选函数参数或变量的方式。使用自定义私有类型作为键也是一个好主意,因为内置值可以被其他包覆盖。指向值的指针也是此类问题的解决方案。
  4. 哪些标准包已经使用上下文? 有不同的包使用上下文。最值得注意的是net/http,它使用上下文进行请求和服务器关闭;net,使用上下文作为DialListen等函数;以及database/sql,它使用上下文作为取消查询等操作的方式。

第十四章

  1. 什么是发电机?它的职责是什么? 生成器是一种返回一系列值的工具,每次调用它时,它都会返回该系列中的下一个值。它负责按需生成序列中的值。在 Go 中,这可以通过使用通道接收由创建值的 goroutine 发送的值来完成。
  2. 您如何描述管道? 管道是一种应用程序流,它将执行分为不同的阶段。这些阶段通过使用某种通信方式(如网络)或运行时内部(如通道)彼此通信。
  3. 什么类型的舞台获得一个通道并返回一个? 中间级将从一个纯接收信道接收,并返回另一个纯接收信道。
  4. 扇入和扇出有什么区别? 扇入也称为解复用,需要将来自不同来源的消息收集到一个源中。扇出(Fan-out)或多路复用(multiplexing)则相反,它需要将单个消息源拆分为多个接收器。

第 15 章

  1. Go 中接口的内存表示是什么? Go 中的接口由两个值表示,第一个值为接口具体类型,第二个值为此类类型的值。
  2. 将一个接口类型强制转换为另一个接口类型时会发生什么? 由于接口值需要是一个具体值,不能是另一个接口,因此创建了一个不同类型、相同具体值的新接口。
  3. 反射中的ValueTypeKind是什么? AValue顾名思义,代表变量的内容;aType表示变量的 Go 类型;而KindType的内存表示,仅指内置类型。 *** 值可寻址是什么意思? 可寻址值是一个可以编辑的值,因为它是由指针获取的。* 为什么结构字段标记在 Go 中很重要? 结构字段标签是一种使用反射Type接口添加有关结构字段的额外信息的简单方法,易于阅读。**

**6. 反思的总体权衡是什么? 反射允许您的代码处理未知类型的数据,并使您的包或应用程序具有通用性,但它带来的开销会带来性能成本。它还使代码更加晦涩,不易维护。 7. 您能描述一下使用反射的好方法吗? 最好的反射方法是我们在标准库的许多不同部分找到的方法;例如,在encoding包中。它们使用反射作为最后手段,并通过提供编码和解码操作的接口来实现。如果类型满足这些接口,则包将使用相应的方法,而不是依赖反射。

第十六章

  1. 什么是 CGO? CGO 是一个强大的 Go 工具,用于处理 C 代码和 Go 代码之间的通信。这使得 C 代码可以在 Go 应用程序中使用,并可以利用大量现有的 C 库。

  2. 如何从 Go 调用 C 代码? Go 提供了一个名为C的伪包,它公开了 C 类型,如C.int,以及一些将 Go 字符串和字节转换为C字符数组的函数,反之亦然。导入C包之前的注释将被解释为 C 代码,其中定义的所有函数(无论是直接的,还是通过导入文件)都将作为C包的函数在 Go 中可用。

  3. 如何在 C 语言中使用 Go 代码? 如果 Go 函数前面有一条特殊注释//export,则 C 代码可以使用该函数。它还必须在 C 中定义为外部函数。

  4. Go 和 C 的数据类型有什么不同? 即使它们有不同的数据类型,C 和 Go 也会共享它们的大部分内置数字类型。Go 中的字符串是内置的不可变类型,但在 C 中,它们只是一个以\0值结尾的字符数组。

  5. 如何在 C 代码中编辑 Go 值? 使用unsafe包,您可以在 C 和 Go 中转换具有相同内存表示的数据类型。您需要将指针转换为 C 对应的值,这将允许您编辑应用程序C部分的指针内容。

  6. 与压缩数据结构相关的主要问题是什么? 压缩数据结构节省内存空间,但它们的字段可以不对齐,这意味着它们被分割到多个内存区域中。这意味着读写操作需要两倍的时间。还有另一个不便之处,一些压缩字段无法从 Go 直接访问。

  7. CGO 的主要失败是什么? 即使 CGO 是一个非常强大的工具,它也有许多缺点—从 C 传递到 Go 的性能成本,反之亦然;编译时间的增加是因为 C 编译器参与了这个过程;您的 Go 代码依赖于您的 C 代码来工作,这可能更难维护和调试。****