mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-19 22:51:41 +08:00
Merge remote-tracking branch 'LCTT/master'
This commit is contained in:
commit
c9d0e62163
@ -1,46 +1,49 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (FSSlc)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-10845-1.html)
|
||||
[#]: subject: (Inter-process communication in Linux: Using pipes and message queues)
|
||||
[#]: via: (https://opensource.com/article/19/4/interprocess-communication-linux-channels)
|
||||
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
|
||||
|
||||
Linux 下的进程间通信:使用管道和消息队列
|
||||
======
|
||||
学习在 Linux 中进程是如何与其他进程进行同步的。
|
||||
|
||||
> 学习在 Linux 中进程是如何与其他进程进行同步的。
|
||||
|
||||
![Chat bubbles][1]
|
||||
|
||||
本篇是 Linux 下[进程间通信][2](IPC)系列的第二篇文章。[第一篇文章][3] 聚焦于通过共享文件和共享内存段这样的共享存储来进行 IPC。这篇文件的重点将转向管道,它是连接需要通信的进程之间的通道。管道拥有一个 _写端_ 用于写入字节数据,还有一个 _读端_ 用于按照先入先出的顺序读入这些字节数据。而这些字节数据可能代表任何东西:数字、数字电影等等。
|
||||
本篇是 Linux 下[进程间通信][2](IPC)系列的第二篇文章。[第一篇文章][3] 聚焦于通过共享文件和共享内存段这样的共享存储来进行 IPC。这篇文件的重点将转向管道,它是连接需要通信的进程之间的通道。管道拥有一个*写端*用于写入字节数据,还有一个*读端*用于按照先入先出的顺序读入这些字节数据。而这些字节数据可能代表任何东西:数字、员工记录、数字电影等等。
|
||||
|
||||
管道有两种类型,命名管道和无名管道,都可以交互式的在命令行或程序中使用它们;相关的例子在下面展示。这篇文章也将介绍内存队列,尽管它们有些过时了,但它们不应该受这样的待遇。
|
||||
|
||||
在本系列的第一篇文章中的示例代码承认了在 IPC 中可能受到竞争条件(不管是基于文件的还是基于内存的)的威胁。自然地我们也会考虑基于管道的 IPC 的安全并发问题,这个也将在本文中提及。针对管道和内存队列的例子将会使用 POSIX 推荐使用的 API, POSIX 的一个核心目标就是线程安全。
|
||||
在本系列的第一篇文章中的示例代码承认了在 IPC 中可能受到竞争条件(不管是基于文件的还是基于内存的)的威胁。自然地我们也会考虑基于管道的 IPC 的安全并发问题,这个也将在本文中提及。针对管道和内存队列的例子将会使用 POSIX 推荐使用的 API,POSIX 的一个核心目标就是线程安全。
|
||||
|
||||
考虑查看 [**mq_open** 函数的 man 页][4],这个函数属于内存队列的 API。这个 man 页中有一个章节是有关 [特性][5] 的小表格:
|
||||
请查看一些 [mq_open 函数的 man 页][4],这个函数属于内存队列的 API。这个 man 页中有关 [特性][5] 的章节带有一个小表格:
|
||||
|
||||
Interface | Attribute | Value
|
||||
接口 | 特性 | 值
|
||||
---|---|---
|
||||
mq_open() | Thread safety | MT-Safe
|
||||
`mq_open()` | 线程安全 | MT-Safe
|
||||
|
||||
上面的 **MT-Safe**(**MT** 指的是 multi-threaded,多线程)意味着 **mq_open** 函数是线程安全的,进而暗示是进程安全的:一个进程的执行和它的一个线程执行的过程类似,假如竞争条件不会发生在处于 _相同_ 进程的线程中,那么这样的条件也不会发生在处于不同进程的线程中。**MT-Safe** 特性保证了调用 **mq_open** 时不会出现竞争条件。一般来说,基于通道的 IPC 是并发安全的,尽管在下面例子中会出现一个有关警告的注意事项。
|
||||
上面的 MT-Safe(MT 指的是<ruby>多线程<rt>multi-threaded</rt></ruby>)意味着 `mq_open` 函数是线程安全的,进而暗示是进程安全的:一个进程的执行和它的一个线程执行的过程类似,假如竞争条件不会发生在处于*相同*进程的线程中,那么这样的条件也不会发生在处于不同进程的线程中。MT-Safe 特性保证了调用 `mq_open` 时不会出现竞争条件。一般来说,基于通道的 IPC 是并发安全的,尽管在下面例子中会出现一个有关警告的注意事项。
|
||||
|
||||
### 无名管道
|
||||
|
||||
首先让我们通过一个特意构造的命令行例子来展示无名管道是如何工作的。在所有的现代系统中,符号 **|** 在命令行中都代表一个无名管道。假设我们的命令行提示符为 **%**,接下来考虑下面的命令:
|
||||
首先让我们通过一个特意构造的命令行例子来展示无名管道是如何工作的。在所有的现代系统中,符号 `|` 在命令行中都代表一个无名管道。假设我们的命令行提示符为 `%`,接下来考虑下面的命令:
|
||||
|
||||
```shell
|
||||
% sleep 5 | echo "Hello, world!" ## writer to the left of |, reader to the right
|
||||
## 写入方在 | 左边,读取方在右边
|
||||
% sleep 5 | echo "Hello, world!"
|
||||
```
|
||||
|
||||
_sleep_ 和 _echo_ 程序以不同的进程执行,无名管道允许它们进行通信。但是上面的例子被特意设计为没有通信发生。问候语 _Hello, world!_ 出现在屏幕中,然后过了 5 秒后,命令行返回,暗示 _sleep_ 和 _echo_ 进程都已经结束了。这期间发生了什么呢?
|
||||
`sleep` 和 `echo` 程序以不同的进程执行,无名管道允许它们进行通信。但是上面的例子被特意设计为没有通信发生。问候语 “Hello, world!” 出现在屏幕中,然后过了 5 秒后,命令行返回,暗示 `sleep` 和 `echo` 进程都已经结束了。这期间发生了什么呢?
|
||||
|
||||
在命令行中的竖线 **|** 的语法中,左边的进程(_sleep_)是写入方,右边的进程(_echo_)为读取方。默认情况下,读入方将会堵塞,直到从通道中能够读取字节数据,而写入方在写完它的字节数据后,将发送 流已终止 的标志。(即便写入方永久地停止了,一个流已终止的标志还是会发给读取方。)无名管道将保持到写入方和读取方都停止的那个时刻。
|
||||
在命令行中的竖线 `|` 的语法中,左边的进程(`sleep`)是写入方,右边的进程(`echo`)为读取方。默认情况下,读取方将会阻塞,直到从通道中能够读取到字节数据,而写入方在写完它的字节数据后,将发送 <ruby>流已终止<rt>end-of-stream</rt></ruby>的标志。(即便写入方过早终止了,一个流已终止的标志还是会发给读取方。)无名管道将保持到写入方和读取方都停止的那个时刻。
|
||||
|
||||
在上面的例子中,_sleep_ 进程并没有向通道写入任何的字节数据,但在 5 秒后就停止了,这时将向通道发送一个流已终止的标志。与此同时,_echo_ 进程立即向标准输出(屏幕)写入问候语,因为这个进程并不从通道中读入任何字节,所以它并没有等待。一旦 _sleep_ 和 _echo_ 进程都终止了,不会再用作通信的无名管道将会消失然后返回命令行提示符。
|
||||
在上面的例子中,`sleep` 进程并没有向通道写入任何的字节数据,但在 5 秒后就终止了,这时将向通道发送一个流已终止的标志。与此同时,`echo` 进程立即向标准输出(屏幕)写入问候语,因为这个进程并不从通道中读入任何字节,所以它并没有等待。一旦 `sleep` 和 `echo` 进程都终止了,不会再用作通信的无名管道将会消失然后返回命令行提示符。
|
||||
|
||||
下面这个更加实用示例将使用两个无名管道。我们假定文件 _test.dat_ 的内容如下:
|
||||
下面这个更加实用的示例将使用两个无名管道。我们假定文件 `test.dat` 的内容如下:
|
||||
|
||||
```
|
||||
this
|
||||
@ -52,13 +55,13 @@ world
|
||||
ends
|
||||
```
|
||||
|
||||
下面的命令
|
||||
下面的命令:
|
||||
|
||||
```
|
||||
% cat test.dat | sort | uniq
|
||||
```
|
||||
|
||||
会将 _cat_(concatenate 的缩写)进程的输出通过管道传给 _sort_ 进程以生成排序后的输出,然后将排序后的输出通过管道传给 _uniq_ 进程以消除重复的记录(在本例中,会将两次出现的 **the** 缩减为一个):
|
||||
会将 `cat`(<ruby>连接<rt>concatenate</rt></ruby>的缩写)进程的输出通过管道传给 `sort` 进程以生成排序后的输出,然后将排序后的输出通过管道传给 `uniq` 进程以消除重复的记录(在本例中,会将两次出现的 “the” 缩减为一个):
|
||||
|
||||
```
|
||||
ends
|
||||
@ -73,7 +76,6 @@ world
|
||||
|
||||
#### 示例 1. 两个进程通过一个无名管道来进行通信
|
||||
|
||||
|
||||
```c
|
||||
#include <sys/wait.h> /* wait */
|
||||
#include <stdio.h>
|
||||
@ -120,21 +122,23 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
上面名为 _pipeUN_ 的程序使用系统函数 **fork** 来创建一个进程。尽管这个程序只有一个单一的源文件,在它正确执行的情况下将会发生多进程的情况。下面的内容是对库函数 **fork** 如何工作的一个简要回顾:
|
||||
上面名为 `pipeUN` 的程序使用系统函数 `fork` 来创建一个进程。尽管这个程序只有一个单一的源文件,在它正确执行的情况下将会发生多进程的情况。
|
||||
|
||||
* **fork** 函数由 _父_ 进程调用,在失败时返回 **-1** 给父进程。在 _pipeUN_ 这个例子中,相应的调用是
|
||||
> 下面的内容是对库函数 `fork` 如何工作的一个简要回顾:
|
||||
|
||||
```c
|
||||
> * `fork` 函数由*父*进程调用,在失败时返回 `-1` 给父进程。在 `pipeUN` 这个例子中,相应的调用是:
|
||||
|
||||
> ```c
|
||||
pid_t cpid = fork(); /* called in parent */
|
||||
```
|
||||
|
||||
函数,调用后的返回值也被保存下来了。在这个例子中,保存在整数类型 **pid_t** 的变量 **cpid** 中。(每个进程有它自己的 _进程 ID_,一个非负的整数,用来标记进程)。复刻一个新的进程可能会因为多种原因而失败。最终它们将会被包括进一个完整的 _进程表_,这个结构由系统维持,以此来追踪进程状态。明确地说,僵尸进程假如没有被处理掉,将可能引起一个进程表被填满。
|
||||
* 假如 **fork** 调用成功,则它将创建一个新的子进程,向父进程返回一个值,向子进程返回另外的一个值。在调用 **fork** 后父进程和子进程都将执行相同的代码。(子进程继承了到此为止父进程中声明的所有变量的拷贝),特别地,一次成功的 **fork** 调用将返回如下的东西:
|
||||
* 向子进程返回 0
|
||||
* 向父进程返回子进程的进程 ID
|
||||
* 在依次成功的 **fork** 调用后,一个 _if/else_ 或等价的结构将会被用来隔离针对父进程和子进程的代码。在这个例子中,相应的声明为:
|
||||
> 函数调用后的返回值也被保存下来了。在这个例子中,保存在整数类型 `pid_t` 的变量 `cpid` 中。(每个进程有它自己的*进程 ID*,这是一个非负的整数,用来标记进程)。复刻一个新的进程可能会因为多种原因而失败,包括*进程表*满了的原因,这个结构由系统维持,以此来追踪进程状态。明确地说,僵尸进程假如没有被处理掉,将可能引起进程表被填满的错误。
|
||||
> * 假如 `fork` 调用成功,则它将创建一个新的子进程,向父进程返回一个值,向子进程返回另外的一个值。在调用 `fork` 后父进程和子进程都将执行相同的代码。(子进程继承了到此为止父进程中声明的所有变量的拷贝),特别地,一次成功的 `fork` 调用将返回如下的东西:
|
||||
> * 向子进程返回 `0`
|
||||
> * 向父进程返回子进程的进程 ID
|
||||
> * 在一次成功的 `fork` 调用后,一个 `if`/`else` 或等价的结构将会被用来隔离针对父进程和子进程的代码。在这个例子中,相应的声明为:
|
||||
|
||||
```c
|
||||
> ```c
|
||||
if (0 == cpid) { /*** child ***/
|
||||
...
|
||||
}
|
||||
@ -143,25 +147,25 @@ else { /*** parent ***/
|
||||
}
|
||||
```
|
||||
|
||||
假如成功地复刻出了一个子进程,_pipeUN_ 程序将像下面这样去执行。存在一个整数的数列
|
||||
假如成功地复刻出了一个子进程,`pipeUN` 程序将像下面这样去执行。在一个整数的数列里:
|
||||
|
||||
```c
|
||||
int pipeFDs[2]; /* two file descriptors */
|
||||
```
|
||||
|
||||
来保存两个文件描述符,一个用来向管道中写入,另一个从管道中写入。(数组元素 **pipeFDs[0]** 是读端的文件描述符,元素 **pipeFDs[1]** 是写端的文件描述符。)在调用 **fork** 之前,对系统 **pipe** 函数的成功调用,将立刻使得这个数组获得两个文件描述符:
|
||||
来保存两个文件描述符,一个用来向管道中写入,另一个从管道中写入。(数组元素 `pipeFDs[0]` 是读端的文件描述符,元素 `pipeFDs[1]` 是写端的文件描述符。)在调用 `fork` 之前,对系统 `pipe` 函数的成功调用,将立刻使得这个数组获得两个文件描述符:
|
||||
|
||||
```c
|
||||
if (pipe(pipeFDs) < 0) report_and_exit("pipeFD");
|
||||
```
|
||||
|
||||
父进程和子进程现在都有了文件描述符的副本。但 _分离关注点_ 模式意味着每个进程恰好只需要一个描述符。在这个例子中,父进程负责写入,而子进程负责读取,尽管这样的角色分配可以反过来。在 _if_ 子句中的第一个语句将用于关闭管道的读端:
|
||||
父进程和子进程现在都有了文件描述符的副本。但*分离关注点*模式意味着每个进程恰好只需要一个描述符。在这个例子中,父进程负责写入,而子进程负责读取,尽管这样的角色分配可以反过来。在 `if` 子句中的第一个语句将用于关闭管道的读端:
|
||||
|
||||
```c
|
||||
close(pipeFDs[WriteEnd]); /* called in child code */
|
||||
```
|
||||
|
||||
在父进程中的 _else_ 子句将会关闭管道的读端:
|
||||
在父进程中的 `else` 子句将会关闭管道的读端:
|
||||
|
||||
```c
|
||||
close(pipeFDs[ReadEnd]); /* called in parent code */
|
||||
@ -169,23 +173,23 @@ close(pipeFDs[ReadEnd]); /* called in parent code */
|
||||
|
||||
然后父进程将向无名管道中写入某些字节数据(ASCII 代码),子进程读取这些数据,然后向标准输出中回放它们。
|
||||
|
||||
在这个程序中还需要澄清的一点是在父进程代码中的 **wait** 函数。一旦被创建后,子进程很大程度上独立于它的父进程,正如简短的 _pipeUN_ 程序所展示的那样。子进程可以执行任意的代码,而它们可能与父进程完全没有关系。但是,假如当子进程终止时,系统将会通过一个信号来通知父进程。
|
||||
在这个程序中还需要澄清的一点是在父进程代码中的 `wait` 函数。一旦被创建后,子进程很大程度上独立于它的父进程,正如简短的 `pipeUN` 程序所展示的那样。子进程可以执行任意的代码,而它们可能与父进程完全没有关系。但是,假如当子进程终止时,系统将会通过一个信号来通知父进程。
|
||||
|
||||
要是父进程在子进程之前终止又该如何呢?在这种情形下,除非采取了预防措施,子进程将会变成在进程表中的一个 _僵尸_ 进程。预防措施有两大类型:第一种是让父进程去通知系统,告诉系统它对子进程的终止没有任何兴趣:
|
||||
要是父进程在子进程之前终止又该如何呢?在这种情形下,除非采取了预防措施,子进程将会变成在进程表中的一个*僵尸*进程。预防措施有两大类型:第一种是让父进程去通知系统,告诉系统它对子进程的终止没有任何兴趣:
|
||||
|
||||
```c
|
||||
signal(SIGCHLD, SIG_IGN); /* in parent: ignore notification */
|
||||
```
|
||||
|
||||
第二种方法是在子进程终止时,让父进程执行一个 **wait**。这样就确保了父进程可以独立于子进程而存在。在 _pipeUN_ 程序中使用了第二种方法,其中父进程的代码使用的是下面的调用:
|
||||
第二种方法是在子进程终止时,让父进程执行一个 `wait`。这样就确保了父进程可以独立于子进程而存在。在 `pipeUN` 程序中使用了第二种方法,其中父进程的代码使用的是下面的调用:
|
||||
|
||||
```c
|
||||
wait(NULL); /* called in parent */
|
||||
```
|
||||
|
||||
这个对 **wait** 的调用意味着 _一直等待直到任意一个子进程的终止发生_,因此在 _pipeUN_ 程序中,只有一个子进程。(其中的 **NULL** 参数可以被替换为一个保存有子程序退出状态的整数变量的地址。)对于更细颗粒度的控制,还可以使用更灵活的 **waitpid** 函数,例如特别指定多个子进程中的某一个。
|
||||
这个对 `wait` 的调用意味着*一直等待直到任意一个子进程的终止发生*,因此在 `pipeUN` 程序中,只有一个子进程。(其中的 `NULL` 参数可以被替换为一个保存有子程序退出状态的整数变量的地址。)对于更细粒度的控制,还可以使用更灵活的 `waitpid` 函数,例如特别指定多个子进程中的某一个。
|
||||
|
||||
_pipeUN_ 将会采取另一个预防措施。当父进程结束了等待,父进程将会调用常规的 **exit** 函数去退出。对应的,子进程将会调用 **_exit** 变种来退出,这类变种将快速跟踪终止相关的通知。在效果上,子进程会告诉系统立刻去通知父进程它的这个子进程已经终止了。
|
||||
`pipeUN` 将会采取另一个预防措施。当父进程结束了等待,父进程将会调用常规的 `exit` 函数去退出。对应的,子进程将会调用 `_exit` 变种来退出,这类变种将快速跟踪终止相关的通知。在效果上,子进程会告诉系统立刻去通知父进程它的这个子进程已经终止了。
|
||||
|
||||
假如两个进程向相同的无名管道中写入内容,字节数据会交错吗?例如,假如进程 P1 向管道写入内容:
|
||||
|
||||
@ -205,42 +209,42 @@ baz baz
|
||||
baz foo baz bar
|
||||
```
|
||||
|
||||
POSIX 标准确保了写不是交错的,使得没有写操作能够超过 **PIPE_BUF** 的范围。在 Linux 系统中, **PIPE_BUF** 的大小是 4096 字节。对于管道我更喜欢只有一个写方和一个读方,从而绕过这个问题。
|
||||
只要没有写入超过 `PIPE_BUF` 字节,POSIX 标准就能确保写入不会交错。在 Linux 系统中, `PIPE_BUF` 的大小是 4096 字节。对于管道我更喜欢只有一个写入方和一个读取方,从而绕过这个问题。
|
||||
|
||||
## 命名管道
|
||||
### 命名管道
|
||||
|
||||
无名管道没有备份文件:系统将维持一个内存缓存来将字节数据从写方传给读方。一旦写方和读方终止,这个缓存将会被回收,进而无名管道消失。相反的,命名管道有备份文件和一个不同的 API。
|
||||
|
||||
下面让我们通过另一个命令行示例来知晓命名管道的要点。下面是具体的步骤:
|
||||
下面让我们通过另一个命令行示例来了解命名管道的要点。下面是具体的步骤:
|
||||
|
||||
* 开启两个终端。这两个终端的工作目录应该相同。
|
||||
* 在其中一个终端中,键入下面的两个命令(命令行提示符仍然是 **%**,我的注释以 **##** 打头。):
|
||||
* 开启两个终端。这两个终端的工作目录应该相同。
|
||||
* 在其中一个终端中,键入下面的两个命令(命令行提示符仍然是 `%`,我的注释以 `##` 打头。):
|
||||
|
||||
```shell
|
||||
% mkfifo tester ## creates a backing file named tester
|
||||
% cat tester ## type the pipe's contents to stdout
|
||||
```shell
|
||||
% mkfifo tester ## 创建一个备份文件,名为 tester
|
||||
% cat tester ## 将管道的内容输出到 stdout
|
||||
```
|
||||
|
||||
在最开始,没有任何东西会出现在终端中,因为到现在为止没有命名管道中写入任何东西。
|
||||
* 在第二个终端中输入下面的命令:
|
||||
在最开始,没有任何东西会出现在终端中,因为到现在为止没有在命名管道中写入任何东西。
|
||||
* 在第二个终端中输入下面的命令:
|
||||
|
||||
```shell
|
||||
```shell
|
||||
% cat > tester ## redirect keyboard input to the pipe
|
||||
hello, world! ## then hit Return key
|
||||
bye, bye ## ditto
|
||||
<Control-C> ## terminate session with a Control-C
|
||||
```
|
||||
|
||||
无论在这个终端中输入什么,它都会在另一个终端中显示出来。一旦键入 **Ctrl+C**,就会回到正常的命令行提示符,因为管道已经被关闭了。
|
||||
* 通过移除实现命名管道的文件来进行清理:
|
||||
无论在这个终端中输入什么,它都会在另一个终端中显示出来。一旦键入 `Ctrl+C`,就会回到正常的命令行提示符,因为管道已经被关闭了。
|
||||
* 通过移除实现命名管道的文件来进行清理:
|
||||
|
||||
```shell
|
||||
```shell
|
||||
% unlink tester
|
||||
```
|
||||
|
||||
正如 _mkfifo_ 程序的名字所暗示的那样,一个命名管道也被叫做一个 FIFO,因为第一个字节先进,然后第一个字节就先出,其他的类似。存在一个名为 **mkfifo** 的库函数,用它可以在程序中创建一个命名管道,它将在下一个示例中被用到,该示例由两个进程组成:一个向命名管道写入,而另一个从该管道读取。
|
||||
正如 `mkfifo` 程序的名字所暗示的那样,命名管道也被叫做 FIFO,因为第一个进入的字节,就会第一个出,其他的类似。有一个名为 `mkfifo` 的库函数,用它可以在程序中创建一个命名管道,它将在下一个示例中被用到,该示例由两个进程组成:一个向命名管道写入,而另一个从该管道读取。
|
||||
|
||||
#### 示例 2. _fifoWriter_ 程序
|
||||
#### 示例 2. fifoWriter 程序
|
||||
|
||||
```c
|
||||
#include <sys/types.h>
|
||||
@ -283,29 +287,29 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
上面的 _fifoWriter_ 程序可以被总结为如下:
|
||||
上面的 `fifoWriter` 程序可以被总结为如下:
|
||||
|
||||
* 首先程序创建了一个命名管道用来写入数据:
|
||||
* 首先程序创建了一个命名管道用来写入数据:
|
||||
|
||||
```c
|
||||
```c
|
||||
mkfifo(pipeName, 0666); /* read/write perms for user/group/others */
|
||||
int fd = open(pipeName, O_CREAT | O_WRONLY);
|
||||
```
|
||||
|
||||
其中的 **pipeName** 是传递给 **mkfifo** 作为它的第一个参数的备份文件的名字。接着命名管道通过我们熟悉的 **open** 函数调用被打开,而这个函数将会返回一个文件描述符。
|
||||
* 在实现层面上,_fifoWriter_ 不会一次性将所有的数据都写入,而是写入一个块,然后休息随机数目的微秒时间,接着再循环往复。总的来说,有 768000 个 4 比特的整数值被写入到命名管道中。
|
||||
* 在关闭命名管道后,_fifoWriter_ 也将使用 unlink 去掉关联。
|
||||
其中的 `pipeName` 是备份文件的名字,传递给 `mkfifo` 作为它的第一个参数。接着命名管道通过我们熟悉的 `open` 函数调用被打开,而这个函数将会返回一个文件描述符。
|
||||
* 在实现层面上,`fifoWriter` 不会一次性将所有的数据都写入,而是写入一个块,然后休息随机数目的微秒时间,接着再循环往复。总的来说,有 768000 个 4 字节整数值被写入到命名管道中。
|
||||
* 在关闭命名管道后,`fifoWriter` 也将使用 `unlink` 取消对该文件的连接。
|
||||
|
||||
```c
|
||||
```c
|
||||
close(fd); /* close pipe: generates end-of-stream marker */
|
||||
unlink(pipeName); /* unlink from the implementing file */
|
||||
```
|
||||
|
||||
一旦连接到管道的每个进程都执行了 unlink 操作后,系统将回收这些备份文件。在这个例子中,只有两个这样的进程 _fifoWriter_ 和 _fifoReader_,它们都做了 _unlink_ 操作。
|
||||
一旦连接到管道的每个进程都执行了 `unlink` 操作后,系统将回收这些备份文件。在这个例子中,只有两个这样的进程 `fifoWriter` 和 `fifoReader`,它们都做了 `unlink` 操作。
|
||||
|
||||
这个两个程序应该在位于相同工作目录下的不同终端中被执行。但是 _fifoWriter_ 应该在 _fifoReader_ 之前被启动,因为需要 _fifoWriter_ 去创建管道。然后 _fifoReader_ 才能够获取到刚被创建的命名管道。
|
||||
这个两个程序应该在不同终端的相同工作目录中执行。但是 `fifoWriter` 应该在 `fifoReader` 之前被启动,因为需要 `fifoWriter` 去创建管道。然后 `fifoReader` 才能够获取到刚被创建的命名管道。
|
||||
|
||||
#### 示例 3. _fifoReader_ 程序
|
||||
#### 示例 3. fifoReader 程序
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@ -352,28 +356,28 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
上面的 _fifoReader_ 的内容可以总结为如下:
|
||||
上面的 `fifoReader` 的内容可以总结为如下:
|
||||
|
||||
* 因为 _fifoWriter_ 已经创建了命名管道,所以 _fifoReader_ 只需要利用标准的 **open** 调用来通过备份文件来获取到管道中的内容:
|
||||
* 因为 `fifoWriter` 已经创建了命名管道,所以 `fifoReader` 只需要利用标准的 `open` 调用来通过备份文件来获取到管道中的内容:
|
||||
|
||||
```c
|
||||
```c
|
||||
const char* file = "./fifoChannel";
|
||||
int fd = open(file, O_RDONLY);
|
||||
```
|
||||
|
||||
这个文件的打开是只读的。
|
||||
* 然后这个程序进入一个潜在的无限循环,在每次循环时,尝试读取 4 比特的块。**read** 调用:
|
||||
这个文件的是以只读打开的。
|
||||
* 然后这个程序进入一个潜在的无限循环,在每次循环时,尝试读取 4 字节的块。`read` 调用:
|
||||
|
||||
```c
|
||||
```c
|
||||
ssize_t count = read(fd, &next, sizeof(int));
|
||||
```
|
||||
|
||||
返回 0 来暗示流的结束。在这种情况下,_fifoReader_ 跳出循环,关闭命名管道,并在终止前 unlink 备份文件。
|
||||
* 在读入 4 比特整数后,_fifoReader_ 检查这个数是否为质数。这个操作代表了一个生产级别的读取器可能在接收到的字节数据上执行的逻辑操作。在示例运行中,接收了 768000 个整数中的 37682 个质数。
|
||||
返回 0 来暗示该流的结束。在这种情况下,`fifoReader` 跳出循环,关闭命名管道,并在终止前 `unlink` 备份文件。
|
||||
* 在读入 4 字节整数后,`fifoReader` 检查这个数是否为质数。这个操作代表了一个生产级别的读取器可能在接收到的字节数据上执行的逻辑操作。在示例运行中,在接收到的 768000 个整数中有 37682 个质数。
|
||||
|
||||
在重复的运行示例时, _fifoReader_ 将成功地读取 _fifoWriter_ 写入的所有字节。这不是很让人惊讶的。这两个进程在相同的机器上执行,从而可以不用考虑网络相关的问题。命名管道是一个可信且高效的 IPC 机制,因而被广泛使用。
|
||||
重复运行示例, `fifoReader` 将成功地读取 `fifoWriter` 写入的所有字节。这不是很让人惊讶的。这两个进程在相同的机器上执行,从而可以不用考虑网络相关的问题。命名管道是一个可信且高效的 IPC 机制,因而被广泛使用。
|
||||
|
||||
下面是这两个程序的输出,在不同的终端中启动,但处于相同的工作目录:
|
||||
下面是这两个程序的输出,它们在不同的终端中启动,但处于相同的工作目录:
|
||||
|
||||
```shell
|
||||
% ./fifoWriter
|
||||
@ -385,13 +389,14 @@ Received ints: 768000, primes: 37682
|
||||
|
||||
### 消息队列
|
||||
|
||||
管道有着严格的先入先出行为:第一个被写入的字节将会第一个被读,第二个写入的字节将第二个被读,以此类推。消息队列可以做出相同的表现,但它又足够灵活,可以使得字节块不以先入先出的次序来接收。
|
||||
管道有着严格的先入先出行为:第一个被写入的字节将会第一个被读,第二个写入的字节将第二个被读,以此类推。消息队列可以做出相同的表现,但它又足够灵活,可以使得字节块可以不以先入先出的次序来接收。
|
||||
|
||||
正如它的名字所建议的那样,消息队列是一系列的消息,每个消息包含两部分:
|
||||
* 荷载,一个字节序列(在 C 中是 **char**)
|
||||
* 一个类型,以一个正整数值的形式给定,类型用来分类消息,为了更灵活的回收
|
||||
正如它的名字所提示的那样,消息队列是一系列的消息,每个消息包含两部分:
|
||||
|
||||
考虑下面对一个消息队列的描述,每个消息被一个整数类型标记:
|
||||
* 荷载,一个字节序列(在 C 中是 char)
|
||||
* 类型,以一个正整数值的形式给定,类型用来分类消息,为了更灵活的回收
|
||||
|
||||
看一下下面对一个消息队列的描述,每个消息由一个整数类型标记:
|
||||
|
||||
```
|
||||
+-+ +-+ +-+ +-+
|
||||
@ -399,11 +404,11 @@ sender--->|3|--->|2|--->|2|--->|1|--->receiver
|
||||
+-+ +-+ +-+ +-+
|
||||
```
|
||||
|
||||
在上面展示的 4 个消息中,标记为 1 的是开头,即最接近接收端,然后另个标记为 2 的消息,最后接着一个标记为 3 的消息。假如按照严格的 FIFO 行为执行,消息将会以 1-2-2-3 这样的次序被接收。但是消息队列允许其他回收次序。例如,消息可以被接收方以 3-2-1-2 的次序接收。
|
||||
在上面展示的 4 个消息中,标记为 1 的是开头,即最接近接收端,然后另个标记为 2 的消息,最后接着一个标记为 3 的消息。假如按照严格的 FIFO 行为执行,消息将会以 1-2-2-3 这样的次序被接收。但是消息队列允许其他收取次序。例如,消息可以被接收方以 3-2-1-2 的次序接收。
|
||||
|
||||
_mqueue_ 示例包含两个程序,_sender_ 将向消息队列中写入数据,而 _receiver_ 将从这个队列中读取数据。这两个程序都包含下面展示的头文件 _queue.h_:
|
||||
`mqueue` 示例包含两个程序,`sender` 将向消息队列中写入数据,而 `receiver` 将从这个队列中读取数据。这两个程序都包含的头文件 `queue.h` 如下所示:
|
||||
|
||||
#### 示例 4. 头文件 _queue.h_
|
||||
#### 示例 4. 头文件 queue.h
|
||||
|
||||
```c
|
||||
#define ProjectId 123
|
||||
@ -417,16 +422,16 @@ typedef struct {
|
||||
} queuedMessage;
|
||||
```
|
||||
|
||||
上面的头文件定义了一个名为 **queuedMessage** 的结构类型,它带有 **payload**(字节数组)和 **type**(整数)这两个域。该文件也定义了一些符号常数(使用 **#define** 语句)。前两个常数被用来生成一个 key,而这个 key 反过来被用来获取一个消息队列的 ID。**ProjectId** 可以是任何正整数值,而 **PathName** 必须是一个存在的,可访问的文件,在这个示例中,指的是文件 _queue.h_。在 _sender_ 和 _receiver_ 中,它们都有的设定语句为:
|
||||
上面的头文件定义了一个名为 `queuedMessage` 的结构类型,它带有 `payload`(字节数组)和 `type`(整数)这两个域。该文件也定义了一些符号常数(使用 `#define` 语句),前两个常数被用来生成一个 `key`,而这个 `key` 反过来被用来获取一个消息队列的 ID。`ProjectId` 可以是任何正整数值,而 `PathName` 必须是一个存在的、可访问的文件,在这个示例中,指的是文件 `queue.h`。在 `sender` 和 `receiver` 中,它们都有的设定语句为:
|
||||
|
||||
```c
|
||||
key_t key = ftok(PathName, ProjectId); /* generate key */
|
||||
int qid = msgget(key, 0666 | IPC_CREAT); /* use key to get queue id */
|
||||
```
|
||||
|
||||
ID **qid** 在效果上是消息队列文件描述符的对应物。
|
||||
ID `qid` 在效果上是消息队列文件描述符的对应物。
|
||||
|
||||
#### 示例 5. _sender_ 程序
|
||||
#### 示例 5. sender 程序
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@ -465,15 +470,15 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
上面的 _sender_ 程序将发送出 6 个消息,每两个为一个类型:前两个是类型 1,接着的连个是类型 2,最后的两个为类型 3。发送的语句:
|
||||
上面的 `sender` 程序将发送出 6 个消息,每两个为一个类型:前两个是类型 1,接着的连个是类型 2,最后的两个为类型 3。发送的语句:
|
||||
|
||||
```c
|
||||
msgsnd(qid, &msg, sizeof(msg), IPC_NOWAIT);
|
||||
```
|
||||
|
||||
被配置为非阻塞的(**IPC_NOWAIT** 标志),因为这里的消息体量上都很小。唯一的危险在于一个完整的序列将可能导致发送失败,而这个例子不会。下面的 _receiver_ 程序也将使用 **IPC_NOWAIT** 标志来接收消息。
|
||||
被配置为非阻塞的(`IPC_NOWAIT` 标志),是因为这里的消息体量上都很小。唯一的危险在于一个完整的序列将可能导致发送失败,而这个例子不会。下面的 `receiver` 程序也将使用 `IPC_NOWAIT` 标志来接收消息。
|
||||
|
||||
#### 示例 6. _receiver_ 程序
|
||||
#### 示例 6. receiver 程序
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@ -511,13 +516,13 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
这个 _receiver_ 程序不会创建消息队列,尽管 API 看起来像是那样。在 _receiver_ 中,对
|
||||
这个 `receiver` 程序不会创建消息队列,尽管 API 尽管建议那样。在 `receiver` 中,对
|
||||
|
||||
```c
|
||||
int qid = msgget(key, 0666 | IPC_CREAT);
|
||||
```
|
||||
|
||||
的调用可能因为带有 **IPC_CREAT** 标志而具有误导性,但是这个标志的真实意义是 _如果需要就创建,否则直接获取_。_sender_ 程序调用 **msgsnd** 来发送消息,而 _receiver_ 调用 **msgrcv** 来接收它们。在这个例子中,_sender_ 以 1-1-2-2-3-3 的次序发送消息,但 _receiver_ 接收它们的次序为 3-1-2-1-3-2,这显示消息队列没有被严格的 FIFO 行为所拘泥:
|
||||
的调用可能因为带有 `IPC_CREAT` 标志而具有误导性,但是这个标志的真实意义是*如果需要就创建,否则直接获取*。`sender` 程序调用 `msgsnd` 来发送消息,而 `receiver` 调用 `msgrcv` 来接收它们。在这个例子中,`sender` 以 1-1-2-2-3-3 的次序发送消息,但 `receiver` 接收它们的次序为 3-1-2-1-3-2,这显示消息队列没有被严格的 FIFO 行为所拘泥:
|
||||
|
||||
```shell
|
||||
% ./sender
|
||||
@ -537,7 +542,7 @@ msg6 received as type 3
|
||||
msg4 received as type 2
|
||||
```
|
||||
|
||||
上面的输出显示 _sender_ 和 _receiver_ 可以在同一个终端中启动。输出也显示消息队列是持久的,即便在 _sender_ 进程在完成创建队列,向队列写数据,然后离开的整个过程后,队列仍然存在。只有在 _receiver_ 进程显式地调用 **msgctl** 来移除该队列,这个队列才会消失:
|
||||
上面的输出显示 `sender` 和 `receiver` 可以在同一个终端中启动。输出也显示消息队列是持久的,即便 `sender` 进程在完成创建队列、向队列写数据、然后退出的整个过程后,该队列仍然存在。只有在 `receiver` 进程显式地调用 `msgctl` 来移除该队列,这个队列才会消失:
|
||||
|
||||
```c
|
||||
if (msgctl(qid, IPC_RMID, NULL) < 0) /* remove queue */
|
||||
@ -545,7 +550,7 @@ if (msgctl(qid, IPC_RMID, NULL) < 0) /* remove queue */
|
||||
|
||||
### 总结
|
||||
|
||||
管道和消息队列的 API 在根本上来说都是单向的:一个进程写,然后另一个进程读。当然还存在双向命名管道的实现,但我认为这个 IPC 机制在它最为简单的时候反而是最佳的。正如前面提到的那样,消息队列已经不大受欢迎了,尽管没有找到什么特别好的原因来解释这个现象。而队列仍然是 IPC 工具箱中的另一个工具。这个快速的 IPC 工具箱之旅将以第 3 部分-通过套接字和信号来示例 IPC -来终结。
|
||||
管道和消息队列的 API 在根本上来说都是单向的:一个进程写,然后另一个进程读。当然还存在双向命名管道的实现,但我认为这个 IPC 机制在它最为简单的时候反而是最佳的。正如前面提到的那样,消息队列已经不大受欢迎了,尽管没有找到什么特别好的原因来解释这个现象;而队列仍然是 IPC 工具箱中的一个工具。这个快速的 IPC 工具箱之旅将以第 3 部分(通过套接字和信号来示例 IPC)来终结。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -554,7 +559,7 @@ via: https://opensource.com/article/19/4/interprocess-communication-linux-channe
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[FSSlc](https://github.com/FSSlc)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
@ -562,7 +567,7 @@ via: https://opensource.com/article/19/4/interprocess-communication-linux-channe
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/talk_chat_communication_team.png?itok=CYfZ_gE7 (Chat bubbles)
|
||||
[2]: https://en.wikipedia.org/wiki/Inter-process_communication
|
||||
[3]: https://opensource.com/article/19/4/interprocess-communication-ipc-linux-part-1
|
||||
[3]: https://linux.cn/article-10826-1.html
|
||||
[4]: http://man7.org/linux/man-pages/man2/mq_open.2.html
|
||||
[5]: http://man7.org/linux/man-pages/man2/mq_open.2.html#ATTRIBUTES
|
||||
[6]: http://www.opengroup.org/onlinepubs/009695399/functions/perror.html
|
@ -1,3 +1,5 @@
|
||||
Translating by Cycoe
|
||||
Cycoe 翻译中
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (FSSlc)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,117 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (MjSeven)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Getting started with social media sentiment analysis in Python)
|
||||
[#]: via: (https://opensource.com/article/19/4/social-media-sentiment-analysis-python)
|
||||
[#]: author: (Michael McCune https://opensource.com/users/elmiko/users/jschlessman)
|
||||
|
||||
Getting started with social media sentiment analysis in Python
|
||||
======
|
||||
Learn the basics of natural language processing and explore two useful
|
||||
Python packages.
|
||||
![Raspberry Pi and Python][1]
|
||||
|
||||
Natural language processing (NLP) is a type of machine learning that addresses the correlation between spoken/written languages and computer-aided analysis of those languages. We experience numerous innovations from NLP in our daily lives, from writing assistance and suggestions to real-time speech translation and interpretation.
|
||||
|
||||
This article examines one specific area of NLP: sentiment analysis, with an emphasis on determining the positive, negative, or neutral nature of the input language. This part will explain the background behind NLP and sentiment analysis and explore two open source Python packages. [Part 2][2] will demonstrate how to begin building your own scalable sentiment analysis services.
|
||||
|
||||
When learning sentiment analysis, it is helpful to have an understanding of NLP in general. This article won't dig into the mathematical guts, rather our goal is to clarify key concepts in NLP that are crucial to incorporating these methods into your solutions in practical ways.
|
||||
|
||||
### Natural language and text data
|
||||
|
||||
A reasonable place to begin is defining: "What is natural language?" It is the means by which we, as humans, communicate with one another. The primary modalities for communication are verbal and text. We can take this a step further and focus solely on text communication; after all, living in an age of pervasive Siri, Alexa, etc., we know speech is a group of computations away from text.
|
||||
|
||||
### Data landscape and challenges
|
||||
|
||||
Limiting ourselves to textual data, what can we say about language and text? First, language, particularly English, is fraught with exceptions to rules, plurality of meanings, and contextual differences that can confuse even a human interpreter, let alone a computational one. In elementary school, we learn articles of speech and punctuation, and from speaking our native language, we acquire intuition about which words have less significance when searching for meaning. Examples of the latter would be articles of speech such as "a," "the," and "or," which in NLP are referred to as _stop words_ , since traditionally an NLP algorithm's search for meaning stops when reaching one of these words in a sequence.
|
||||
|
||||
Since our goal is to automate the classification of text as belonging to a sentiment class, we need a way to work with text data in a computational fashion. Therefore, we must consider how to represent text data to a machine. As we know, the rules for utilizing and interpreting language are complicated, and the size and structure of input text can vary greatly. We'll need to transform the text data into numeric data, the form of choice for machines and math. This transformation falls under the area of _feature extraction_.
|
||||
|
||||
Upon extracting numeric representations of input text data, one refinement might be, given an input body of text, to determine a set of quantitative statistics for the articles of speech listed above and perhaps classify documents based on them. For example, a glut of adverbs might make a copywriter bristle, or excessive use of stop words might be helpful in identifying term papers with content padding. Admittedly, this may not have much bearing on our goal of sentiment analysis.
|
||||
|
||||
### Bag of words
|
||||
|
||||
When you assess a text statement as positive or negative, what are some contextual clues you use to assess its polarity (i.e., whether the text has positive, negative, or neutral sentiment)? One way is connotative adjectives: something called "disgusting" is viewed as negative, but if the same thing were called "beautiful," you would judge it as positive. Colloquialisms, by definition, give a sense of familiarity and often positivity, whereas curse words could be a sign of hostility. Text data can also include emojis, which carry inherent sentiments.
|
||||
|
||||
Understanding the polarity influence of individual words provides a basis for the [_bag-of-words_][3] (BoW) model of text. It considers a set of words or vocabulary and extracts measures about the presence of those words in the input text. The vocabulary is formed by considering text where the polarity is known, referred to as _labeled training data_. Features are extracted from this set of labeled data, then the relationships between the features are analyzed and labels are associated with the data.
|
||||
|
||||
The name "bag of words" illustrates what it utilizes: namely, individual words without consideration of spatial locality or context. A vocabulary typically is built from all words appearing in the training set, which tends to be pruned afterward. Stop words, if not cleaned prior to training, are removed due to their high frequency and low contextual utility. Rarely used words can also be removed, given the lack of information they provide for general input cases.
|
||||
|
||||
It is important to note, however, that you can (and should) go further and consider the appearance of words beyond their use in an individual instance of training data, or what is called [_term frequency_][4] (TF). You should also consider the counts of a word through all instances of input data; typically the infrequency of words among all documents is notable, which is called the [_inverse document frequency_][5] (IDF). These metrics are bound to be mentioned in other articles and software packages on this subject, so having an awareness of them can only help.
|
||||
|
||||
BoW is useful in a number of document classification applications; however, in the case of sentiment analysis, things can be gamed when the lack of contextual awareness is leveraged. Consider the following sentences:
|
||||
|
||||
* We are not enjoying this war.
|
||||
* I loathe rainy days, good thing today is sunny.
|
||||
* This is not a matter of life and death.
|
||||
|
||||
|
||||
|
||||
The sentiment of these phrases is questionable for human interpreters, and by strictly focusing on instances of individual vocabulary words, it's difficult for a machine interpreter as well.
|
||||
|
||||
Groupings of words, called _n-grams_ , can also be considered in NLP. A bigram considers groups of two adjacent words instead of (or in addition to) the single BoW. This should alleviate situations such as "not enjoying" above, but it will remain open to gaming due to its loss of contextual awareness. Furthermore, in the second sentence above, the sentiment context of the second half of the sentence could be perceived as negating the first half. Thus, spatial locality of contextual clues also can be lost in this approach. Complicating matters from a pragmatic perspective is the sparsity of features extracted from a given input text. For a thorough and large vocabulary, a count is maintained for each word, which can be considered an integer vector. Most documents will have a large number of zero counts in their vectors, which adds unnecessary space and time complexity to operations. While a number of clever approaches have been proposed for reducing this complexity, it remains an issue.
|
||||
|
||||
### Word embeddings
|
||||
|
||||
Word embeddings are a distributed representation that allows words with a similar meaning to have a similar representation. This is based on using a real-valued vector to represent words in connection with the company they keep, as it were. The focus is on the manner that words are used, as opposed to simply their existence. In addition, a huge pragmatic benefit of word embeddings is their focus on dense vectors; by moving away from a word-counting model with commensurate amounts of zero-valued vector elements, word embeddings provide a more efficient computational paradigm with respect to both time and storage.
|
||||
|
||||
Following are two prominent word embedding approaches.
|
||||
|
||||
#### Word2vec
|
||||
|
||||
The first of these word embeddings, [Word2vec][6], was developed at Google. You'll probably see this embedding method mentioned as you go deeper in your study of NLP and sentiment analysis. It utilizes either a _continuous bag of words_ (CBOW) or a _continuous skip-gram_ model. In CBOW, a word's context is learned during training based on the words surrounding it. Continuous skip-gram learns the words that tend to surround a given word. Although this is more than what you'll probably need to tackle, if you're ever faced with having to generate your own word embeddings, the author of Word2vec advocates the CBOW method for speed and assessment of frequent words, while the skip-gram approach is better suited for embeddings where rare words are more important.
|
||||
|
||||
#### GloVe
|
||||
|
||||
The second word embedding, [_Global Vectors for Word Representation_][7] (GloVe), was developed at Stanford. It's an extension to the Word2vec method that attempts to combine the information gained through classical global text statistical feature extraction with the local contextual information determined by Word2vec. In practice, GloVe has outperformed Word2vec for some applications, while falling short of Word2vec's performance in others. Ultimately, the targeted dataset for your word embedding will dictate which method is optimal; as such, it's good to know the existence and high-level mechanics of each, as you'll likely come across them.
|
||||
|
||||
#### Creating and using word embeddings
|
||||
|
||||
Finally, it's useful to know how to obtain word embeddings; in part 2, you'll see that we are standing on the shoulders of giants, as it were, by leveraging the substantial work of others in the community. This is one method of acquiring a word embedding: namely, using an existing trained and proven model. Indeed, myriad models exist for English and other languages, and it's possible that one does what your application needs out of the box!
|
||||
|
||||
If not, the opposite end of the spectrum in terms of development effort is training your own standalone model without consideration of your application. In essence, you would acquire substantial amounts of labeled training data and likely use one of the approaches above to train a model. Even then, you are still only at the point of acquiring understanding of your input-text data; you then need to develop a model specific for your application (e.g., analyzing sentiment valence in software version-control messages) which, in turn, requires its own time and effort.
|
||||
|
||||
You also could train a word embedding on data specific to your application; while this could reduce time and effort, the word embedding would be application-specific, which would reduce reusability.
|
||||
|
||||
### Available tooling options
|
||||
|
||||
You may wonder how you'll ever get to a point of having a solution for your problem, given the intensive time and computing power needed. Indeed, the complexities of developing solid models can be daunting; however, there is good news: there are already many proven models, tools, and software libraries available that may provide much of what you need. We will focus on [Python][8], which conveniently has a plethora of tooling in place for these applications.
|
||||
|
||||
#### SpaCy
|
||||
|
||||
[SpaCy][9] provides a number of language models for parsing input text data and extracting features. It is highly optimized and touted as the fastest library of its kind. Best of all, it's open source! SpaCy performs tokenization, parts-of-speech classification, and dependency annotation. It contains word embedding models for performing this and other feature extraction operations for over 46 languages. You will see how it can be used for text analysis and feature extraction in the second article in this series.
|
||||
|
||||
#### vaderSentiment
|
||||
|
||||
The [vaderSentiment][10] package provides a measure of positive, negative, and neutral sentiment. As the [original paper][11]'s title ("VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text") indicates, the models were developed and tuned specifically for social media text data. VADER was trained on a thorough set of human-labeled data, which included common emoticons, UTF-8 encoded emojis, and colloquial terms and abbreviations (e.g., meh, lol, sux).
|
||||
|
||||
For given input text data, vaderSentiment returns a 3-tuple of polarity score percentages. It also provides a single scoring measure, referred to as _vaderSentiment's compound metric_. This is a real-valued measurement within the range **[-1, 1]** wherein sentiment is considered positive for values greater than **0.05** , negative for values less than **-0.05** , and neutral otherwise.
|
||||
|
||||
In [part 2][2], you will learn how to use these tools to add sentiment analysis capabilities to your designs.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/19/4/social-media-sentiment-analysis-python
|
||||
|
||||
作者:[Michael McCune ][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/elmiko/users/jschlessman
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/getting_started_with_python.png?itok=MFEKm3gl (Raspberry Pi and Python)
|
||||
[2]: https://opensource.com/article/19/4/social-media-sentiment-analysis-python-part-2
|
||||
[3]: https://en.wikipedia.org/wiki/Bag-of-words_model
|
||||
[4]: https://en.wikipedia.org/wiki/Tf%E2%80%93idf#Term_frequency
|
||||
[5]: https://en.wikipedia.org/wiki/Tf%E2%80%93idf#Inverse_document_frequency
|
||||
[6]: https://en.wikipedia.org/wiki/Word2vec
|
||||
[7]: https://en.wikipedia.org/wiki/GloVe_(machine_learning)
|
||||
[8]: https://www.python.org/
|
||||
[9]: https://pypi.org/project/spacy/
|
||||
[10]: https://pypi.org/project/vaderSentiment/
|
||||
[11]: http://comp.social.gatech.edu/papers/icwsm14.vader.hutto.pdf
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (bodhix)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -0,0 +1,115 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (MjSeven)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Getting started with social media sentiment analysis in Python)
|
||||
[#]: via: (https://opensource.com/article/19/4/social-media-sentiment-analysis-python)
|
||||
[#]: author: (Michael McCune https://opensource.com/users/elmiko/users/jschlessman)
|
||||
|
||||
使用 Python 进行社交媒体情感分析入门
|
||||
======
|
||||
学习自然语言处理的基础知识并探索两个有用的 Python 包。
|
||||
![Raspberry Pi and Python][1]
|
||||
|
||||
自然语言处理(NLP)是机器学习的一种,它解决了口语或书面语言和计算机辅助分析这些语言之间的相关性。日常生活中我们经历了无数的 NLP 创新,从写作帮助和建议到实时语音翻译,还有口译。
|
||||
|
||||
本文研究了 NLP 的一个特定领域:情感分析。重点是确定输入语言的积极、消极或中性性质。本部分将解释 NLP 和情感分析的背景,并探讨两个开源的 Python 包。[第 2 部分][2]将演示如何开始构建自己的可扩展情感分析服务。
|
||||
|
||||
在学习情感分析时,对 NLP 有一个大体了解是有帮助的。本文不会深入研究数学本质。相反,我们的目标是阐明 NLP 中的关键概念,这些概念对于将这些方法实际结合到你的解决方案中至关重要。
|
||||
|
||||
### 自然语言和文本数据
|
||||
|
||||
合理的起点是从定义开始:“什么是自然语言?”它是我们人类相互交流的方式,沟通的主要方式是口语和文字。我们可以更进一步,只关注文本交流。毕竟,生活在 Siri, Alexa 等无处不在的时代,我们知道语音是一组与文本无关的计算。
|
||||
|
||||
### 数据前景和挑战
|
||||
|
||||
我们只考虑使用文本数据,我们可以对语言和文本做什么呢?首先是语言,特别是英语,除了规则还有很多例外,含义的多样性和语境差异,这些都可能使人类口译员感到困惑,更不用说计算机翻译了。在小学,我们学习文章和标点符号,通过讲母语,我们获得了寻找直觉上表示唯一意义的词的能力。比如,出现诸如 "a"、"the" 和 "or" 之类的文章,它们在 NLP 中被称为 _停用词_,因为传统上 NLP 算法是在一个序列中找到这些词时意味着搜索停止。
|
||||
|
||||
由于我们的目标是自动将文本分类为情感类,因此我们需要一种以计算方式处理文本数据的方法。因此,我们必须考虑如何向机器表示文本数据。众所周知,利用和解释语言的规则很复杂,输入文本的大小和结构可能会有很大差异。我们需要将文本数据转换为数字数据,这是机器和数学的首选方式。这种转变属于 _特征提取_ 的范畴。
|
||||
|
||||
在提取输入文本数据的数字表示形式后,一个改进可能是:给定一个文本输入体,为上面列出的文章确定一组向量统计数据,并根据这些数据对文档进行分类。例如,过多的副词可能会使撰稿人感到愤怒,或者过度使用停用词可能有助于识别带有内容填充的学期论文。诚然,这可能与我们情感分析的目标没有太大关系。
|
||||
|
||||
### 词袋
|
||||
|
||||
当你评估一个文本陈述是积极还是消极的时候,你使用哪些上下文来评估它的极性?(例如,文本中是否具有积极的、消极的或中性的情感)一种方式是隐含形容词:被称为 "disgusting" 的东西被认为是消极的,但如果同样的东西被称为 "beautiful",你会认为它是积极的。从定义上讲,俗语给人一种熟悉感,通常是积极的,而脏话可能是敌意的表现。文本数据也可以包括表情符号,它带有固定的情感。
|
||||
|
||||
理解单个单词的极性影响为文本的[_词袋_][3](BoW) 模型提供了基础。它考虑一组单词或词汇表,并提取关于这些单词在输入文本中是否存在的度量。词汇表是通过考虑极性已知的文本形成的,称为 _标记的训练数据_。从这组标记数据中提取特征,然后分析特征之间的关系,并将标签与数据关联起来。
|
||||
|
||||
“词袋”这个名称说明了它的用途:即不考虑空间位置或上下文的的单个词。词汇表通常是由训练集中出现的所有单词构建的,在训练结束后被删除。如果在训练之前没有清理停用词,那么停用词会因为其高频率和低语境而被移除。很少使用的单词也可以删除,因为一般情况下它们提供了缺失的信息。
|
||||
|
||||
但是,重要的是要注意,你可以(并且应该)进一步考虑单词在单独的训练数据实例中使用之外的外观,称为[_词频_][4] (TF)。你还应该考虑输入数据的所有实例中的单词计数,通常,所有文档中的单词频率显著,这被称为[_逆文本频率指数_][5](IDF)。这些指标一定会在本主题的其他文章和软件包中提及,因此了解它们会有所帮助。
|
||||
|
||||
词袋在许多文档分类应用程序中很有用。然而,在情感分析中,当缺乏情境意识的问题被利用时,事情就可以解决。考虑以下句子:
|
||||
|
||||
* 我们不喜欢这场战争。
|
||||
* 我讨厌下雨天,好事是今天是晴天。
|
||||
* 这不是生死攸关的问题。
|
||||
|
||||
|
||||
这些短语的情感对于人类口译员来说是有难度的,而且由于严格关注单个词汇的实例,对于机器翻译来说也是困难的。
|
||||
|
||||
在 NLP 中也可以考虑称为 _n-grams_ 的单词分组。一个二元组考虑两个相邻单词组成的组而不是(或除了)单个词袋。这应该可以缓解诸如上述“不喜欢”之类的情况,但由于缺乏语境意思,它仍然是个问题。此外,在上面的第二句中,下半句的情感语境可以被理解为否定前半部分。因此,这种方法中也会丢失上下文线索的空间局部性。从实用角度来看,使问题复杂化的是从给定输入文本中提取的特征的稀疏性。对于一个完整的大型词汇表,每个单词都有一个计数,可以将其视为一个整数向量。大多数文档的向量中都有大量的零计数,这给操作增加了不必要的空间和时间复杂度。虽然已经提出了许多用于降低这种复杂性的简便方法,但它仍然是一个问题。
|
||||
|
||||
### 词嵌入
|
||||
|
||||
词嵌入是一种分布式表示,它允许具有相似含义的单词具有相似的表示。这是基于使用实值向量来与它们周围相关联。重点在于使用单词的方式,而不仅仅是它们的存在。此外,词嵌入的一个巨大语用优势是它们对密集向量的关注。通过摆脱具有相应数量的零值向量元素的单词计数模型,词嵌入在时间和存储方面提供了一个更有效的计算范例。
|
||||
|
||||
以下是两个优秀的词嵌入方法。
|
||||
|
||||
#### Word2vec
|
||||
|
||||
第一个是 [Word2vec][6],它是由 Google 开发的。随着你对 NLP 和情绪分析研究的深入,你可能会看到这种嵌入方法。它要么使用一个 _连续的词袋_(CBOW),要么使用一个 _连续的 skip-gram_ 模型。在 CBOW 中,一个单词的上下文是在训练中根据围绕它的单词来学习的。连续的 skip-gram 学习倾向于围绕给定的单词学习单词。虽然这可能超出了你需要解决的问题,但是如果你曾经面对必须生成自己的词嵌入情况,那么 Word2vec 的作者提倡使用 CBOW 方法来提高速度并评估频繁的单词,而 skip-gram 方法更适合嵌入稀有单词更重要的嵌入。
|
||||
|
||||
#### GloVe
|
||||
|
||||
第二个是 [ _Global Vectors for Word Representation_][7(GloVe),它是斯坦福大学开发的。它是 Word2vec 方法的扩展,它试图将通过经典的全局文本统计特征提取获得的信息与 Word2vec 确定的本地上下文信息相结合。实际上,在一些应用程序中,GloVe 性能优于 Word2vec,而在另一些应用程序中则不如 Word2vec。最终,用于词嵌入的目标数据集将决定哪种方法最优。因此,最好了解它们的存在性和高级机制,因为你很可能会遇到它们。
|
||||
|
||||
#### 创建和使用词嵌入
|
||||
|
||||
最后,知道如何获得词嵌入是有用的。在第 2 部分中,你将看到我们通过利用社区中其他人的实质性工作,可以说我们是站在了巨人的肩膀上。这是获取词嵌入的一种方法:即使用现有的经过训练和验证的模型。实际上,有无数的模型适用于英语和其他语言,一定会有一种模型可以满足你的应用程序,让你开箱即用!
|
||||
|
||||
如果没有的话,就开发工作而言,另一个极端是培训你自己的独立模型,而不考虑你的应用程序。实质上,你将获得大量标记的训练数据,并可能使用上述方法之一来训练模型。即使这样,你仍然只是在获取对输入文本数据的理解。然后,你需要为你应用程序开发一个特定的模型(例如,分析软件版本控制消息中的情感价值),这反过来又需要自己的时间和精力。
|
||||
|
||||
你还可以为你的应用程序数据训练一个词嵌入,虽然这可以减少时间和精力,但这个词嵌入将是特定于应用程序的,这将会降低它的可重用性。
|
||||
|
||||
### 可用的工具选项
|
||||
|
||||
考虑到所需的大量时间和计算能力,你可能想知道如何才能找到解决问题的方法。的确,开发可靠模型的复杂性可能令人望而生畏。但是,有一个好消息:已经有许多经过验证的模型、工具和软件库可以为我们提供所需的大部分内容。我们将重点关注 [Python][8],因为它为这些应用程序提供了大量方便的工具。
|
||||
|
||||
#### SpaCy
|
||||
|
||||
[SpaCy][9] 提供了许多用于解析输入文本数据和提取特征的语言模型。它经过了高度优化,并被誉为同类中最快的库。最棒的是,它是开源的!SpaCy 会执行标识化、词性分类和依赖项注释。它包含了用于执行此功能的词嵌入模型,还有用于为超过 46 种语言的其他特征提取操作。在本系列的第二篇文章中,你将看到它如何用于文本分析和特征提取。
|
||||
|
||||
#### vaderSentiment
|
||||
|
||||
[vaderSentiment][10] 包提供了积极、消极和中性情绪的衡量标准。正如 [original paper][11] 的标题(“VADER:一个基于规则的社交媒体文本情感分析模型”)所示,这些模型是专门为社交媒体文本数据开发和调整的。VADER 接受了一组完整的人类标记数据的训练,包括常见的表情符号、UTF-8 编码的表情符号以及口语术语和缩写(例如 meh、lol、sux)。
|
||||
|
||||
对于给定的输入文本数据,vaderSentiment 返回一个极性分数百分比的三元组。它还提供了一个单个的评分标准,称为 _vaderSentiment 复合指标_。这是一个在 **[-1, 1]** 范围内的实值,其中对于分值大于 **0.05** 的情绪被认为是积极的,对于分值小于 **-0.05** 的被认为是消极的,否则为中性。
|
||||
|
||||
在[第 2 部分][2]中,你将学习如何使用这些工具为你的设计添加情感分析功能。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/19/4/social-media-sentiment-analysis-python
|
||||
|
||||
作者:[Michael McCune ][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[MjSeven](https://github.com/MjSeven)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/elmiko/users/jschlessman
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/getting_started_with_python.png?itok=MFEKm3gl (Raspberry Pi and Python)
|
||||
[2]: https://opensource.com/article/19/4/social-media-sentiment-analysis-python-part-2
|
||||
[3]: https://en.wikipedia.org/wiki/Bag-of-words_model
|
||||
[4]: https://en.wikipedia.org/wiki/Tf%E2%80%93idf#Term_frequency
|
||||
[5]: https://en.wikipedia.org/wiki/Tf%E2%80%93idf#Inverse_document_frequency
|
||||
[6]: https://en.wikipedia.org/wiki/Word2vec
|
||||
[7]: https://en.wikipedia.org/wiki/GloVe_(machine_learning)
|
||||
[8]: https://www.python.org/
|
||||
[9]: https://pypi.org/project/spacy/
|
||||
[10]: https://pypi.org/project/vaderSentiment/
|
||||
[11]: http://comp.social.gatech.edu/papers/icwsm14.vader.hutto.pdf
|
Loading…
Reference in New Issue
Block a user