CmdTree是一个命令行库,参见 这里

https://github.com/winkidney/cmdtree

轮子满地都是……然而我又再造了一个(自尽

现有的库虽然有各自的设计哲学,却无法满足某些特定的需求:)。

能不能享受click带来的便利的同时,也能获取到argparse的子命令支持呢?

为了不再每次都要重复解决这个问题,复制粘贴代码,我写了CmdTree.

- 阅读剩余部分 -

引子

上周基友忽然问我,周六能不能帮他写写代码,说是参加什么比赛,可以多个人一起做题,赛程好像是24小时。

当时我感觉应该是比较麻烦的事情,准备不做的,不过后面一想,万一很有趣呢,哈哈。

到了周六,都差点忘了有这回事,被一个QQ消息提醒了,就开始做题了。后面才知道,这个活动叫“极限编程大赛”,会临场发布一些网上基本上搜不到的问题,给你解决,分数根据跑过的测试用例来计算。

我帮他做了两个题,都还算比较理想,也花了不少时间,但感觉颇有收获:)

- 阅读剩余部分 -

https://github.com/pallets/werkzeug

文件在 werkzeug/local.py

看这部分源码,主要想搞清楚以下几个问题:

  • ThreadLocal解决什么问题
  • ThreadLocal如何实现
  • ThreadLocal的生命周期管理

ThreadLocal解决什么问题

ThreadLocal是需要拿来和全局变量对比的。

当大家都需要用相同的逻辑,引用相同的变量名/资源来完成自己的逻辑,但又不希望不同的线程直接一个全局引用会对别的线程造成影响,需要使用ThreadLocal来解决这个问题。

ThreadLocal 解决的不是多线程编程资源共享的问题,更多的是在逻辑层面,用来管理一些跟随线程生命周期的上下文数据,让程序逻辑更加容易编写和维护。

所有的代码能公用同一个逻辑,而不会对另外的线程(全局资源)造成影响。

ThreadLocal如何实现

每一个运行中的Thread都会有自己的一个标识符,这个标识符是线程数据结构的一部分。

通过如下步骤,可以实现ThreadLocal

一个进程内全局的Storage,依靠一个Map来存储ThreadID和其对应的数据。
接下来,使用Local()来获取本地变量,实际上是一个查询函数用于获取数据,这个函数帮忙做的事情,就是自动获取Thread ID,从全局的ThreadVariableMap中获取到线程对应的数据集合。
如此一来,每个线程,都可以使用相同的代码逻辑来执行逻辑而不会对全局资源产生影响/依赖。

在Werkzeug中的ThreadLocal大概是这样的结构。

+---------------------------------------------------------+
|                                                         |
|         +----------------------------------+            |
|         |                                  |            |
|         |    LocalStack/LocalsRegistry     |            |
|         |                                  |            |
|         +---------^------------------------+            |
|                   |                                     |
|                   |                                     |
|                   |                                     |
|                   |                                     |
|                   |                                     |
|   +---------------+-------+                             |
|   |                       |                             |
|   |  Local Variables      |                             |
|   |  Marked by Thred-ID   |                             |
|   |  or Greenlet-ID       |                             |
|   |                       |                             |
|   +-----------------------+                             |
|                                                         |
+---------------------------^-----------------------------+
                            |
                            |
                            |
                            |
                            | Identifier(Greenet-ID or
                            | Thread-ID)
                            |
+---------------------------+-----------------------------+
|                                                         |
|                       LocalProxy                        |
|                                                         |
+---------------------------^-----------------------------+
                            |
                            |
                            |
+---------------------------+-----------------------------+
|                                                         |
|                     Application                         |
|                                                         |
+---------------------------------------------------------+

ThreadLocal的生命周期管理

ThreadLocal是的生命周期是跟随Thread的生命周期的。

通常来说,ThreadLocal应该在Thread生命周期结束的时候进行销毁和清理,不然就会造成内存泄漏。

在Werkeug中,ThreadLocal用于WSGIRequest和WSGIResponse,可以看到,ThreadLocal被作为一个Middleware被插入WSGI处理流程中,然后在Response返回执行完毕之后被销毁。

这两天读了一下Python的Condition实现源码,是实现Queue的工具之一,发现是非常朴素的sleep->loop->query模式。源码很少,直接贴出,就不做注释了:)

def wait(self, timeout=None):
        """Wait until notified or until a timeout occurs.
 
        If the calling thread has not acquired the lock when this method is
        called, a RuntimeError is raised.
 
        This method releases the underlying lock, and then blocks until it is
        awakened by a notify() or notifyAll() call for the same condition
        variable in another thread, or until the optional timeout occurs. Once
        awakened or timed out, it re-acquires the lock and returns.
 
        When the timeout argument is present and not None, it should be a
        floating point number specifying a timeout for the operation in seconds
        (or fractions thereof).
 
        When the underlying lock is an RLock, it is not released using its
        release() method, since this may not actually unlock the lock when it
        was acquired multiple times recursively. Instead, an internal interface
        of the RLock class is used, which really unlocks it even when it has
        been recursively acquired several times. Another internal interface is
        then used to restore the recursion level when the lock is reacquired.
 
        """
        if not self._is_owned():
            raise RuntimeError("cannot wait on un-acquired lock")
        waiter = _allocate_lock()
        waiter.acquire()
        self.__waiters.append(waiter)
        saved_state = self._release_save()
        try:    # restore state no matter what (e.g., KeyboardInterrupt)
            if timeout is None:
                waiter.acquire()
                if __debug__:
                    self._note("%s.wait(): got it", self)
            else:
                # Balancing act:  We can't afford a pure busy loop, so we
                # have to sleep; but if we sleep the whole timeout time,
                # we'll be unresponsive.  The scheme here sleeps very
                # little at first, longer as time goes on, but never longer
                # than 20 times per second (or the timeout time remaining).
                endtime = _time() + timeout
                delay = 0.0005 # 500 us -> initial delay of 1 ms
                while True:
                    gotit = waiter.acquire(0)
                    if gotit:
                        break
                    remaining = endtime - _time()
                    if remaining <= 0:
                        break
                    delay = min(delay * 2, remaining, .05)
                    _sleep(delay)
                if not gotit:
                    if __debug__:
                        self._note("%s.wait(%s): timed out", self, timeout)
                    try:
                        self.__waiters.remove(waiter)
                    except ValueError:
                        pass
                else:
                    if __debug__:
                        self._note("%s.wait(%s): got it", self, timeout)
        finally:
            self._acquire_restore(saved_state)

前段时间买了一个树莓派,最初的想法是可以用来挂一些下载,或者挂一些爬虫,做做简单的NAS之类的。

最后到手之后,发现树莓派3 B+还是很快的,4个CPU核心+1G RAM,外接移动硬盘的情况下,IO上也可以接受。

添加风扇控制

我从淘宝上弄了一个风扇,直接接到树莓派的5v-0v接口上,这样风扇会一直运转,其实还挺烦的……因为声音比较大……所以希望风扇能在温度低于45度的时候,能够自动关闭就好了。

首先参考如下篇文章,接好一个三极管,并将三极管的P级接到树莓派的GPIO 12针脚。

在树莓派上启用软件PWM控制风扇(Shell脚本版)
http://www.zhangminghao.com/post/43.html

树莓派用开关三极管控制散热风扇
http://yshblog.com/blog/55

树莓派的PWM功能我参考了一下,但是调试之后无效,可能是我的三极管问题,或者接线没接好。

参考RPi.GPIO 0.5.2a now has software PWM – How to use it

http://raspi.tv/2013/rpi-gpio-0-5-2a-now-has-software-pwm-how-to-use-it

所以最后,我做了风扇自动开启和关闭的功能,但是没做自动调速功能。

项目在Github: PIFanTuner https://github.com/winkidney/PIFanTuner

直接下载下来,运行

sudo python setup.py install
./install-systemd-file.sh

[Unit]
Description=Remote desktop service (VNC)
After=syslog.target network.target

[Service]
Type=forking
User=pi
ExecStartPre=/bin/sh -c '/usr/bin/tightvncserver -kill :i > /dev/null 2>&1 || :'
ExecStart=/usr/bin/tightvncserver  :1
ExecStop=/usr/bin/tightvncserver -kill :1

[Install]
WantedBy=multi-user.target

然后运行

sudo systemctl daemon-reload
sudo systemctl enable vnc-server@1
sudo systemctl start vnc-server@1

然后,在Windows/linux/Mac上下载一个tightvnc viewer, 使用 your_ip_or_host:5901 ,就可以连接到VNC服务器了,以后每次开启树莓派,服务也会自动启动。

连接到隐藏的无线网络

树莓派使用lxde-panel 并没有连接到无线网络的功能。

参考noob wifi with hidden ssid

在命令行键入 sudo iwlist wlan0 scan essid *yourSSID* 即可在无线列表中找到隐藏了ID的无线,点击一下连接即可。

搭建下载环境

搭建下载环境,可以参考 Raspberry Pi从零开始搭建低成本NAS(7)-Aria2远程下载,
http://mkitby.com/2016/01/15/raspberry-pi-nas-remote-download-aria2/

最后的aria2 web-ui我选择了webui-aria2