2021年1月

在 Ubuntu 或 Debian 中通过命令行来安装应用是一件很简单的事,你只需要执行 apt install package_name 就可以了。

但如果你想在安装一个软件包之前或之后知晓这个软件包的依赖,那该怎么办呢?

在本教程中,我将向你展示多种方法来在 Ubuntu 或其他使用 APT 包管理器 的 Debian 系 Linux 发行版中查看一个软件包的依赖。

什么是 Ubuntu 中的包依赖?

当你在 Linux 中安装一个软件包,有时这个软件包还需要其他的软件包来使它工作正常。这些额外的软件包就叫作这个包的依赖。假如这些软件包之前没有在系统中被安装,那么这些依赖在安装这个软件包的同时会被自动安装上。

举个例子,用来转换视频格式的 GUI 工具 HandBrake 需要 FFmpegGStreamer 软件包。所以对于 HandBrake 来说, FFmpeg 和 GStreamer 就是它的包依赖。

假如在你的系统上这些软件包没有被安装,则当你 在 Ubuntu 上安装 HandBrake 时,就会自动安装上它们。

在 Ubuntu 和基于 Debian 的发行版中查看一个软件包的依赖

正如在 Linux 上经常发生的那样,有多种方法来达到相同的目标。下面让我们一起瞧瞧查看一个软件包依赖的多种方法。

使用 apt show 来查看依赖

你可以使用 apt show 命令 来展示一个包的详细信息。其中依赖信息就是其中一部分,你可以在以 “Depends” 打头的那些行中看到它们。

例如,下面展示的是使用 apt show 展示 ubuntu-restricted-extras 这个包的详细信息:

abhishek@itsfoss:~$ apt show ubuntu-restricted-extras 
Package: ubuntu-restricted-extras
Version: 67
Priority: optional
Section: multiverse/metapackages
Origin: Ubuntu
Maintainer: Ubuntu Developers <[email protected]>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 14.3 kB
Depends: ubuntu-restricted-addons
Recommends: libavcodec-extra, ttf-mscorefonts-installer, unrar
Download-Size: 3,200 B
APT-Manual-Installed: yes
APT-Sources: http://us.archive.ubuntu.com/ubuntu focal/multiverse amd64 Packages
Description: Commonly used media codecs and fonts for Ubuntu
 This collection of packages includes:
  - MP3 and other audio codec software to play various audio formats
    (GStreamer plugins)
  - software to install the Microsoft Web fonts
  - the Adobe Flash plugin
  - LAME, software to create compressed audio files.
 .
 This software does not include libdvdcss2, and will not let you play
 encrypted DVDs. For more information, see
 https://help.ubuntu.com/community/RestrictedFormats/PlayingDVDs
 .
 These software packages are from the Multiverse channel, restricted by
 copyright or legal issues in some countries. For more information, see
 http://www.ubuntu.com/ubuntu/licensing

如你所见,ubuntu-restricted-extras 包依赖于 ubuntu-restricted-addons 这个软件包。

但你得小心的是依赖包还可能依赖于其他包,这样一直循环往复直到尽头。但幸好 APT 包管理器可以为你处理这些复杂的依赖关系,自动地安装所有的依赖(大多数情况下)。

什么是推荐包?

你注意到了上面结果输出中以 “Recommends” 开头的那些行了吗?

推荐包不是软件包的直接依赖,但它们可以开启软件包的一些额外功能。

正如你上面看到的那样, ubuntu-restricted-extras 包有 ttf-mscorefonts-installer 这个推荐包,用来在 Ubuntu 上安装 Microsoft 的字体。

这些推荐包也会默认被一同安装上,假如你想显式地禁止这些推荐包的安装,你可以像下面这样使用 –-no-install-recommends 选项。

sudo apt install --no-install-recommends package_name

使用 apt-cache 来直接获取依赖信息

上面通过 apt show 的方式会获取到大量信息,假如你想在脚本中获取到依赖信息,那么 apt-cache 命令将会给你一个更好且更简洁的输出结果。

apt-cache depends package_name

下面的输出看起来更加干净,不是吗?

使用 dpkg 来查看一个 DEB 文件的依赖

aptapt-cache 都作用于软件仓库中的软件包,但假如你下载了一个 DEB 文件,那么这两个命令就不起作用了。

在这种情形下,你可以使用 dpkg 命令的 -I--info 选项。

dpkg -I path_to_deb_file

依赖信息就可以在以 “Depends” 开头的那些行中找到。

使用 apt-rdepends 来查看依赖及依赖的依赖

假如你想查看更多关于依赖的信息,那么你可以使用 apt-rdepends 工具。这个工具可以创建完整的依赖树。这样你就可以得到一个软件包的依赖以及这些依赖的依赖。

它不是一个常规的 apt 命令,所以你需要从 universe 软件仓库中安装上它:

sudo apt install apt-rdepends

这个命令的输出通常很多,取决于依赖树的大小。

Reading package lists... Done
Building dependency tree
Reading state information... Done
shutter
  Depends: procps
  Depends: xdg-utils
imagemagick
  Depends: imagemagick-6.q16 (>= 8:6.9.2.10+dfsg-2~)
imagemagick-6.q16
  Depends: hicolor-icon-theme
  Depends: libc6 (>= 2.4)
  Depends: libmagickcore-6.q16-6 (>= 8:6.9.10.2)
  Depends: libmagickwand-6.q16-6 (>= 8:6.9.10.2)
hicolor-icon-theme
libc6
  Depends: libcrypt1 (>= 1:4.4.10-10ubuntu4)
  Depends: libgcc-s1
libcrypt1
  Depends: libc6 (>= 2.25)

apt-rdepends 工具的功能非常多样,它还可以用来计算反向依赖。这意味着你可以查看某个特定的包被哪些软件包依赖。

apt-rdepends -r package_name

输出可能会非常多,因为它将打印出反向依赖树。

abhishek@itsfoss:~$ apt-rdepends -r ffmpeg
Reading package lists... Done
Building dependency tree       
Reading state information... Done
ffmpeg
  Reverse Depends: ardour-video-timeline (>= 1:5.12.0-3ubuntu4)
  Reverse Depends: deepin-screen-recorder (5.0.0-1build2)
  Reverse Depends: devede (4.15.0-2)
  Reverse Depends: dvd-slideshow (0.8.6.1-1)
  Reverse Depends: green-recorder (>= 3.2.3)

我希望这个快速的教程可以帮助你提高一点儿你的命令行知识。为了知晓更多类似小知识点,请保持关注。


via: https://itsfoss.com/check-dependencies-package-ubuntu/

作者:Abhishek Prakash 选题:lujun9972 译者:FSSlc 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

C++ 语言很复杂,但它可以教会你很多关于数据类型、内存管理和代码链接的知识。

学习一门编程语言有几种方法。如果你是编码新手,你通常会学习一些基本的计算机编码概念,并尝试应用它们。如果你已经知道如何用另一种语言进行编码,你可以重新学习编码概念在新语言中是如何表达的。

不管是哪种情况,学习这些新原理的便捷方法是创建一个简单的猜谜游戏。这会迫使你了解一门语言如何接收输入和发送输出,如何比较数据,如何控制程序的流程,以及如何利用条件来影响结果。它还确保你知道一门语言是如何组织其代码的;例如,Lua 或 Bash 可以很容易地作为脚本运行,而 Java 则需要你创建一个类。

在本文中,我将演示如何用 C++ 在终端上实现猜谜游戏。

安装依赖关系

要跟上本文的步伐,你需要 C++ 和一个编译器。

在 Linux 上,你可以通过从你的发行版软件仓库中安装 Qt Creator IDE 来获得你所需要的一切。

在 Fedora、CentOS 或 RHEL 上:

$ sudo dnf install qt-creator

在 Debian、Ubuntu、Chromebook 或类似的系统上:

$ sudo apt install qtcreator

本文并没有使用 Qt Creator IDE,但它是一个安装你所需要的一切的简单方法,对于复杂的 C++ 项目(包括那些带有 GUI 的项目),它是一个必不可少的工具。在 macOS 或 Windows 上,按照 Qt 网站上的安装说明进行安装。

设置包含和命名空间

C++ 的核心语言是精简的。即使是一个简单的应用程序也需要使用额外的库。这个应用程序使用 iostream 来获得对 coutcin 关键字的访问。

另外,确保程序使用 std 命名空间:

#include <iostream>

using namespace std;

这并不是绝对必要,但如果不将命名空间设置为 std,所有来自 iostream 库的关键字都需要一个命名空间前缀。例如,我不能写作 cout,而是要写作 std::cout

C++ 中的语句以分号结束。

创建一个函数

每个 C++ 应用程序至少需要一个函数。一个 C++ 应用程序的主函数必须称为 main,它必须返回一个整数(int),这符合 POSIX 的期望,即一个进程在成功时返回 0,而在失败时返回其他值。你可以通过为它提供返回类型和名称来创建一个新函数。

int main() {
 // code goes here
}

实现程序逻辑

游戏代码必须首先产生一个随机数供玩家猜测。在 C++ 中,你可以通过建立一个用于生成伪随机数的种子来实现。一个简单的种子就是当前的时间。一旦有了种子,你就可以得到一个在 1 和 100 之间的数字。通过调用 rand 函数,并设置上限值 100 来产生一个从 0 到 99 的随机数,所以无论选择了什么数字都要加 1,并将结果分配给一个名为 number 的变量。你还必须声明一个变量来保存玩家的猜测值。为了清楚起见,我称这个变量为 guess

这个示例代码还包括一个调试语句,告诉你随机数到底是什么。这对于猜测游戏来说不是很好,但它使测试速度快了很多。以后,你可以删除这一行,或者直接在行前面用 // 注释出来:

 srand (time(NULL));
 int number = rand() % 100+1;
 int guess = 0;

 cout << number << endl; //debug

增加 do-while 和 if 语句

C++ 中的 do-while 语句以关键字 do 开头,并将你希望 C++ 做的所有事情用括号括起来。用 while 关键字结束语句,后面是必须满足的条件(括号内):

do {
 // code here
} while ( number != guess );

游戏代码出现在 ifelse ifelse 语句之间,为玩家提供提示。

首先,用 cout 语句提示玩家猜测。cout 函数将输出打印到 stdout 上。因为 cout 语句没有连着 endl(endline)函数,所以没有换行。紧接着这个 cout 语句,通过使用 cin 函数告诉 C++ 等待输入。正如你所猜测的那样,cin 等待来自 stdin 的输入。

接下来,程序进入 if 控制语句。如果玩家的猜测大于 number 变量中包含的伪随机数,那么程序就会打印出一个提示,后面是一个换行符。这就中断了 if 语句,但 C++ 仍然被困在 do-while 循环中,因为它的条件(number 变量等于 guess)还没有满足。

如果玩家的猜测小于 number 变量中包含的伪随机数,那么程序就会打印出一个提示,后面是一个换行符。这再次中断了 if 语句,但程序仍然被困在 do-while 循环中。

guess 等于 number 时,最终满足关键条件,触发 else 语句,do-while 循环结束,程序结束:

do {
  cout << "Guess a number between 1 and 100: ";
  cin >> guess;

  if ( guess > number) { cout << "Too high.\n" << endl; }
  else if ( guess < number ) { cout << "Too low.\n" << endl; }
  else {
    cout << "That's right!\n" << endl;
    exit(0);
  } // fi
 } while ( number != guess );
return 0;
} // main

构建代码和玩游戏

你可以用 GCC 构建你的应用程序:

$ g++ -o guess.bin guess.cpp

运行二进制文件试试:

$ ./guess.bin
74
Guess a number between 1 and 100: 76
Too high.

Guess a number between 1 and 100: 1
Too low.

Guess a number between 1 and 100: 74
That's right!

成功了!

试试 C++ 吧

C++ 语言很复杂。为终端编写 C++ 应用程序可以让你学到很多关于数据类型、内存管理和代码链接的知识。试着用 C++ 编写一个有用的实用程序,看看你能发现什么!


via: https://opensource.com/article/20/12/learn-c-game

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

更新:如果你是从一篇题为 《糟糕的 Go 语言》 的汇编文章看到这篇博文的话,那么我想表明的是,我很惭愧被列在这样的名单上。Go 绝对是我使用过的最不糟糕的的编程语言。在我写作本文时,我是想遏制我所看到的一种趋势,那就是过度使用 Go 的一些较复杂的部分。我仍然认为 通道 Channel 可以更好,但是总体而言,Go 很棒。这就像你最喜欢的工具箱中有 这个工具;它可以有用途(甚至还可能有更多的用途),它仍然可以成为你最喜欢的工具箱!

更新 2:如果我没有指出这项对真实问题的优秀调查,那我将是失职的:《理解 Go 中的实际并发错误》。这项调查的一个重要发现是...Go 通道会导致很多错误。

从 2010 年中后期开始,我就断断续续地在使用 Google 的 Go 编程语言,自 2012 年 1 月开始(在 Go 1.0 之前!),我就用 Go 为 Space Monkey 编写了合规的产品代码。我对 Go 的最初体验可以追溯到我在研究 Hoare 的 通信顺序进程 并发模型和 Matt MightUCombinator 研究组 下的 π-演算 时,作为我(现在已重定向)博士工作的一部分,以更好地支持多核开发。Go 就是在那时发布的(多么巧合啊!),我当即就开始学习尝试了。

它很快就成为了 Space Monkey 开发的核心部分。目前,我们在 Space Monkey 的生产系统有超过 42.5 万行的纯 Go 代码( 包括我们所有的 vendored 库中的代码量,这将使它接近 150 万行),所以也并不是你见过的最多的 Go 代码,但是对于相对年轻的语言,我们是重度用户。我们之前 写了我们的 Go 使用情况。也开源了一些使用率很高的库;许多人似乎是我们的 OpenSSL 绑定(比 crypto/tls 更快,但请保持 openssl 本身是最新的!)、我们的 错误处理库日志库度量标准收集库/zipkin 客户端 的粉丝。我们使用 Go、我们热爱 Go、我们认为它是目前为止我们使用过的最不糟糕的、符合我们需求的编程语言。

尽管我也不认为我能说服自己不要提及我的广泛避免使用 goroutine-local-storage 库 (尽管它是一个你不应该使用的魔改技巧,但它是一个漂亮的魔改),希望我的其他经历足以证明我在解释我故意煽动性的帖子标题之前知道我在说什么。

等等,什么?

如果你在大街上问一个有名的程序员,Go 有什么特别之处? 她很可能会告诉你 Go 最出名的是 通道 Channels 和 goroutine。 Go 的理论基础很大程度上是建立在 Hoare 的 CSP( 通信顺序进程 Communicating Sequential Processes )模型上的,该模型本身令人着迷且有趣,我坚信,到目前为止,它产生的收益远远超过了我们的预期。

CSP(和 π-演算)都使用通信作为核心同步原语,因此 Go 会有通道是有道理的。Rob Pike 对 CSP 着迷(有充分的理由)相当深 已经有一段时间了。(当时现在)。

但是从务实的角度来看(也是 Go 引以为豪的),Go 把通道搞错了。在这一点上,通道的实现在我的书中几乎是一个坚实的反模式。为什么这么说呢?亲爱的读者,让我细数其中的方法。

你可能最终不会只使用通道

Hoare 的 “通信顺序进程” 是一种计算模型,实际上,唯一的同步原语是在通道上发送或接收的。一旦使用 互斥量 mutex 信号量 semaphore 条件变量 condition variable 、bam,你就不再处于纯 CSP 领域。 Go 程序员经常通过高呼 “通过交流共享内存” 的 缓存的思想 来宣扬这种模式和哲学。

那么,让我们尝试在 Go 中仅使用 CSP 编写一个小程序!让我们成为高分接收者。我们要做的就是跟踪我们看到的最大的高分值。如此而已。

首先,我们将创建一个 Game 结构体。

type Game struct {
  bestScore int
  scores    chan int
}

bestScore 不会受到 互斥量 mutex 的保护!这很好,因为我们只需要一个 goroutine 来管理其状态并通过通道来接收新的分值即可。

func (g *Game) run() {
  for score := range g.scores {
    if g.bestScore < score {
      g.bestScore = score
    }
  }
}

好的,现在我们将创建一个有用的构造函数来开始 Game

func NewGame() (g *Game) {
  g = &Game{
    bestScore: 0,
    scores:    make(chan int),
  }
  go g.run()
  return g
}

接下来,假设有人给了我们一个可以返回分数的 Player。它也可能会返回错误,因为可能传入的 TCP 流可能会死掉或发生某些故障,或者玩家退出。

type Player interface {
  NextScore() (score int, err error)
}

为了处理 Player,我们假设所有错误都是致命的,并将获得的比分向下传递到通道。

func (g *Game) HandlePlayer(p Player) error {
  for {
    score, err := p.NextScore()
    if err != nil {
      return err
    }
    g.scores <- score
  }
}

好极了!现在我们有了一个 Game 类型,可以以线程安全的方式跟踪 Player 获得的最高分数。

你圆满完成了自己的开发工作,并开始拥有客户。你将这个游戏服务器公开,就取得了令人难以置信的成功!你的游戏服务器上也许正在创建许多游戏。

很快,你发现人们有时会离开你的游戏。许多游戏不再有任何玩家在玩,但没有任何东西可以阻止游戏运行的循环。死掉的 (*Game).run goroutines 让你不知所措。

挑战: 在无需互斥量或 panics 的情况下修复上面的 goroutine 泄漏。实际上,可以滚动到上面的代码,并想出一个仅使用通道来解决此问题的方案。

我等着。

就其价值而言,它完全可以只通过通道来完成,但是请观察以下解决方案的简单性,它甚至没有这个问题:

type Game struct {
  mtx sync.Mutex
  bestScore int
}

func NewGame() *Game {
  return &Game{}
}

func (g *Game) HandlePlayer(p Player) error {
  for {
    score, err := p.NextScore()
    if err != nil {
      return err
    }
    g.mtx.Lock()
    if g.bestScore < score {
      g.bestScore = score
    }
    g.mtx.Unlock()
  }
}

你想选择哪一个?不要被欺骗了,以为通道的解决方案可以使它在更复杂的情况下更具可读性和可理解性。 拆解 Teardown 是非常困难的。这种拆解若用 互斥量 mutex 来做那只是小菜一碟,但最困难的是只使用 Go 专用通道来解决。另外,如果有人回复说发送通道的通道更容易推理,我马上就是感到头疼。

重要的是,这个特殊的情况可能真的 很容易 解决,而通道有一些运行时的帮助,而 Go 没有提供!不幸的是,就目前的情况来看,与 Go 的 CSP 版本相比,使用传统的 同步原语 synchronization primitives 可以更好地解决很多问题,这是令人惊讶的。稍后,我们将讨论 Go 可以做些什么来简化此案例。

练习: 还在怀疑? 试着让上面两种解决方案(只使用通道与只使用互斥量channel-only vs mutex-only)在一旦 bestScore 大于或等于 100 时,就停止向 Players 索要分数。继续打开你的文本编辑器。这是一个很小的玩具问题。

这里的总结是,如果你想做任何实际的事情,除了通道之外,你还会使用传统的同步原语。

通道比你自己实现要慢一些

Go 如此重视 CSP 理论,我认为其中一点就是,运行时应该可以通过通道做一些杀手级的调度优化。也许通道并不总是最直接的原语,但肯定是高效且快速的,对吧?

正如 Dustin HiattTyler Treat’s post about Go 上指出的那样,

在幕后,通道使用锁来序列化访问并提供线程安全性。 因此,通过使用通道同步对内存的访问,你实际上就是在使用锁。 被包装在线程安全队列中的锁。 那么,与仅仅使用标准库 sync 包中的互斥量相比,Go 的花式锁又如何呢? 以下数字是通过使用 Go 的内置基准测试功能,对它们的单个集合连续调用 Put 得出的。
> BenchmarkSimpleSet-8 3000000 391 ns/op
> BenchmarkSimpleChannelSet-8 1000000 1699 ns/o
>

无缓冲通道的情况与此类似,甚至是在争用而不是串行运行的情况下执行相同的测试。

也许 Go 调度器会有所改进,但与此同时,良好的旧互斥量和条件变量是非常好、高效且快速。如果你想要提高性能,请使用久经考验的方法。

通道与其他并发原语组合不佳

好的,希望我已经说服了你,有时候,你至少还会与除了通道之外的原语进行交互。标准库似乎显然更喜欢传统的同步原语而不是通道。

你猜怎么着,正确地将通道与互斥量和条件变量一起使用,其实是有一定的挑战性的。

关于通道的一个有趣的事情是,通道发送是同步的,这在 CSP 中是有很大意义的。通道发送和通道接收的目的是为了成为同步屏蔽,发送和接收应该发生在同一个虚拟时间。如果你是在执行良好的 CSP 领域,那就太好了。

实事求是地说,Go 通道也有多种缓冲方式。你可以分配一个固定的空间来考虑可能的缓冲,以便发送和接收是不同的事件,但缓冲区大小是有上限的。Go 并没有提供一种方法来让你拥有任意大小的缓冲区 —— 你必须提前分配缓冲区大小。 这很好,我在邮件列表上看到有人在争论,因为无论如何内存都是有限的

What。

这是个糟糕的答案。有各种各样的理由来使用一个任意缓冲的通道。如果我们事先知道所有的事情,为什么还要使用 malloc 呢?

没有任意缓冲的通道意味着在 任何 通道上的幼稚发送可能会随时阻塞。你想在一个通道上发送,并在互斥下更新其他一些记账吗?小心!你的通道发送可能被阻塞!

// ...
s.mtx.Lock()
// ...
s.ch <- val // might block!
s.mtx.Unlock()
// ...

这是哲学家晚餐大战的秘诀。如果你使用了锁,则应该迅速更新状态并释放它,并且尽可能不要在锁下做任何阻塞。

有一种方法可以在 Go 中的通道上进行非阻塞发送,但这不是默认行为。假设我们有一个通道 ch := make(chan int),我们希望在其上无阻塞地发送值 1。以下是在不阻塞的情况下你必须要做的最小量的输入:

select {
case ch <- 1: // it sent
default:      // it didn't
}

对于刚入门的 Go程序员来说,这并不是自然而然就能想到的事情。

综上所述,因为通道上的很多操作都会阻塞,所以需要对哲学家及其就餐仔细推理,才能在互斥量的保护下,成功地将通道操作与之并列使用,而不会造成死锁。

严格来说,回调更强大,不需要不必要的 goroutines

每当 API 使用通道时,或者每当我指出通道使某些事情变得困难时,总会有人会指出我应该启动一个 goroutine 来读取该通道,并在读取该通道时进行所需的任何转换或修复。

呃,不。如果我的代码位于热路径中怎么办?需要通道的实例很少,如果你的 API 可以设计为使用 互斥量 mutexes 信号量 semaphores 回调 callbacks ,而不使用额外的 goroutine (因为所有事件边缘都是由 API 事件触发的),那么使用通道会迫使我在资源使用中添加另一个内存分配堆栈。是的,goroutine 比线程轻得多,但更轻量并不意味着是最轻量。

正如我以前 在一篇关于使用通道的文章的评论中争论过的(呵呵,互联网),如果你使用回调而不是通道,你的 API 总是 可以更通用,总是 更灵活,而且占用的资源也会大大减少。“总是” 是一个可怕的词,但我在这里是认真的。有证据级的东西在进行。

如果有人向你提供了一个基于回调的 API,而你需要一个通道,你可以提供一个回调,在通道上发送,开销不大,灵活性十足。

另一方面,如果有人提供了一个基于通道的 API 给你,而你需要一个回调,你必须启动一个 goroutine 来读取通道,并且 你必须希望当你完成读取时,没有人试图在通道上发送更多的东西,这样你就会导致阻塞的 goroutine 泄漏。

对于一个超级简单的实际例子,请查看 context 接口(顺便说一下,它是一个非常有用的包,你应该用它来代替 goroutine 本地存储)。

type Context interface {
  ...
  // Done returns a channel that closes when this work unit should be canceled.
  // Done 返回一个通道,该通道在应该取消该工作单元时关闭。
  Done() <-chan struct{}

  // Err returns a non-nil error when the Done channel is closed
  // 当 Done 通道关闭时,Err 返回一个非 nil 错误
  Err() error
  ...
}

想象一下,你要做的只是在 Done() 通道触发时记录相应的错误。你该怎么办?如果你没有在通道中选择的好地方,则必须启动 goroutine 进行处理:

go func() {
  <-ctx.Done()
  logger.Errorf("canceled: %v", ctx.Err())
}()

如果 ctx 在不关闭返回 Done() 通道的情况下被垃圾回收怎么办?哎呀!这正是一个 goroutine 泄露!

现在假设我们更改了 Done 的签名:

// Done calls cb when this work unit should be canceled.
Done(cb func())

首先,现在日志记录非常容易。看看:ctx.Done(func() { log.Errorf ("canceled:%v", ctx.Err()) })。但是假设你确实需要某些选择行为。你可以这样调用它:

ch := make(chan struct{})
ctx.Done(func() { close(ch) })

瞧!通过使用回调,不会失去表现力。 ch 的工作方式类似于用于返回的通道 Done(),在日志记录的情况下,我们不需要启动整个新堆栈。我必须保留堆栈跟踪信息(如果我们的日志包倾向于使用它们);我必须避免将其他堆栈分配和另一个 goroutine 分配给调度程序。

下次你使用通道时,问问你自己,如果你用互斥量和条件变量代替,是否可以消除一些 goroutine ? 如果答案是肯定的,那么修改这些代码将更加有效。而且,如果你试图使用通道只是为了在集合中使用 range 关键字,那么我将不得不请你放下键盘,或者只是回去编写 Python 书籍。

more like Zooey De-channel, amirite

通道 API 不一致,只是 cray-cray

在通道已关闭的情况下,执行关闭或发送消息将会引发 panics!为什么呢? 如果想要关闭通道,你需要在外部同步它的关闭状态(使用互斥量等,这些互斥量的组合不是很好!),这样其他写入者才不会写入或关闭已关闭的通道,或者只是向前冲,关闭或写入已关闭的通道,并期望你必须恢复所有引发的 panics。

这是多么怪异的行为。 Go 中几乎所有其他操作都有避免 panic 的方法(例如,类型断言具有 , ok = 模式),但是对于通道,你只能自己动手处理它。

好吧,所以当发送失败时,通道会出现 panic。我想这是有一定道理的。但是,与几乎所有其他带有 nil 值的东西不同,发送到 nil 通道不会引发 panic。相反,它将永远阻塞!这很违反直觉。这可能是有用的行为,就像在你的除草器上附加一个开罐器,可能有用(在 Skymall 可以找到)一样,但这肯定是意想不到的。与 nil 映射(执行隐式指针解除引用),nil 接口(隐式指针解除引用),未经检查的类型断言以及其他所有类型交互不同,nil 通道表现出实际的通道行为,就好像为该操作实例化了一个全新的通道一样。

接收的情况稍微好一点。在已关闭的通道上执行接收会发生什么?好吧,那会是有效操作——你将得到一个零值。好吧,我想这是有道理的。奖励!接收允许你在收到值时进行 , ok = 样式的检查,以确定通道是否打开。谢天谢地,我们在这里得到了 , ok =

但是,如果你从 nil 渠道接收会发生什么呢? 也是永远阻塞! 耶!不要试图利用这样一个事实:如果你关闭了通道,那么你的通道是 nil!

通道有什么好处?

当然,通道对于某些事情是有好处的(毕竟它们是一个通用容器),有些事情你只能用它们来做(比如 select)。

它们是另一种特殊情况下的通用数据结构

Go 程序员已经习惯于对泛型的争论,以至于我一提起这个词就能感觉到 PTSD(创伤后应激障碍)的到来。我不是来谈论这件事的,所以擦擦额头上的汗,让我们继续前进吧。

无论你对泛型的看法是什么,Go 的映射、切片和通道都是支持泛型元素类型的数据结构,因为它们已经被特殊封装到语言中了。

在一种不允许你编写自己的泛型容器的语言中,任何允许你更好地管理事物集合的东西都是有价值的。在这里,通道是一个支持任意值类型的线程安全数据结构。

所以这很有用!我想这可以省去一些陈词滥调。

我很难把这算作是通道的胜利。

Select

使用通道可以做的主要事情是 select 语句。在这里,你可以等待固定数量的事件输入。它有点像 epoll,但你必须预先知道要等待多少个套接字。

这是真正有用的语言功能。如果不是 select,通道将被彻底清洗。但是我的天呐,让我告诉你,第一次决定可能需要在多个事物中选择,但是你不知道有多少项,因此必须使用 reflect.Select

通道如何才能更好?

很难说 Go 语言团队可以为 Go 2.0 做的最具战术意义的事情是什么(Go 1.0 兼容性保证很好,但是很费劲),但这并不能阻止我提出一些建议。

在条件变量上的 Select !

我们可以不需要通道!这是我提议我们摆脱一些“ 圣牛 sacred cows ”(LCTT 译注:神圣不可质疑的事物)的地方,但是让我问你,如果你可以选择任何自定义同步原语,那会有多棒?(答:太棒了。)如果有的话,我们根本就不需要通道了。

GC 可以帮助我们吗?

在第一个示例中,如果我们能够使用定向类型的通道垃圾回收(GC)来帮助我们进行清理,我们就可以轻松地解决通道的高分服务器清理问题。

如你所知,Go 具有定向类型的通道。 你可以使用仅支持读取的通道类型(<-chan)和仅支持写入的通道类型(chan<-)。 这太棒了!

Go 也有垃圾回收功能。 很明显,某些类型的记账方式太繁琐了,我们不应该让程序员去处理它们。 我们清理未使用的内存! 垃圾回收非常有用且整洁。

那么,为什么不帮助清理未使用或死锁的通道读取呢? 与其让 make(chan Whatever) 返回一个双向通道,不如让它返回两个单向通道(chanReader, chanWriter:= make(chan Type))。

让我们重新考虑一下最初的示例:

type Game struct {
  bestScore int
  scores    chan<- int
}

func run(bestScore *int, scores <-chan int) {
  // 我们不会直接保留对游戏的引用,因为这样我们就会保留着通道的发送端。
  for score := range scores {
    if *bestScore < score {
      *bestScore = score
    }
  }
}

func NewGame() (g *Game) {
  // 这种 make(chan) 返回风格是一个建议
  scoreReader, scoreWriter := make(chan int)
  g = &Game{
    bestScore: 0,
    scores:    scoreWriter,
  }
  go run(&g.bestScore, scoreReader)
  return g
}

func (g *Game) HandlePlayer(p Player) error {
  for {
    score, err := p.NextScore()
    if err != nil {
      return err
    }
    g.scores <- score
  }
}

如果垃圾回收关闭了一个通道,而我们可以证明它永远不会有更多的值,那么这个解决方案是完全可行的。是的,是的,run 中的评论暗示着有一把相当大的枪瞄准了你的脚,但至少现在这个问题可以很容易地解决了,而以前确实不是这样。此外,一个聪明的编译器可能会做出适当的证明,以减少这种脚枪造成的损害。

其他小问题

  • Dup 通道吗? —— 如果我们可以在通道上使用等效于 dup 的系统调用,那么我们也可以很容易地解决多生产者问题。 每个生产者可以关闭自己的 dup 版通道,而不会破坏其他生产者。
  • 修复通道 API! —— 关闭不是幂等的吗? 在已关闭的通道上发送信息引起的 panics 没有办法避免吗? 啊!
  • 任意缓冲的通道 —— 如果我们可以创建没有固定的缓冲区大小限制的缓冲通道,那么我们可以创建非阻塞的通道。

那我们该怎么向大家介绍 Go 呢?

如果你还没有,请看看我目前最喜欢的编程文章:《你的函数是什么颜色》。虽然不是专门针对 Go,但这篇博文比我更有说服力地阐述了为什么 goroutines 是 Go 最好的特性(这也是 Go 在某些应用程序中优于 Rust 的方式之一)。

如果你还在使用这样的一种编程语言写代码,它强迫你使用类似 yield 关键字来获得高性能、并发性或事件驱动的模型,那么你就是活在过去,不管你或其他人是否知道这一点。到目前为止,Go 是我所见过的实现 M:N 线程模型(非 1:1 )的语言中最好的入门者之一,而且这种模型非常强大。

所以,跟大家说说 goroutines 吧。

如果非要我选择 Go 的另一个主要特性,那就是接口。静态类型的 鸭子模型 duck typing 使得扩展、使用你自己或他人的项目变得如此有趣而令人惊奇,这也许值得我改天再写一组完全不同的文章来介绍它。

所以…

我一直看到人们争先恐后冲进 Go,渴望充分利用通道来发挥其全部潜力。这是我对你的建议。

够了!

当你在编写 API 和接口时,尽管“绝不”的建议可能很糟糕,但我非常肯定,通道从来没有什么时候好过,我用过的每一个使用通道的 Go API,最后都不得不与之抗争。我从来没有想过“哦 太好了,这里是一个通道;”它总是被一些变体取代,这是什么新鲜的地狱?

所以,请在适当的地方,并且只在适当的地方使用通道。

在我使用的所有 Go 代码中,我可以用一只手数出有多少次通道真的是最好的选择。有时候是这样的。那很好!那就用它们吧。但除此之外,就不要再使用了。

特别感谢我的校对读者 Jeff Wendling、Andrew HardingGeorge ShankTyler Treat 提供的宝贵反馈。

如果你想和我们一起用 Go 在 Space Monkey 项目工作,请给我打个招呼


via: https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-feel-bad

作者:jtolio.com 选题:lujun9972 译者:gxlct008 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

轻巧而直接,nano 提供了一个简单、直观的编辑器,没有额外的麻烦。

许多 Linux 发行版都捆绑了 Vim 作为默认的文本编辑器。这吸引了很多长期使用 Linux 的用户,反正那些不喜欢它的用户也可以在安装后及时更换。不过 Vim 是一个很有趣的编辑器,因为它是少数几个打开时的模式不允许输入文字的编辑器之一。这对任何用户来说都是一个令人费解的选择,对一个新用户来说也是很困惑的。

多亏了 GNU nano,才有了 Vim 之外的另一种轻量级终端文本编辑器,而且使用起来非常方便 —— 它的窗口底部列出了最重要的命令。

 title=

安装

在 Linux 和 macOS 上,你可能已经安装了 GNU nano。你可以用 which 命令来验证:

$ which nano
/bin/nano

如果你没有安装它,你可以从你的软件库中安装,或者你可以自己下载它的源代码并编译

在 Windows 上,你可以使用 Chocolatey安装 GNU nano

启动 nano

从终端启动 nano,要么单独打开它:

$ nano

或者你也可以在你的命令后面加上一个文件的路径来打开一个特定的文件。如果你命名的文件还不存在,它就会被创建:

$ nano example.txt

使用 nano

nano,是个只要稍看一下,就会发现它是一个非常自明的东西。当你启动它的时候,nano 会打开一个空的缓冲区或者你要打开的文件。在屏幕下方,有一个功能列表和相应的键盘快捷键。更多的功能可以按 Ctrl+G 获取帮助。

以下是最重要的应用程序命令:

  • Ctrl+S 保存你的工作
  • Ctrl+W 另存为
  • Ctrl+R 加载文件(读取)
  • Ctrl+X 退出
  • Ctrl+G 获得帮助

以下是最常用的编辑命令:

  • Alt+A 选择(标记)一个区域
  • Ctrl+K 剪切标记的文字
  • Ctrl+U 粘贴(不剪切)
  • Alt+F 撤销
  • Alt+E 重做

可定制

nano 不像 Emacs 或 Vim 那样可扩展,但你可以在一个名为 ~/.nanorc 的文件中进行一些重要的定制。在这个文件中,你可以设置全局的偏好,包括文字折行设置、颜色方案、行号等。你也可以创建你自己的键绑定,所以如果你想用 Ctrl+V 来代替 nano 默认的 Ctrl+U 来粘贴,你可以改变分配给 paste 函数的绑定。

bind ^V paste all

你可以在 GNU nano 文档中获得所有可用函数的列表。

简单而有效

GNU nano 是一款简单明了的文本编辑器。它易于使用,并提供了你所期望的所有文本编辑器的功能。试试吧,享受直观编辑的简单性。


via: https://opensource.com/article/20/12/gnu-nano

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你对 Git 了解得越多,使用 Git 就会越容易。一起来回顾下年度最佳 Git 文章。

Git 是开源开发者工具箱中最基本的工具。这个强大的版本控制系统有很多复杂的功能。使用 Git 不需要了解它所有的功能,但是对 Git 了解得越多,使用 Git 就会越容易。

下面每篇文章都提供了一些奇技淫巧来提升和增强你的 Git 技能。

怎么解决 git 合并时的冲突

Brian Breniser 的这篇教程从 git merge 的定义以及解释什么是冲突开始。然后他详细解释了在合并时如果有冲突如何解决冲突。Breniser 还提了一些能学习更多关于解决冲突和其他 Git 功能的建议。

4 个不可或缺的 Git 脚本

Vince Power 分享了他最重要的 Git 脚本。这些脚本可以从 Git Extras 包中获得,该包提供了 60 多个 Git 增强脚本。Power 最爱的脚本有:在无需打开文本编辑器的情况下编辑 .git-ignoregit-ignore ;用于提供 Git 仓库的摘要的 git-info;用来处理 GitLab 的合并请求(MR)和 GitHub 的拉取请求(PR)的 git-pr;把 Git 的提交(commit)、标签(tag)和推送(push)合为一体的 git-release

完美生活:git rebase -i

在 Dave Neary 的这篇文章中可以学习使用 git rebase -i 来修改你的 Git 提交历史。Neary 从解释 Git 是如何把提交历史记录到仓库中的以及 git commitgit rebase 的区别。之后,他又解释了如何使用 git rebase -i 让 Git 仓库的提交历史变得简洁。这个命令能让你把“修复书写错误”的提交合进其它提交里,把几个相似的小提交合并成一个大的提交。

Git Cola 让使用 Git 变得简单

Seth Kenlon 演示了如何使用 Git Cola。Git 是个命令行工具,这对于有些人来说是有学习门槛的。Git Cola 提供了一个图形界面,因此不习惯用命令行的用户也可以使用 Git。在此文中,Kenlon 展示了如何安装 Git Cola,并使用 Git Cola 的图形用户界面完成了很多 Git 提交任务。

6 个在团队中使用 Git 的最佳实践

从设计上讲,Git 是个协同工具,但是关于如何协同的很多细节是由团队自行决定的。Ravi Chandran 提了一些建议,团队应该采用这些建议更高效地使用 Git。Chandran 在文中列出的 6 个最佳实践是:“使约定正式化”,“正确地合并修改”,“经常变基你的功能分支”,“在合并之前把压扁你的提交”,“使用标签”,“让软件的可执行程序打印标签”。

改变我使用 Git 工作方式的七个技巧

Rajeev Bera 分享了 7 个 Git 技巧,这些技巧能提升 Git 的用户体验。文章探讨了 Git 的自动更正、提交计数、仓库优化、备份未追踪的文件、了解 .git 目录、在另一个分支查看文件以及在 Git 下搜索。

使用 tmux 和 Git 定制化我的 Linux 终端

Moshe Zadka 展示了他是如何使用 tmux 和 Git定制化他的 Linux 终端的。Zadka 的文章是个人工作流的优秀探索。他使用 GNOME 终端,用 tmux 和一些能让他快速查看 Git 仓库状态的功能来增强终端。他只需要用一个字母就可以提交文件或把提交推送到远程仓库。

使用 Lazygit 让复杂的 Git 任务简单化

Jesse Duffield 解释了如何使用Lazygit,一个能让使用 Git 变得简单的终端界面。Lazygit 的开发者 Duffield 详细阐述了如何使用这个界面来暂存文件、以交互方式变基、进行筛选、搜索提交以及创建一个 PR。

使用子模块和子树来管理 Git 项目

子模块和子树是两种在 Git 仓库中引入嵌套的子项目的方式。在使用子模块和子树来管理 Git 项目中,Manaswini Das 解释了两个选项的工作原理和区别。

不喜欢 diff?那么试试 Meld

Ben Nuttall 展示了如何使用 Meld 代替 diff来进行对比和合并修改。Meld 是图形化的 diff,输出更容易理解。Nuttall 演示了使用 diff 和 Meld 进行对比的区别。他还解释了 Meld 是如何识别 Git 项目的,这意味着在 Git 中一个文件被提交之后,可以用 Meld 来搜索修改。

你想学习关于 Git 的什么内容?请在评论去分享你的想法。


via: https://opensource.com/article/20/12/git

作者:Joshua Allen Holm 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你可以尝试以多种语言编程一个简单的游戏来开始编程之路。

 title=

当你想学习一门新的编程语言时,不妨关注一下编程语言的共同点。

  • 变量
  • 表达式
  • 语句

这些概念是大多数编程语言的基础。一旦你理解了它们,你就可以开始弄清楚其余的东西。

因为编程语言通常具有相似性,一旦你懂了一种语言,你就可以通过理解其差异来学习另一种语言的基础知识。

学习新语言的一个好方法是使用一个你可以用来练习的标准程序。这可以让你专注于语言,而不是程序的逻辑。我在这一系列文章中使用了一个“猜数字”的程序,在这个程序中,电脑会在 1 到 100 之间选一个数字让你猜。程序一直循环,直到你猜对数字为止。

这个程序锻炼了编程语言的几个概念:

  • 变量
  • 输入
  • 输出
  • 条件评估
  • 循环

这是学习一门新编程语言的很好的实践实验。

安装 Rust

你可以使用 Rustup 安装一个 Rust 工具链,或者你可以在线尝试 Rust 而不在本地安装它。

如果你在本地安装,你应该定期用 rustup update 来更新它,以保持你的工具链的新鲜,并使用 cargo update 来保持你的库的最新版本。

Rust 语言版本的猜数字

Rust 是一门赋予任何人构建可靠和高效的软件能力的语言。你可以通过编写一个 Rust 版本的“猜数字”游戏来探索 Rust。

第一步是编写一个 Cargo.toml 文件。你可以使用 cargo new 命令生成一个骨架 Cargo.toml。这几乎是开始一个 Rust 项目的最佳方式。

$ cargo new guess
$ cd guess
$ ls -1
Cargo.toml
src/

Cargo.toml 文件为你的包命名,并给它一些元数据,最重要的是,指明了它依赖于 rand crate

[package]
name = "guess"
version = "2020.11.0"
authors = ["Moshe Zadka <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "*"

Rust 中的许多东西不是由语言或标准库提供的。取而代之的是,你可以从某个外部 crate 得到它们,这些 crate 可以做许多事情。

程序逻辑在 src/main.rs 中:

use rand::Rng;
use std::io::BufRead;

fn main() {
    let mut rng = rand::thread_rng();
    let random = rng.gen_range(1..101);
    println!("Guess a number between 1 and 100");
    for line in std::io::stdin().lock().lines() {
        let parsed = line.ok().as_deref().map(str::parse::<i64>);
        if let Some(Ok(guess)) = parsed {
            match guess {
                _ if guess < random => println!("Too low"),
                _ if guess > random => println!("Too high"),
                _ => {
                    println!("That's right");
                    break;
                }
            }
        }
    }
}

代码的前两行声明你要做什么。在本例中,rand::Rng 生成一个猜测值,而 trait std::io::BufRead 使得可以从标准输入中读取。

Rust 代码的入口在 main() 函数中,所以下一步就是定义 main()

要给一个变量赋值,先放 let,再放变量的名字,后面放 = 号。这样就创建了一个不可变变量。

Rust 中大多数变量都是不可变的,但是 rng 对象必须是可变的(mut)。例如,语句 let random = 0random 变量分配一个零值。

函数的第一行创建了一个线程安全的 Rng 对象,并将其分配给变量 rng。Rust 是建立在线程和内存安全的基础上的,所以你必须在开始写代码时就考虑到这些事情。

程序的下一行读取函数 gen_range() 的结果,并将其分配给名为 random 的变量。该函数取一个最小值(包含)和一个上界(不包含)。为了也包含上界,你可以用一个等号来标记较大的数字(例如,1...=100),或者你也可以像我在代码中做的那样,将上界设置为比你的预期最大值大 1。在这种情况下,该范围是 1100,使得游戏刚好有足够的挑战性。

中央循环在 std::io::stdin() 中迭代行。由于有各种可能导致行不能读取的例外情况,Rust 要求你用一个 Result 来包裹一行。有时候可能无法将一行解析为一个整数。

这段代码使用条件模式匹配来忽略所有会导致错误的行:

        let parsed = line.ok().as_deref().map(str::parse::<i64>);
        if let Some(Ok(guess)) = parsed {
            // ...
        }

第一行创建了一个 Result<Option<i64>, ...> 对象,因为它可能在读取或解析步骤中失败。由于下一行只在 Some(Ok(guess)) 上匹配,每当一行的结果是一个不匹配的值时,它就会跳过 if 语句。这是一种强大的忽略错误的方法。

Rust 支持条件表达式和流程控制,比如循环。在“猜数字”游戏中,只要猜中的值不等于 random,Rust 就会继续循环。

if 语句的主体包含一个 Rust 的 match 语句的三向分支。虽然 match 最常用于模式匹配,但它也可以检查任意条件。在这种情况下是打印一个适当的信息,如果猜测是正确的,则中断(break)循环。

示例输出

现在你已经写好了你的 Rust 程序,你可以运行它来玩“猜数字”游戏。每次运行程序时,Rust 都会选择一个不同的随机数,所以继续猜,直到找到正确的数字。

$ cargo run
   Compiling guess v2020.11.0 (/Users/mzadka/src/guess)
    Finished dev [unoptimized + debuginfo] target(s) in 0.70s
     Running `target/debug/guess`
Guess a number between 1 and 100
50
Too high
25
Too high
12
Too low
18
Too high
15
Too high
13
Too low
14
That's right

典型的做法是用 cargo run 来测试程序。最终,你可能会使用 cargo build 分成两个独立的步骤构建一个可执行文件并运行它

学习 Rust

这个“猜数字”游戏是学习一门新的编程语言的一个很好的入门程序,因为它以一种相当直接的方式锻炼了几个常见的编程概念。通过在不同的编程语言中实现这个简单的游戏,你可以展示语言的一些核心概念,并比较它们的细节。

你有喜欢的编程语言吗?你会如何用它来写“猜数字”游戏呢?请关注本系列文章,看看你可能感兴趣的其他编程语言的例子吧!


via: https://opensource.com/article/20/12/learn-rust

作者:Moshe Zadka 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出