Python 3.7.1 模块 并发执行 底层线程API _thread


该模块提供了用于处理多线程(也称为轻量级进程或任务)的低级原语- 多个控制线程共享其全局数据空间。为了同步,提供了简单的锁(也称为互斥锁或二进制信号量)。 threading模块提供了一个基于此模块构建的更易于使用和更高级别的线程API。

版本3.7中已更改:此模块曾经是可选的,现在始终可用。

1._thread 函数

该模块定义了以下常量和函数:

exception _thread.error

引发特定于线程的错误。

在版本3.3中更改:现在是内置RuntimeError的同义词。

_thread.LockType

这是锁定对象的锁的类型。

_thread.start_new_thread(function, args[, kwargs])

启动一个新线程并返回其标识符。线程使用参数列表args(必须是元组)执行function函数。可选的 kwargs参数指定关键字参数的字典。当函数返回时,线程将以静默方式退出。当函数以未处理的异常终止时,将打印堆栈跟踪,然后线程退出(但其他线程继续运行)。

_thread.interrupt_main()

在主线程中引发KeyboardInterrupt异常。子线程可以使用此函数来中断主线程。

_thread.exit()

引发SystemExit异常。未捕获时,这将导致线程以静默方式退出。

_thread.allocate_lock()

返回一个新的锁对象。锁的方法如下所述。锁最初是非锁定的。

_thread.get_ident()

返回当前线程的“线程标识符”。这是一个非零整数。它的值没有直接意义; 它旨在用作例如索引线程特定数据的字典的 magic cookie。当线程退出并创建另一个线程时,可以回收线程标识符。

_thread.stack_size([size])

返回创建新线程时使用的线程堆栈大小。可选的 size参数指定用于后续创建的线程的堆栈大小,并且必须为0(使用平台或已配置的默认值)或至少为32,768(32 KiB)的正整数值。如果未指定size,则使用0。如果不支持更改线程堆栈大小,则引发RuntimeError。如果指定的堆栈大小无效,则引发ValueError,堆栈大小未经修改。32 KiB是目前支持的最小堆栈大小值,以保证解释器本身有足够的堆栈空间。请注意,某些平台可能对堆栈大小的值有特定限制,例如要求最小堆栈大小> 32 KiB或需要以系统内存页面大小的倍数进行分配 - 应参考平台文档以获取更多信息(4 KiB页面很常见;在没有更具体信息的情况下,建议的方法是使用4096的倍数作为堆栈大小)。

可用性:Windows,具有POSIX线程的系统。

_thread.TIMEOUT_MAX

允许Lock.acquire()的timeout参数最大值。指定超过此值的超时将引发一个OverflowError。

版本3.2中的新功能。

2.lock函数

锁对象具有以下方法:

lock.acquire(waitflag=1, timeout=-1)

在没有任何可选参数的情况下,此方法无条件地获取锁,如果有必要,等待它被另一个线程释放(一次只有一个线程可以获取锁定 - 这就是它们存在的原因)。

如果存在整数waitflag参数,则操作取决于其值:如果它为零,则仅在不等待的情况下立即获取锁定时获取锁定,而如果它非零,则如上所述无条件地获取锁定。

如果浮点型timeout参数存在且为正,则它指定返回之前的最长等待时间(以秒为单位)。负 timeout参数指定无限制等待。如果waitflag为零,则无法指定超时。

如果成功获取锁定则返回值True,否则 返回值False。

改变在3.2版本:该超时参数是新增加的。

在版本3.2中更改:现在可以通过POSIX上的信号中断锁定获取。

lock.release()

释放锁定。必须先获取锁,但不一定是同一个线程。

lock.locked()

返回锁的状态:如果已被某个线程获取返回True, 如果没有返回False。

3.其它

除了这些方法之外,还可以通过with语句使用锁定对象 ,例如:

import _thread

a_lock = _thread.allocate_lock()

with a_lock:
    print("a_lock is locked while this executes")

注意事项:

  • 线程与中断奇怪地交互:KeyboardInterrupt 异常将由任意线程接收。(当signal 模块可用时,中断始终转到主线程。)
  • 调用sys.exit()或抛出SystemExit异常等同于调用_thread.exit()。
  • 无法在锁定时中断acquire()方法 - KeyboardInterrupt异常发生在锁被accquired之后。
  • 当主线程退出时,系统决定其他线程是否存活!在大多数系统中,它们在不执行 try… finally子句或执行对象析构函数的情况下被终止。
  • 当主线程退出时,它不执行任何常规清理(除了try… finally子句被使用),并且不刷新标准I / O文件。

4.实例

这里引用了网友的 实例,特此说明

#!/usr/bin/env python3
# coding:utf-8
from time import ctime
from time import sleep
import _thread

loops = [4, 2, 3, 5]


def loop(nloop, nsec, lock):    # 参数依次为:标识,睡眠时间,锁对象
    print("start loop", nloop, 'at:', ctime())
    sleep(nsec)
    print("loop", nloop, "done at:", ctime())
    lock.release()  # 释放锁


def main():
    print('start at:', ctime())
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = _thread.allocate_lock()  # 分配锁对象
        lock.acquire()  # 获取锁对象
        locks.append(lock)

    for i in nloops:
        _thread.start_new(loop, (i, loops[i], locks[i]))

    # 等待所有锁被释放
    for i in nloops:
        while(locks[i].locked()):
            pass
    print('all DONE at', ctime())


if __name__ == '__main__':
    main()
我们将在飞牛NAS上部署一个Docker容器,该容器运行一个WebSocket服务器,要求:1.支持密钥验证2.接收数据并存储到SQLite数据库3.提供API接口用于查询实时数据、历史数据4.支持根据指定Tag生成历史曲线图##解决方案###1.项目结构创建项目目录ws-server,结构如下:ws-server/├──Dockerfile├──requirements.txt├──app/│├──main.py#主程序,包含WebSocket和API服务│├──auth.py#密钥验证│├──database.py#数据库操作│└──plotter.py#绘图功能###2.代码实现####2.1密钥验证(auth.py)pythonfromfastapiimportHTTPException,HeaderSECRET_KEY="your_secure_key_123"#实际部署时使用环境变量asyncdefverify_token(authorization:str=Header(None)):ifauthorizationisNoneorauthorization!=f"Bearer{SECRET_KEY}":raiseHTTPException(status_code=403,detail="Invalidauthenticationtoken")####2.2数据库操作(database.py)pythonimportsqlite3importosDB_PATH=os.getenv('DB_PATH','sensor_data.db')definit_db():conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute('''CREATETABLEIFNOTEXISTSsensor_data(idINTEGERPRIMARYKEYAUTOINCREMENT,timestampDATETIMEDEFAULTCURRENT_TIMESTAMP,tag_idINTEGER,tag_nameTEXT,valueREAL,data_timeTEXT)''')conn.commit()conn.close()definsert_data(tag_id:int,tag_name:str,value:float,data_time:str):conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute("INSERTINTOsensor_data(tag_id,tag_name,value,data_time)VALUES(?,?,?,?)",(tag_id,tag_name,value,data_time))conn.commit()conn.close()defget_latest_data(tag_id:int,limit:int=1):conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute("SELECT*FROMsensor_dataWHEREtag_id=?ORDERBYtimestampDESCLIMIT?",(tag_id,limit))result=c.fetchall()conn.close()returnresultdefget_historical_data(tag_id:int,hours:int=24):conn=sqlite3.connect(DB_PATH)c=conn.cursor()#获取最近hours小时的数据c.execute("SELECT*FROMsensor_dataWHEREtag_id=?ANDtimestamp>=datetime('now',?)ORDERBYtimestamp",(tag_id,f'-{hours}hours'))result=c.fetchall()conn.close()returnresult####2.3绘图功能(plotter.py)pythonimportmatplotlib.pyplotaspltimportnumpyasnpfromdatetimeimportdatetimeimportosfrom.databaseimportget_historical_dataPLOT_DIR='plots'defgenerate_plot(tag_id:int,hours:int=24):#确保绘图目录存在ifnotos.path.exists(PLOT_DIR):os.makedirs(PLOT_DIR)#获取数据data=get_historical_data(tag_id,hours)ifnotdata:returnNonetimestamps=[row[1]forrowindata]#时间戳values=[row[4]forrowindata]#值#将时间字符串转换为datetime对象timestamps=[datetime.strptime(ts,'%Y-%m-%d%H:%M:%S')fortsintimestamps]plt.figure(figsize=(10,6))plt.plot(timestamps,values,'b-')plt.title(f'TagID:{tag_id}-Last{hours}Hours')plt.xlabel('Time')plt.ylabel('Value')plt.grid(True)plt.xticks(rotation=45)plt.tight_layout()#保存图像plot_path=os.path.join(PLOT_DIR,f'tag_{tag_id}_{hours}h.png')plt.savefig(plot_path)plt.close()returnplot_path####2.4主程序(main.py)pythonimportasyncioimportwebsocketsimportjsonimportosfromfastapiimportFastAPI,APIRouter,Depends,HTTPExceptionfromfastapi.responsesimportFileResponsefrom.importauth,database,plotterapp=FastAPI()api_router=APIRouter()#初始化数据库database.init_db()#WebSocket服务器asyncdefsensor_server(websocket,path):try:#第一步:接收密钥进行验证token=awaitwebsocket.recv()iftoken!=auth.SECRET_KEY:awaitwebsocket.close(code=1008,reason="Authenticationfailed")return#第二步:接收数据asyncformessageinwebsocket:try:data=json.loads(message)#数据格式:{"TagId":1,"TagName":"1#注浆A液压力","Value":0.76,"DataTime":"2025-6-182:28"}database.insert_data(tag_id=data['TagId'],tag_name=data['TagName'],value=data['Value'],data_time=data['DataTime'])print(f"Inserteddatafortag{data['TagId']}")exceptExceptionase:print(f"Errorprocessingmessage:{e}")exceptwebsockets.exceptions.ConnectionClosedase:print(f"Clientdisconnected:{e}")#API路由@api_router.get("/data/latest/{tag_id}")asyncdefget_latest_data(tag_id:int,_:str=Depends(auth.verify_token)):data=database.get_latest_data(tag_id)ifnotdata:raiseHTTPException(status_code=404,detail="Datanotfound")returndata[0]#返回最新一条@api_router.get("/data/history/{tag_id}")asyncdefget_history_data(tag_id:int,hours:int=24,_:str=Depends(auth.verify_token)):data=database.get_historical_data(tag_id,hours)ifnotdata:raiseHTTPException(status_code=404,detail="Datanotfound")returndata@api_router.get("/plot/{tag_id}")asyncdefget_plot(tag_id:int,hours:int=24,_:str=Depends(auth.verify_token)):plot_path=plotter.generate_plot(tag_id,hours)ifplot_pathisNone:raiseHTTPException(status_code=404,detail="Nodataavailableforplotting")returnFileResponse(plot_path)app.include_router(api_router,prefix="/api")#启动WebSocket服务器和FastAPI服务asyncdefstart_servers():#启动WebSocket服务器ws_server=awaitwebsockets.serve(sensor_server,"0.0.0.0",8765)print("WebSocketserverstartedonport8765")#注意:FastAPI服务需要单独启动,这里我们使用uvicorn,在Dockerfile中启动if__name__=="__main__":asyncio.run(start_servers())###3.Docker配置####3.1Dockerfile````dockerfileFROMpython:3.9-slimWORKDIR/app#安装依赖RUNapt-getupdate&&apt-getinstall-ylibsqlite3-devCOPYrequirements.txt.RUNpipinstall--no-cache-dir-rrequirements.txtCOPY..#设置环境变量(密钥通过环境变量传入)ENVSECRET_KEY=your_actual_key_here#启动命令:同时启动WebSocket服务器和FastAPI服务CMD["sh","-c","uvicornapp.main:app--host0.0.0.0--port8000&python-mapp.main"]```####3.2requirements.txt````fastapiuvicornwebsocketssqlalchemymatplotlibpython-multipart###4.在飞牛NAS上部署1.**构建镜像**:将整个`ws-server`目录上传到飞牛NAS,然后进入该目录执行:bashdockerbuild-tws-sensor-server.2.**运行容器**:bashdockerrun-d--namesensor-server-p8765:8765#WebSocket端口-p8000:8000#API端口-v/path/on/nas/data:/app/data#挂载数据库和绘图目录-eSECRET_KEY=“your_actual_key_here”\ws-sensor-server3.**飞牛NAS图形界面操作**:-打开飞牛NAS的Docker管理界面-选择“镜像”并导入构建好的`ws-sensor-server`镜像-创建容器,配置端口映射:8765和8000-设置环境变量:`SECRET_KEY=your_actual_key_here`-挂载数据卷:将NAS上的目录挂载到容器内的`/app/data`-启动容器###5.功能测试####5.1WebSocket数据发送pythonimportasyncioimportwebsocketsimportjsonasyncdefsend_data():asyncwithwebsockets.connect(‘ws://your_nas_ip:8765’)asws:#先发送密钥awaitws.send(“your_actual_key_here”)#发送数据data={“TagId”:1,“TagName”:“1#注浆A液压力”,“Value”:0.76,“DataTime”:“2025-06-1802:28:00”}awaitws.send(json.dumps(data))print(“Datasent”)asyncio.run(send_data())```####5.2API接口调用-获取最新数据:`GEThttp://your_nas_ip:8000/api/data/latest/1`需在Header中添加:`Authorization:Beareryour_actual_key_here`-获取历史数据:`GEThttp://your_nas_ip:8000/api/data/history/1?hours=24`-获取曲线图:`GEThttp://your_nas_ip:8000/api/plot/1?hours=72`###6.注意事项1.密钥管理:在实际部署中,应通过环境变量设置密钥,避免硬编码。2.数据持久化:通过挂载卷将数据库文件和绘图目录保存在NAS上,避免容器重启数据丢失。3.性能优化:对于高频数据,考虑使用批量插入和数据库索引优化。4.安全性:建议在生产环境中使用HTTPS加密API和WebSocket通信(可通过Nginx反向代理实现)。§§相关问题§§1.如何为SQLite数据库添加索引以提高查询性能?2.如何在Docker容器中安全地管理密钥?3.如何通过Nginx为WebSocket和API服务添加HTTPS加密?4.如何扩展此系统以支持多客户端并发连接?5.如何实现数据存储的分区或分表以优化大量历史数据的查询?
最新发布
06-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值