作者: 默默
解决Raspberry PI Zero W中Lua使用lua-periphery与Python中设置的GPIO端口不一致的问题
在树莓派实时系统下脚本语言的选择(应当使用Lua而不是Python)中,我们没有使用rpi-gpio,而是使用了lua-periphery来解决Lua
语言下操作树莓派GPIO
的问题。
当时选择lua-periphery的原因在于rpi-gpio在Raspberry PI Zero W
中使用的时候会崩溃。这个原因是在于cpuinfo.c这个文件中缺少对于BCM2835
这颗新的CPU
的判断,只判断了BCM2708
(估计写这个库的时候,只有BCM2708
)。导致RPi_GPIO_Lua_module.c在初始化GPIO
的时候抛出了异常。这个已经有人提交了代码合并请求,估计很快会修复。
但是在lua-periphery中,没有对于GPIO
进行重新映射,导致跟rpi-gpio以及树莓派自带的Python
库在设置GPIO
的时候,端口号对应不一致。比如,在Python
中设置GPIO 22
,执行命令观察ls /sys/class/gpio/
,会发现系统创建的是GPIO 25
这个对应关系就是通过查表获取的。如下图:
继续阅读解决Raspberry PI Zero W中Lua使用lua-periphery与Python中设置的GPIO端口不一致的问题
解决Raspberry Pi安装libgtk2.0-dev出错的问题
最近在树莓派上需要安装libgtk2.0-dev
,执行如下命令:
1 |
$ sudo apt-get install libgtk2.0-dev |
出错信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Reading package lists... Done Building dependency tree Reading state information... Done Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstable distribution that some required packages have not yet been created or been moved out of Incoming. The following information may help to resolve the situation: The following packages have unmet dependencies: libgtk2.0-dev : Depends: libpango1.0-dev (>= 1.20) but it is not going to be installed Depends: libcairo2-dev (>= 1.6.4-6.1) but it is not going to be installed E: Unable to correct problems, you have held broken packages. |
根据出错信息,明显是软件源中出现了安装包缺失的问题。这个现象是不应该出现的。网上查询了不少地方,最终找到解决方法:
1 |
$ sudo vim /etc/apt/sources.list.d/raspi.list |
可以看到如下内容:
1 2 3 |
#deb http://archive.raspberrypi.org/debian/ stretch main ui staging # Uncomment line below then 'apt-get update' to enable 'apt-get source' #deb-src http://archive.raspberrypi.org/debian/ stretch main ui |
默认里面的内容都是被注释掉的,我们需要做的就是把这个源打开即可。
也可以直接执行如下命令来开启:
1 2 3 4 |
#先备份配置文件 $ sudo cp /etc/apt/sources.list.d/raspi.list /etc/apt/sources.list.d/raspi.list.bak $ sudo sed -i "s/^\#deb/deb/g" /etc/apt/sources.list.d/raspi.list |
参考链接
修正ubuntu 18.04上执行"sudo apt upgrade"报告"libc6-dev-armhf-cross"升级出错
最近自己电脑上的ubuntu 18.04
在更新软件的时候报告如下错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$ sudo apt upgrade 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 正在计算更新... 完成 下列软件包将被升级: libc6-armhf-cross libc6-dev-armhf-cross 升级了 2 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 0 个软件包未被升级。 需要下载 0 B/2,904 kB 的归档。 解压缩后会消耗 0 B 的额外空间。 您希望继续执行吗? [Y/n] (正在读取数据库 ... 系统当前共安装有 239272 个文件和目录。) 正准备解包 .../libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb ... 正在将 libc6-dev-armhf-cross (2.27-3ubuntu1cross1.1) 解包到 (2.27-3ubuntu1cross1) 上 ... dpkg: 处理归档 /var/cache/apt/archives/libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb (--unpack)时出错: 无法打开 /usr/arm-linux-gnueabihf/lib/Mcrt1.o.dpkg-new : 没有那个文件或目录 错误信息显示本地系统有一些问题,因此没有写入 apport 报告 正准备解包 .../libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb ... 正在将 libc6-armhf-cross (2.27-3ubuntu1cross1.1) 解包到 (2.27-3ubuntu1cross1) 上 ... dpkg: 处理归档 /var/cache/apt/archives/libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb (--unpack)时出错: 无法打开 /usr/arm-linux-gnueabihf/lib/ld-2.27.so.dpkg-new : 没有那个文件或目录 错误信息显示本地系统有一些问题,因此没有写入 apport 报告 在处理时有错误发生: /var/cache/apt/archives/libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb /var/cache/apt/archives/libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb E: Sub-process /usr/bin/dpkg returned an error code (1) |
如果系统语言为英文,则错误信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ sudo apt upgrade Reading package lists... Done Building dependency tree Reading state information... Done Calculating upgrade... Done The following packages will be upgraded: libc6-armhf-cross libc6-dev-armhf-cross 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. Need to get 0 B/2,904 kB of archives. After this operation, 0 B of additional disk space will be used. Do you want to continue? [Y/n] (Reading database ... 239272 files and directories currently installed.) Preparing to unpack .../libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb ... Unpacking libc6-dev-armhf-cross (2.27-3ubuntu1cross1.1) over (2.27-3ubuntu1cross1) ... dpkg: error processing archive /var/cache/apt/archives/libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb (--unpack): unable to open '/usr/arm-linux-gnueabihf/lib/Mcrt1.o.dpkg-new': No such file or directory No apport report written because the error message indicates an issue on the local system Preparing to unpack .../libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb ... Unpacking libc6-armhf-cross (2.27-3ubuntu1cross1.1) over (2.27-3ubuntu1cross1) ... dpkg: error processing archive /var/cache/apt/archives/libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb (--unpack): unable to open '/usr/arm-linux-gnueabihf/lib/ld-2.27.so.dpkg-new': No such file or directory No apport report written because the error message indicates an issue on the local system Errors were encountered while processing: /var/cache/apt/archives/libc6-dev-armhf-cross_2.27-3ubuntu1cross1.1_all.deb /var/cache/apt/archives/libc6-armhf-cross_2.27-3ubuntu1cross1.1_all.deb E: Sub-process /usr/bin/dpkg returned an error code (1) |
解决方法为卸载后重新安装,而不使用升级安装:
1 2 3 |
$ sudo apt remove libc6-dev-armhf-cross $ sudo apt install libc6-dev-armhf-cross |
参考链接
How do I fix an error with libc6-dev-armhf-cross in Ubuntu 18.04 when trying to apt upgrade?
Lua的集成开发环境ZeroBrane Studio
Lua
进行脚本的开发,可是官方并没有提供很好的集成开发环境。体验了很多,发现
ZeroBrane Studio
这个开源软件还是非常好用的,并且已经能正常支持Linux
,Windows
,MacOS
这三个主流平台。
建议去官方网站下载最新的版本,但是鉴于国内网络访问不是非常稳定,可以从本站下载一份目前最新的版本。
下面的版本根据自身操作系统来选择其中一个进行下载
ZeroBraneStudioEduPack-1.70-linux.sh
ZeroBraneStudioEduPack-1.70-macos.dmg
ZeroBraneStudioEduPack-1.70-win32.exe
树莓派下的编译(目前编译出的暂时无法使用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
$ git clone https://github.com/pkulchenko/ZeroBraneStudio.git #如果代码下载存在问题,可以本站下载一份拷贝 # wget https://www.mobibrw.com/wp-content/uploads/2018/09/ZeroBraneStudio.tar.xz # tar xvf ZeroBraneStudio.tar.xz $ cd ZeroBraneStudio $ cd build $ bash build-linux-prep-deb.sh # gthread $ sudo apt-get install libglib2.0-dev # gtk+ $ sudo apt-get install libgtk2.0-dev $ sudo apt-get install libgtk-3-dev #opengl $ sudo apt-get install freeglut3-dev # ssl for luasec $ sudo apt-get install libssl-dev $ sudo ln -s /usr/lib/arm-linux-gnueabihf/libssl.so /usr/lib/libssl.so # lua $ sudo apt-get install lua5.1 liblua5.1-dev #webview可选 #sudo apt-get install libwebkitgtk-dev # luasec最新版本,早期版本编译不通过 $ sed -i "s/^LUASEC_BASENAME=\"luasec-0.6\"/LUASEC_BASENAME=\"luasec-0.7\"/g" build-linux.sh #此处wxWidgets的克隆比较慢,因此可以本站下载一份拷贝,手工修改脚本的下载 # wget https://www.mobibrw.com/wp-content/uploads/2018/09/wxWidgets.tar.xz # sed -i "s/^[ \t]*git clone \"\$WXWIDGETS_URL\".*/ wget https:\/\/www.mobibrw.com\/wp-content\/uploads\/2018\/09\/wxWidgets.tar.xz\n rm -rf wxWidgets\n tar xvf wxWidgets.tar.xz/g" build-linux.sh # wget # sed -i "s/^[ \t]*git clone \"\$WXLUA_URL\" \"\$WXLUA_BASENAME\".*/ wget https:\/\/www.mobibrw.com\/wp-content\/uploads\/2018\/09\/wxlua.tar.xz\n rm -rf wxlua\n tar xvf wxlua.tar.xz/g" build-linux.sh # sed -i "s/^LEXLPEG_URL=\"https:\/\/foicica.com\/scintillua\/download/LEXLPEG_URL=\"https:\/\/www.mobibrw.com\/wp-content\/uploads\/2018\/09/g" build-linux.sh # for debug "bash build-linux.sh debug all" $ bash build-linux.sh all #编译两次,解决第一次的问题,第一次有些目录创建存在问题 $ bash build-linux.sh all #还是需要安装一些依赖,上面编译的库并没有完整完成依赖设置 $ sudo apt-get install luarocks $ sudo luarocks install luasocket $ cp deps/lib/libwx.so deps/lib/wx.so #动态链接库应当设置LUA_CPATH而不是LUA_PATH $ export LUA_CPATH="`pwd`/deps/lib/?.so;`pwd`/deps/lib/lua/51/?.so" $ export LD_LIBRARY_PATH=`pwd`/deps/lib #去掉两个检测,这两检测总是会失败,原因不好排查 $ sed -i "s/check_lua_module(wx TRUE)/#check_lua_module(wx TRUE)/g" CMakeLists.txt $ sed -i "s/check_lua_module(socket TRUE)/#check_lua_module(socket TRUE)/g" CMakeLists.txt $ cmake -DCMAKE_SYSROOT=`pwd`/deps/ -DCMAKE_FIND_ROOT_PATH=`pwd`/deps/ . $ make $ sudo make install |
参考链接
Debugging Lua Code
Lightweight IDE for your Lua needs ZeroBrane Studio
解决Raspberry Pi Zero W启动后没有在HDMI口输出内容的问题
最新在使用的Raspberry Pi Zero W
V1.3
在使用目前(2018.09.26
)的系统的时候发现无法正常输出内容到屏幕上面,屏幕一直黑屏无信号。
原因在于Raspberry Pi Zero W
在启动的时候没有正确检测到屏幕信号,导致没有正常输出。
解决方法是打开启动配置文件/boot/config.txt
, 找到如下内容:
1 2 3 4 5 6 |
.................. # uncomment if hdmi display is not detected and composite is being output #hdmi_force_hotplug=1 ................. |
然后去掉注释,修改为如下:
1 2 |
# uncomment if hdmi display is not detected and composite is being output hdmi_force_hotplug=1 |
树莓派实时系统下脚本语言的选择(应当使用Lua而不是Python)
最近在使用树莓派与其他设备通过SPI
接口进行通信,使用一个GPIO
管脚触发读取数据的信号,为了简化开发,使用了Python
。
在实际运行过程中,发现当长时间运行的是,会出现中断管脚信号丢失的情况,在参考 Ubuntu 16.04 (x64)下从源代码为Raspberry Pi Zero W编译实时内核 更换为实时内核之后,短时间运行已经可以正常,但是在十几个小时之后,依然出现了中断丢失的现象。
这个现象初步评估为Python
的GC
动作时间过长导致的中断信号丢失。Python
本身并不是为实时系统设计的,因此在GC
进行垃圾回收的时候,是没有实时性的考虑的,因此在严格要求实时性的系统环境下,不是非常的合适。更何况很多的IO
操作默认都是阻塞的,更加容易导致实时性问题。
由于树莓派本身也是支持Lua
脚本的,默认安装的Lua
引擎默认是5.1.4
。Lua
本身在游戏中使用较多,而游戏本身对于实时性的要求是很高的。
尤其是Lua 5.1
开始使用最新的GC
已经能很好的解决实时性问题。
关于Lua
的GC
相关信息,参考如下的文章:
Ubuntu 16.04 (x64)下从源代码为Raspberry Pi Zero W编译实时内核
首先参考 Ubuntu 16.04 (x64)树莓派B+ (Raspberry Pi B+)源代码编译 保证能够成功编译标准内核的源代码,然后切换到实时内核分支,并执行如下编译命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
$ export PATH=$PATH:~/rpi/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin $ cd ~/rpi/rpi-linux/ $ git checkout rpi-4.14.y-rt $ git reset --hard $ KERNEL=kernel $ make clean $ make mrproper $ rm -rf .config #调整内核切换频率,增加实时性 $ sed -i '$a\CONFIG_HZ_1000=y' arch/arm/configs/bcmrpi_defconfig #Raspberry Pi Zero W的CPU是BCM2835 $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcmrpi_defconfig $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j8 $ mkdir rt_kernel $ make modules_install ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=./rt_kernel -j8 $ make dtbs_install ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_DTBS_PATH=./rt_kernel -j8 $ ./scripts/mkknlimg ./arch/arm/boot/zImage ./rt_kernel/kernel.img |
安装编译好的内核
Ubuntu
下面,SD
卡会自动挂载,默认挂载到了/media/
目录下面,如果是使用NOOBS
安装的话,系统目录是固定的,执行如下命令拷贝到目标SD
卡上面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ cd ~/rpi/rpi-linux #备份需要修改的文件 $ mv /media/`whoami`/boot/kernel.img /media/`whoami`/boot/kernel_old.img $ mv /media/`whoami`/boot/overlays /media/`whoami`/boot/overlays.old #拷贝内核 $ cp rt_kernel/kernel.img /media/`whoami`/boot/kernel.img #拷贝硬件配置 $ cp rt_kernel/bcm2835*.dtb /media/`whoami`/boot/ #拷贝overlays $ cp -r rt_kernel/overlays /media/`whoami`/boot/ #拷贝内核模块 $ sudo cp -r rt_kernel/lib/modules/* /media/`whoami`/rootfs/lib/modules/ #卸载设备 $ sudo umount -A -R -a /media/`whoami`/boot |
借助编译环境,单独编译内核模块
有时,我们需要单独编译新下载的内核驱动,这个时候,就可以使用如下的方式进行单独内核模块的编译。
下面,我们以ASIX AX88772
系列的USB
有线网卡驱动的编译为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ export PATH=$PATH:~/rpi/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin $ cd ~/rpi/rpi-linux # wget https://www.mobibrw.com/wp-content/uploads/2018/09/AX88772C_772B_772A_760_772_178_LINUX_DRIVER_v4.22.0_Source.tar.bz2 $ wget http://www.asix.com.tw/FrootAttach/driver/AX88772C_772B_772A_760_772_178_LINUX_DRIVER_v4.22.0_Source.tar.bz2 $ tar xvf AX88772C_772B_772A_760_772_178_LINUX_DRIVER_v4.22.0_Source.tar.bz2 $ cd AX88772C_772B_772A_760_772_178_LINUX_DRIVER_v4.22.0_Source $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C ../ M=`pwd` #当前目录下就会生成 asix.ko 这个内核模块,生成的内核模块拷贝到指定的目录即可正常工作 |
解决已知问题
使用上面的命令安装完成内核后,目前(2018.09.26
)遇到的问题为,当插入ASIX AX88772
系列的USB
有线网卡之后,会导致内核崩溃,启动失败。
设备信息如下:
1 |
Bus 001 Device 034: ID 0b95:7720 ASIX Electronics Corp. AX88772 |
初步怀疑是USB
设备的驱动依赖关系不正确导致内核崩溃。
目前的临时解决方法为要求设备启动时候优先加载USB
设备相关的驱动,而不是等到网卡插入的时候再去加载。
也就是在/boot/config.txt
文件尾部新增加一行dtoverlay=dwc2
。这段代码本来是为树莓派通过USB
访问网络的虚拟网卡准备的(是的,你没看错,树莓派本身可以不借助网卡直接通过USB
接口跟电脑共享方式上网,不过需要设置一堆东西,最简单的还是外接真正的USB
网卡)。我们加载这个模块,但是并不使用这个功能,造成的结果就是重新调整了模块加载顺序,规避了后续的问题。
1 |
$ sed -i '$a\\ndtoverlay=dwc2' /media/`whoami`/boot/config.txt |
上述修改后,依旧存在动态插拔网卡,设备会重启的问题,不过已经不影响正常使用。
参考链接
How to Perform Real-Time Processing on the Raspberry Pi
继续阅读How to Perform Real-Time Processing on the Raspberry Pi
基于协程的Python网络库gevent介绍
继续Python协程方面的介绍,这次要讲的是gevent,它是一个并发网络库。它的协程是基于greenlet的,并基于libev实现快速事件循环(Linux上是epoll,FreeBSD上是kqueue,Mac OS X上是select)。有了gevent,协程的使用将无比简单,你根本无须像greenlet一样显式的切换,每当一个协程阻塞时,程序将自动调度,gevent处理了所有的底层细节。让我们看个例子来感受下吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import gevent def test1(): print 12 gevent.sleep(0) print 34 def test2(): print 56 gevent.sleep(0) print 78 gevent.joinall([ gevent.spawn(test1), gevent.spawn(test2), ]) |
解释下,”gevent.spawn()”方法会创建一个新的greenlet协程对象,并运行它。”gevent.joinall()”方法会等待所有传入的greenlet协程运行结束后再退出,这个方法可以接受一个”timeout”参数来设置超时时间,单位是秒。运行上面的程序,执行顺序如下:
- 先进入协程test1,打印12
- 遇到”gevent.sleep(0)”时,test1被阻塞,自动切换到协程test2,打印56
- 之后test2被阻塞,这时test1阻塞已结束,自动切换回test1,打印34
- 当test1运行完毕返回后,此时test2阻塞已结束,再自动切换回test2,打印78
- 所有协程执行完毕,程序退出
所以,程序运行下来的输出就是:
1 2 3 4 |
12 56 34 78 |
注意,这里与上一篇greenlet中第一个例子运行的结果不一样,greenlet一个协程运行完后,必须显式切换,不然会返回其父协程。而在gevent中,一个协程运行完后,它会自动调度那些未完成的协程。
我们换一个更有意义的例子:
1 2 3 4 5 6 7 8 |
import gevent import socket urls = ['www.baidu.com', 'www.gevent.org', 'www.python.org'] jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls] gevent.joinall(jobs, timeout=5) print [job.value for job in jobs] |
我们通过协程分别获取三个网站的IP地址,由于打开远程地址会引起IO阻塞,所以gevent会自动调度不同的协程。另外,我们可以通过协程对象的”value”属性,来获取协程函数的返回值。
猴子补丁 Monkey patching
细心的朋友们在运行上面例子时会发现,其实程序运行的时间同不用协程是一样的,是三个网站打开时间的总和。可是理论上协程是非阻塞的,那运行时间应该等于最长的那个网站打开时间呀?其实这是因为Python标准库里的socket是阻塞式的,DNS解析无法并发,包括像urllib库也一样,所以这种情况下用协程完全没意义。那怎么办?
一种方法是使用gevent下的socket模块,我们可以通过”from gevent import socket”来导入。不过更常用的方法是使用猴子布丁(Monkey patching):
1 2 3 4 5 6 7 8 9 |
from gevent import monkey; monkey.patch_socket() import gevent import socket urls = ['www.baidu.com', 'www.gevent.org', 'www.python.org'] jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls] gevent.joinall(jobs, timeout=5) print [job.value for job in jobs] |
上述代码的第一行就是对socket标准库打上猴子补丁,此后socket标准库中的类和方法都会被替换成非阻塞式的,所有其他的代码都不用修改,这样协程的效率就真正体现出来了。Python中其它标准库也存在阻塞的情况,gevent提供了”monkey.patch_all()”方法将所有标准库都替换。
1 |
from gevent import monkey; monkey.patch_all() |
使用猴子补丁褒贬不一,但是官网上还是建议使用”patch_all()”,而且在程序的第一行就执行。
获取协程状态
协程状态有已启动和已停止,分别可以用协程对象的”started”属性和”ready()”方法来判断。对于已停止的协程,可以用”successful()”方法来判断其是否成功运行且没抛异常。如果协程执行完有返回值,可以通过”value”属性来获取。另外,greenlet协程运行过程中发生的异常是不会被抛出到协程外的,因此需要用协程对象的”exception”属性来获取协程中的异常。下面的例子很好的演示了各种方法和属性的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#coding:utf8 import gevent def win(): return 'You win!' def fail(): raise Exception('You failed!') winner = gevent.spawn(win) loser = gevent.spawn(fail) print winner.started # True print loser.started # True # 在Greenlet中发生的异常,不会被抛到Greenlet外面。 # 控制台会打出Stacktrace,但程序不会停止 try: gevent.joinall([winner, loser]) except Exception as e: # 这段永远不会被执行 print 'This will never be reached' print winner.ready() # True print loser.ready() # True print winner.value # 'You win!' print loser.value # None print winner.successful() # True print loser.successful() # False # 这里可以通过raise loser.exception 或 loser.get() # 来将协程中的异常抛出 print loser.exception |
协程运行超时
之前我们讲过在”gevent.joinall()”方法中可以传入timeout参数来设置超时,我们也可以在全局范围内设置超时时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import gevent from gevent import Timeout timeout = Timeout(2) # 2 seconds timeout.start() def wait(): gevent.sleep(10) try: gevent.spawn(wait).join() except Timeout: print('Could not complete') |
上例中,我们将超时设为2秒,此后所有协程的运行,如果超过两秒就会抛出”Timeout”异常。我们也可以将超时设置在with语句内,这样该设置只在with语句块中有效:
1 2 |
with Timeout(1): gevent.sleep(10) |
此外,我们可以指定超时所抛出的异常,来替换默认的”Timeout”异常。比如下例中超时就会抛出我们自定义的”TooLong”异常。
1 2 3 4 5 |
class TooLong(Exception): pass with Timeout(1, TooLong): gevent.sleep(10) |
协程间通讯
greenlet协程间的异步通讯可以使用事件(Event)对象。该对象的”wait()”方法可以阻塞当前协程,而”set()”方法可以唤醒之前阻塞的协程。在下面的例子中,5个waiter协程都会等待事件evt,当setter协程在3秒后设置evt事件,所有的waiter协程即被唤醒。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#coding:utf8 import gevent from gevent.event import Event evt = Event() def setter(): print 'Wait for me' gevent.sleep(3) # 3秒后唤醒所有在evt上等待的协程 print "Ok, I'm done" evt.set() # 唤醒 def waiter(): print "I'll wait for you" evt.wait() # 等待 print 'Finish waiting' gevent.joinall([ gevent.spawn(setter), gevent.spawn(waiter), gevent.spawn(waiter), gevent.spawn(waiter), gevent.spawn(waiter), gevent.spawn(waiter) ]) |
除了Event事件外,gevent还提供了AsyncResult事件,它可以在唤醒时传递消息。让我们将上例中的setter和waiter作如下改动:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from gevent.event import AsyncResult aevt = AsyncResult() def setter(): print 'Wait for me' gevent.sleep(3) # 3秒后唤醒所有在evt上等待的协程 print "Ok, I'm done" aevt.set('Hello!') # 唤醒,并传递消息 def waiter(): print("I'll wait for you") message = aevt.get() # 等待,并在唤醒时获取消息 print 'Got wake up message: %s' % message |
队列 Queue
队列Queue的概念相信大家都知道,我们可以用它的put和get方法来存取队列中的元素。gevent的队列对象可以让greenlet协程之间安全的访问。运行下面的程序,你会看到3个消费者会分别消费队列中的产品,且消费过的产品不会被另一个消费者再取到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import gevent from gevent.queue import Queue products = Queue() def consumer(name): while not products.empty(): print '%s got product %s' % (name, products.get()) gevent.sleep(0) print '%s Quit' def producer(): for i in xrange(1, 10): products.put(i) gevent.joinall([ gevent.spawn(producer), gevent.spawn(consumer, 'steve'), gevent.spawn(consumer, 'john'), gevent.spawn(consumer, 'nancy'), ]) |
put和get方法都是阻塞式的,它们都有非阻塞的版本:put_nowait和get_nowait。如果调用get方法时队列为空,则抛出”gevent.queue.Empty”异常。
信号量
信号量可以用来限制协程并发的个数。它有两个方法,acquire和release。顾名思义,acquire就是获取信号量,而release就是释放。当所有信号量都已被获取,那剩余的协程就只能等待任一协程释放信号量后才能得以运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import gevent from gevent.coros import BoundedSemaphore sem = BoundedSemaphore(2) def worker(n): sem.acquire() print('Worker %i acquired semaphore' % n) gevent.sleep(0) sem.release() print('Worker %i released semaphore' % n) gevent.joinall([gevent.spawn(worker, i) for i in xrange(0, 6)]) |
上面的例子中,我们初始化了”BoundedSemaphore”信号量,并将其个数定为2。所以同一个时间,只能有两个worker协程被调度。程序运行后的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Worker 0 acquired semaphore Worker 1 acquired semaphore Worker 0 released semaphore Worker 1 released semaphore Worker 2 acquired semaphore Worker 3 acquired semaphore Worker 2 released semaphore Worker 3 released semaphore Worker 4 acquired semaphore Worker 4 released semaphore Worker 5 acquired semaphore Worker 5 released semaphore |
如果信号量个数为1,那就等同于同步锁。
协程本地变量
同线程类似,协程也有本地变量,也就是只在当前协程内可被访问的变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import gevent from gevent.local import local data = local() def f1(): data.x = 1 print data.x def f2(): try: print data.x except AttributeError: print 'x is not visible' gevent.joinall([ gevent.spawn(f1), gevent.spawn(f2) ]) |
通过将变量存放在local对象中,即可将其的作用域限制在当前协程内,当其他协程要访问该变量时,就会抛出异常。不同协程间可以有重名的本地变量,而且互相不影响。因为协程本地变量的实现,就是将其存放在以的”greenlet.getcurrent()”的返回为键值的私有的命名空间内。
实际应用
讲到这里,大家肯定很想看一个gevent的实际应用吧,这里有一个简单的聊天室程序,基于Flask实现,大家可以参考下。