PAN*_*ack 11 python multithreading
我希望你能帮助我.这需要一些解释......
TLDR问题:为什么线程进程会从解释器中按预期运行(分离的python线程),myprocess.start()但是当从终端运行时阻塞子线程python myprocess.py?
背景:我threading.Thread为我的类子类化,它也调用了另外两个Thread类型的子类.看起来像:
class Node(threading.Thread):
def __init__(self, gps_device):
threading.Thread.__init__(self)
self.daemon = False
logging.info("Setting up GPS service")
self.gps_svc = gps.CoordinateService(gps_device)
self.gps_svc.daemon = True
logging.info("Setting up BLE scanning service")
# TODO: This is blocking when run in terminal (aka how we do on Raspberry Pi)
self.scan_svc = scan.BleMonitor()
self.scan_svc.daemon = True
logging.info("Node initialized - ready for start")
def run(self):
self.gps_svc.start()
self.scan_svc.start() # blocks here in terminal
do stuff...
Run Code Online (Sandbox Code Playgroud)
这两个服务(gps_svc和scan_svc)都像解释器那样按预期工作node = Node(...); node.start().当我在终端中运行时,gps_svc启动和功能,但是scan_svc在特定线路上的块,它会监听蓝牙设备.
BLE扫描仪低于(它很长).这是父类BleMonitor- 没有任何胆量不同,我只添加了几个实用函数.
问题:为什么会这样?我可以运行/与PROCESS交互吗myprocess.start()(即:调用类的方法并实时获取数据)?
class Monitor(threading.Thread):
"""Continously scan for BLE advertisements."""
def __init__(self, callback, bt_device_id, device_filter, packet_filter):
"""Construct interface object."""
# do import here so that the package can be used in parsing-only mode (no bluez required)
self.bluez = import_module('bluetooth._bluetooth')
threading.Thread.__init__(self)
self.daemon = False
self.keep_going = True
self.callback = callback
# number of the bt device (hciX)
self.bt_device_id = bt_device_id
# list of beacons to monitor
self.device_filter = device_filter
self.mode = get_mode(device_filter)
# list of packet types to monitor
self.packet_filter = packet_filter
# bluetooth socket
self.socket = None
# keep track of Eddystone Beacon <-> bt addr mapping
self.eddystone_mappings = []
def run(self):
"""Continously scan for BLE advertisements."""
self.socket = self.bluez.hci_open_dev(self.bt_device_id)
filtr = self.bluez.hci_filter_new()
self.bluez.hci_filter_all_events(filtr)
self.bluez.hci_filter_set_ptype(filtr, self.bluez.HCI_EVENT_PKT)
self.socket.setsockopt(self.bluez.SOL_HCI, self.bluez.HCI_FILTER, filtr)
self.toggle_scan(True)
while self.keep_going:
pkt = self.socket.recv(255)
event = to_int(pkt[1])
subevent = to_int(pkt[3])
if event == LE_META_EVENT and subevent == EVT_LE_ADVERTISING_REPORT:
# we have an BLE advertisement
self.process_packet(pkt)
def toggle_scan(self, enable):
"""Enable and disable BLE scanning."""
if enable:
command = "\x01\x00"
else:
command = "\x00\x00"
self.bluez.hci_send_cmd(self.socket, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, command)
def process_packet(self, pkt):
"""Parse the packet and call callback if one of the filters matches."""
# check if this could be a valid packet before parsing
# this reduces the CPU load significantly
if (self.mode == MODE_BOTH and \
(pkt[19:21] != b"\xaa\xfe") and (pkt[19:23] != b"\x4c\x00\x02\x15")) \
or (self.mode == MODE_EDDYSTONE and (pkt[19:21] != b"\xaa\xfe")) \
or (self.mode == MODE_IBEACON and (pkt[19:23] != b"\x4c\x00\x02\x15")):
return
bt_addr = bt_addr_to_string(pkt[7:13])
rssi = bin_to_int(pkt[-1])
# strip bluetooth address and parse packet
packet = parse_packet(pkt[14:-1])
# return if packet was not an beacon advertisement
if not packet:
return
# we need to remember which eddystone beacon has which bt address
# because the TLM and URL frames do not contain the namespace and instance
self.save_bt_addr(packet, bt_addr)
# properties hold the identifying information for a beacon
# e.g. instance and namespace for eddystone; uuid, major, minor for iBeacon
properties = self.get_properties(packet, bt_addr)
if self.device_filter is None and self.packet_filter is None:
# no filters selected
self.callback(bt_addr, rssi, packet, properties)
elif self.device_filter is None:
# filter by packet type
if is_one_of(packet, self.packet_filter):
self.callback(bt_addr, rssi, packet, properties)
else:
# filter by device and packet type
if self.packet_filter and not is_one_of(packet, self.packet_filter):
# return if packet filter does not match
return
# iterate over filters and call .matches() on each
for filtr in self.device_filter:
if isinstance(filtr, BtAddrFilter):
if filtr.matches({'bt_addr':bt_addr}):
self.callback(bt_addr, rssi, packet, properties)
return
elif filtr.matches(properties):
self.callback(bt_addr, rssi, packet, properties)
return
def save_bt_addr(self, packet, bt_addr):
"""Add to the list of mappings."""
if isinstance(packet, EddystoneUIDFrame):
# remove out old mapping
new_mappings = [m for m in self.eddystone_mappings if m[0] != bt_addr]
new_mappings.append((bt_addr, packet.properties))
self.eddystone_mappings = new_mappings
def get_properties(self, packet, bt_addr):
"""Get properties of beacon depending on type."""
if is_one_of(packet, [EddystoneTLMFrame, EddystoneURLFrame, \
EddystoneEncryptedTLMFrame, EddystoneEIDFrame]):
# here we retrieve the namespace and instance which corresponds to the
# eddystone beacon with this bt address
return self.properties_from_mapping(bt_addr)
else:
return packet.properties
def properties_from_mapping(self, bt_addr):
"""Retrieve properties (namespace, instance) for the specified bt address."""
for addr, properties in self.eddystone_mappings:
if addr == bt_addr:
return properties
return None
def terminate(self):
"""Signal runner to stop and join thread."""
self.toggle_scan(False)
self.keep_going = False
self.join()
Run Code Online (Sandbox Code Playgroud)
从Python文档中,我认为交互模式下的解释器在线程方面违反了以下规定:
在 CPython 中,全局解释器锁(GIL)是一个互斥体,用于保护对 Python 对象的访问,防止多个线程同时执行 Python 字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(但是,自从 GIL 存在以来,其他功能已经开始依赖于它所强制执行的保证。)
因此,存在这样的规则:只有获得了GIL的线程才可以操作Python对象或调用Python/C API函数。为了模拟执行的并发性,解释器会定期尝试切换线程(请参阅 sys.setswitchinterval())。该锁也会在可能阻塞的 I/O 操作(例如读取或写入文件)周围释放,以便其他 Python 线程可以同时运行。
我需要进一步研究这个问题,但我的怀疑表明 GIL 和线程对象管理之间存在冲突。希望有帮助或者有人有更多补充。