繁体   English   中英

Python 相当于 Golang 在频道上的 select

[英]Python equivalent of Golang's select on channels

Go 有一个 select 声明,适用于频道。 从文档中:

select 语句让 goroutine 等待多个通信操作。

select 阻塞直到它的一个 case 可以运行,然后它执行那个 case。 如果多个准备就绪,它会随机选择一个。

是否有 Python 等效于以下代码:

package main

import "fmt"

func main() {
    c1 := make(chan int)
    c2 := make(chan int)
    quit := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c1 <- i
        }
        quit <- 0
    }()

    go func() {
        for i := 0; i < 2; i++ {
            c2 <- i
        }
    }()

    for {
        select {
        case <-c1:
            fmt.Println("Received value from c1")
        case <-c2:
            fmt.Println("Received value from c2")
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

本程序Output:

Received value from c1
Received value from c1
Received value from c2
Received value from c1
Received value from c2
Received value from c1
Received value from c1
Received value from c1
Received value from c1
Received value from c1
Received value from c1
Received value from c1
quit

这是一个非常直接的翻译,但“选择哪个如果多个已经准备好”部分的工作方式不同 - 它只是采取了首先出现的内容。 这也就像使用gomaxprocs(1)运行代码一样。

import threading
import Queue

def main():
    c1 = Queue.Queue(maxsize=0)
    c2 = Queue.Queue(maxsize=0)
    quit = Queue.Queue(maxsize=0)

    def func1():
        for i in range(10):
            c1.put(i)
        quit.put(0)

    threading.Thread(target=func1).start()

    def func2():
        for i in range(2):
            c2.put(i)

    threading.Thread(target=func2).start()

    combined = Queue.Queue(maxsize=0)

    def listen_and_forward(queue):
        while True:
            combined.put((queue, queue.get()))

    t = threading.Thread(target=listen_and_forward, args=(c1,))
    t.daemon = True
    t.start()
    t = threading.Thread(target=listen_and_forward, args=(c2,))
    t.daemon = True
    t.start()
    t = threading.Thread(target=listen_and_forward, args=(quit,))
    t.daemon = True
    t.start()

    while True:
        which, message = combined.get()
        if which is c1:
            print 'Received value from c1'
        elif which is c2:
            print 'Received value from c2'
        elif which is quit:
            print 'Received value from quit'
            return
main()

基本的变化是使用组合消息的线程模拟选择。 如果您打算多使用此模式,可以编写一些选择代码:

import threading
import Queue

def select(*queues):
    combined = Queue.Queue(maxsize=0)
    def listen_and_forward(queue):
        while True:
            combined.put((queue, queue.get()))
    for queue in queues:
        t = threading.Thread(target=listen_and_forward, args=(queue,))
        t.daemon = True
        t.start()
    while True:
        yield combined.get()

def main():

    c1 = Queue.Queue(maxsize=0)
    c2 = Queue.Queue(maxsize=0)
    quit = Queue.Queue(maxsize=0)

    def func1():
        for i in range(10):
            c1.put(i)
        quit.put(0)

    threading.Thread(target=func1).start()

    def func2():
        for i in range(2):
            c2.put(i)

    threading.Thread(target=func2).start()

    for which, msg in select(c1, c2, quit):
        if which is c1:
            print 'Received value from c1'
        elif which is c2:
            print 'Received value from c2'
        elif which is quit:
            print 'Received value from quit'
            return
main()

但...

请注意,这个选择并不是很好的选择,尽管它对你的程序没有关系 - 如果我们并不总是遍历,那么goroutine可以在一个将在select中排队的频道发送结果并丢失。选择完成!

还可以考虑Benoit Chesneau的偏移库 它是Python的Go并发模型的一个端口,使用了光纤。

他在PyCon APAC 2013上发表了关于此事的演讲:

您可以使用multiprocessing.Pipe而不是chanthreading.Thread而不是goselect.select而不是select

这是使用这种方法在Python中重新实现你的go示例:

import random
from multiprocessing import Pipe
from select import select
from threading import Thread


def main():
    c1_r, c1_w = Pipe(duplex=False)
    c2_r, c2_w = Pipe(duplex=False)
    quit_r, quit_w = Pipe(duplex=False)

    def func1():
        for i in range(10):
            c1_w.send(i)
        quit_w.send(0)

    Thread(target=func1).start()

    def func2():
        for i in range(2):
            c2_w.send(i)

    Thread(target=func2).start()

    while True:
        ready, _, _ = select([c1_r, c2_r, quit_r], [], [])
        which = random.choice(ready)
        if which == c1_r:
            c1_r.recv()
            print 'Received value from c1'
        elif which == c2_r:
            c2_r.recv()
            print 'Received value from c2'
        elif which == quit_r:
            quit_r.recv()
            print 'Received value from quit'
            return

if __name__ == '__main__':
    main()

这个实现基于@Thomas的实现,但与@ Thomas不同,它不会产生额外的线程来执行select。

使用Python 2.7.13在Linux上测试。 Windows可能表现不同,因为select是Unixy的东西。

使用Python 3.5,有关键字asyncawait可以使函数可以在执行中暂停,因此可以在evenloop而不是线程上运行。 asyncio std lib提供了一个。

为了更直接地映射Go阻塞通道的行为并select你可以使用这个小库 ,然后你的示例代码在Python中看起来非常相似。

这是另一种模仿go语法的尝试:

from threading import Thread
from Queue import Queue

def main():

    c1 = Queue.Queue(maxsize=0)
    c2 = Queue.Queue(maxsize=0)
    quit = Queue.Queue(maxsize=0)

    Thread(target=lambda: [c1.put(i) for i in range(10)] or quit.put(0)).start()
    Thread(target=lambda: [c2.put(i) for i in range(2)]).start()

    for which, msg in select(c1, c2, quit):
        if which is c1:
            print 'Received value from c1'
        elif which is c2:
            print 'Received value from c2'
        elif which is quit:
            print 'Received value from quit'
            return

def select(*queues):
    combined = Queue.Queue(maxsize=0)
    def listen_and_forward(queue):
        while True:
            combined.put((queue, queue.get()))
    for queue in queues:
        t = Thread(target=listen_and_forward, args=(queue,))
        t.daemon = True
        t.start()
    while True:
        yield combined.get()

main()

是的,所有是可能的goless 你可以尝试一下。

玩得开心 ;-)

这是一个例子:

c1 = goless.chan()
c2 = goless.chan()

def func1():
    time.sleep(1)
    c1.send('one')
goless.go(func1)

def func2():
    time.sleep(2)
    c2.send('two')
goless.go(func2)

for i in range(2):
    case, val = goless.select([goless.rcase(c1), goless.rcase(c2)])
    print(val)

为了完整性:go-style渠道,包括工作选择,作为pygolang的一部分提供:

ch1 = chan()    # synchronous channel
ch2 = chan(3)   # channel with buffer of size 3

def _():
    ch1.send('a')
    ch2.send('b')
go(_)

ch1.recv()      # will give 'a'
ch2.recv_()     # will give ('b', True)

_, _rx = select(
    ch1.recv,           # 0
    ch2.recv_,          # 1
    (ch2.send, obj2),   # 2
    default,            # 3
)
if _ == 0:
    # _rx is what was received from ch1
    ...
if _ == 1:
    # _rx is (rx, ok) of what was received from ch2
    ...
if _ == 2:
    # we know obj2 was sent to ch2
    ...
if _ == 3:
    # default case
    ...

offset (参见https://stackoverflow.com/a/19143696/9456786 )似乎也很有趣。

遗憾的是, goless (请参阅https://stackoverflow.com/a/39269599/9456786 )具有弱选择实现 ,这在设计上无法在同步通道上正常工作

这里有几个答案使用queue.Queuethreading.Thread来模拟 select 行为,但这不是必需的。 您可以像这样扩展queue.Queue

import queue
import os
import select

class EQueue(queue.Queue):
    def __init__(self, *args, **kwargs)
        self._fd = os.eventfd(flags=0x00004001)
        super().__init__(*args, **kwargs)

    def put(self, *args, **kwargs):
        super().put(*args, **kwargs)
        eventfd_write(self._fd, 1)

    def get(self, *args, **kwargs):
        os.eventfd_read(self._fd)
        super().get(*args, **kwargs)

    def fileno(self):
        return self._fd

    def __del__(self):
        os.close(self._fd)

这会在队列周围添加一个额外的信号量,而且至关重要的是,它可以通过文件描述符访问。 这意味着您现在可以使用select.select()在此队列上等待。 因此,可以在没有额外线程的情况下重写上述使用队列和线程的示例:

def main():

    c1 = EQueue(maxsize=0)
    c2 = EQueue(maxsize=0)
    quit = EQueue(maxsize=0)

    def func1():
        for i in range(10):
            c1.put(i)
        quit.put(0)

    threading.Thread(target=func1).start()

    def func2():
        for i in range(2):
            c2.put(i)

    threading.Thread(target=func2).start()

    rx, _, _ = select.select([c1, c2, quit], [], []):
        if c1 in rx:
            msg = c1.get()
            print 'Received value from c1'
        elif c2 in rx:
            msg = c2.get()
            print 'Received value from c2'
        elif quit in rx:
            print 'Received value from quit'
            return
main()

这里的main function 与上面@alkasm 给出的非常相似,但是没有select的自定义实现,也没有 thread-per-queue 将所有单独的队列收集到一个; 它依靠操作系统来告诉您队列何时有可用的项目。

请注意, os.eventfd仅在 Python 3.10 中添加,但在 ctypes 中实现它相当简单,或者在 PyPI 上有eventfd package。 后者还支持 Windows,与其他选项不同,它使用管道模拟 eventfds。 python doco 声称 eventfds 仅在运行 glibc >= 2.8 的 Linux 系统上可用,但 muslc 也支持它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM