牟曉東
在計(jì)算機(jī)編程中,“線程”(thread)指的是一組可以在程序中獨(dú)立執(zhí)行的指令集合,它是代碼執(zhí)行的最小單位。如果程序在運(yùn)行過(guò)程中只有一個(gè)線程的話,那么下一個(gè)任務(wù)必須要等到上一個(gè)任務(wù)結(jié)束后才能進(jìn)行,這是一種低效的“串行”流程;引入“多線程”運(yùn)行機(jī)制后,就可以在主線程執(zhí)行任務(wù)的同時(shí)“并行”執(zhí)行其他的任務(wù),幾乎不需要等待時(shí)間,從而極大提高程序的運(yùn)行效率。值得一提的是,“多線程”與程序調(diào)用函數(shù)并不相同,因?yàn)楹瘮?shù)的調(diào)用是“阻塞”執(zhí)行方式——必須等函數(shù)正常執(zhí)行結(jié)束后才會(huì)繼續(xù)執(zhí)行后面的程序代碼(否則會(huì)一直處于等待中)。在此分別以Python代碼編程和樹(shù)莓派、掌控板圖形化編程為例,演示“多線程”編程的應(yīng)用。
在Spyder編輯器中新建“Python多線程畫(huà)圖.py”文件,首先通過(guò)“import turtle”和“from threading import Thread”將海龜庫(kù)模塊和線程庫(kù)模塊導(dǎo)入;接著,設(shè)置好“畫(huà)布”的大小為800×600像素:“turtle.screensize(800,600)”,并且通過(guò)turtle模塊的Turtle()函數(shù)建立t1-t4四個(gè)Turtle對(duì)象;然后自定義四個(gè)畫(huà)圖函數(shù),以my_draw1(a)為例:
傳入的參數(shù)a控制循環(huán)的次序:“for i in range(a):”,循環(huán)體只包括“t1.forward(2)”和“t1.left(1)”兩行代碼,作用是控制t1(Turtle對(duì)象)向前走兩個(gè)像素再向左轉(zhuǎn)1度。比如調(diào)用該函數(shù)時(shí)傳入的參數(shù)a值為180,則會(huì)畫(huà)一個(gè)180度的“半圓”(畫(huà)筆顏色默認(rèn)為黑色)。其余的my_draw2(b)、my_draw3(c)和my_draw4(d)三個(gè)函數(shù)基本類似,只是多了一行“t2.pencolor(‘red)”設(shè)置畫(huà)筆顏色的代碼。
接下來(lái),同時(shí)開(kāi)啟四個(gè)線程,調(diào)用目標(biāo)分別是四個(gè)Turtle對(duì)象的畫(huà)圖函數(shù),傳入的參數(shù)均為360(畫(huà)360度的空心圓):“Thread(target=my_draw1,args=(360,)).start()”、“Thread(target=my_draw2,args=(360,)).start()”、“Thread(target=my_draw3,args=(360,)).start()”和“Thread(target=my_draw4,args=(360,)).start()”,特別要注意args后的參數(shù)必須是元組形式“(360,)”(逗號(hào)不能省略);最后,添加“turtle.mainloop()”無(wú)限循環(huán)方式處理事件語(yǔ)句。
程序代碼保存后運(yùn)行測(cè)試,在彈出的“Python Turtle Graphics”窗口中出現(xiàn)了四個(gè)小箭頭,同時(shí)分別朝著設(shè)定的方向運(yùn)動(dòng),最終畫(huà)出了四個(gè)顏色相異的“內(nèi)切”和“外切”圓(如圖1)。
首先,將一條可編程ws281x燈帶通過(guò)古德微擴(kuò)展板的18號(hào)引腳與樹(shù)莓派連接;接著,登錄古德微機(jī)器人網(wǎng)站進(jìn)入“積木”編程區(qū)進(jìn)行圖形化編程。
在主程序中對(duì)燈帶先進(jìn)行初始化,然后順序添加三個(gè)子線程,名稱為“燈帶紅色”、“燈帶綠色”和“燈帶藍(lán)色”,分別對(duì)應(yīng)三個(gè)同名的函數(shù);每個(gè)函數(shù)均控制整條燈帶60個(gè)燈珠的三分之一部分,其中的“燈帶紅色”函數(shù)的功能是將1-20號(hào)燈珠先設(shè)置為發(fā)紅光,0.1秒鐘后再熄滅并持續(xù)0.1秒鐘;“燈帶綠色”函數(shù)的功能是將21-40號(hào)燈珠先設(shè)置為發(fā)綠光,0.2秒鐘后再熄滅并持續(xù)0.2秒鐘;而“燈帶藍(lán)色”函數(shù)的功能則是將剩下的41-60號(hào)燈珠先設(shè)置為發(fā)藍(lán)光,0.4秒鐘后再熄滅并持續(xù)0.4秒鐘(如圖2)。
如果主程序不是采用“多線程”而是函數(shù)的直接調(diào)用方式,其運(yùn)行效果就會(huì)是在1-20號(hào)燈珠閃爍紅光的0.2秒鐘(兩個(gè)0.1秒鐘)周期內(nèi),其余的40個(gè)燈珠是全熄滅狀態(tài);接下來(lái),在21-40號(hào)燈珠閃爍綠光的0.4秒鐘周期內(nèi),前20個(gè)和后20個(gè)燈珠同樣是全熄滅狀態(tài);最后,在41-60號(hào)燈珠閃爍藍(lán)光的0.8秒鐘周期內(nèi),前40個(gè)燈珠也是處于全熄滅狀態(tài)的。
使用“多線程”編程的話,會(huì)有什么樣的展示效果呢?將程序保存后再點(diǎn)擊“運(yùn)行”按鈕,出現(xiàn)了三組燈珠互不干擾地以各自的頻率進(jìn)行不同步閃爍的效果,而不是函數(shù)式的阻塞等待的執(zhí)行方式。
運(yùn)行Mind+,先點(diǎn)擊左下角的“擴(kuò)展”按鈕,將掌控板和“功能模塊”中的“多線程”添加至主界面;返回后,在“ESP32主程序”下的“循環(huán)執(zhí)行”結(jié)構(gòu)中添加啟動(dòng)三個(gè)子線程,其中的“子線程1”只有一行“播放音樂(lè)”語(yǔ)句,并且其重復(fù)模式為“無(wú)限循環(huán)”,音樂(lè)可自行選擇Mind+內(nèi)置的曲目(比如PRELUDE);“子線程2”實(shí)現(xiàn)的功能是控制OLED顯示屏根據(jù)聲音傳感器的檢測(cè)數(shù)據(jù)實(shí)時(shí)輸出多個(gè)柱狀音量的動(dòng)態(tài)波形圖,包括設(shè)置線條的寬度和兩個(gè)坐標(biāo)軸數(shù)據(jù)的繪制,特別需要注意的是,將“讀取麥克風(fēng)聲音強(qiáng)度”數(shù)據(jù)進(jìn)行映射運(yùn)算——從0-4095映射為從50-0,并且需要添加“清屏”語(yǔ)句模塊(“屏幕顯示為‘全黑”);“子線程3”實(shí)現(xiàn)的功能是控制三支LED燈進(jìn)行有規(guī)律的變色閃爍,發(fā)光顏色的隨機(jī)變化是由三個(gè)“在0和255之間取隨機(jī)數(shù)”的RGB值來(lái)動(dòng)態(tài)實(shí)現(xiàn)的,兩個(gè)“等待0.2秒”的語(yǔ)句模擬控制的是LED燈的閃爍頻率,可根據(jù)所選曲目節(jié)奏的快慢來(lái)多次調(diào)試。
將程序保存為“多線程節(jié)拍.sb3”,然后連接好掌控板,再點(diǎn)擊“上傳到設(shè)備”按鈕進(jìn)行程序的測(cè)試。當(dāng)左下角出現(xiàn)“上傳成功”的提示后,掌控板開(kāi)始有了“反應(yīng)”:蜂鳴器循環(huán)播放程序設(shè)置好的音樂(lè)曲目,同時(shí)三支LED燈在不斷變換顏色地閃爍,而且在OLED顯示屏上有柱狀音量動(dòng)態(tài)波形圖在不停地隨音樂(lè)的音量大小而跳動(dòng)(如圖3)。