H5 worker 系列四 flv.js原始碼之log
在H5 worker 系列三 webworkify處理音視訊解碼中提到了 flv.js使用webworkify進行音視訊解碼,那麼在解碼的worker執行緒中,如果想使用公共的Log模組,想把log日誌整合到主執行緒中,要怎麼做呢?
一、worker執行緒和主執行緒關於log的資料
//logger.js
Log.GLOBAL_TAG = 'flv.js';
Log.FORCE_GLOBAL_TAG = false;
Log.ENABLE_ERROR = true;
Log.ENABLE_INFO = true;
Log.ENABLE_WARN = true;
Log.ENABLE_DEBUG = true;
Log.ENABLE_VERBOSE = true;
Log.ENABLE_CALLBACK = false;
Log.emitter = new EventEmitter();
log.js裡有很多開關,當改變這些開關值時,需要使用logging_control.js中的一些get/set方法,比如:
//logging-control.js
static get enableWarn() {
return Log.ENABLE_WARN;
}
static set enableWarn(enable) {
Log.ENABLE_WARN = enable;
LoggingControl._notifyChange();
}
static _notifyChange() {
let emitter = LoggingControl.emitter;
if (emitter.listenerCount('change') > 0) {
let config = LoggingControl.getConfig();
emitter.emit('change', config);
}
}
static registerListener(listener) {
LoggingControl.emitter.addListener('change', listener);
}
static removeListener(listener) {
LoggingControl.emitter.removeListener('change', listener);
}
這裡使用了EventEmitter丟擲change事件,來通知一下registerListener中註冊的handler。關於EventEmitter基礎知識,可以參考Node.js EventEmitter
搜尋了下,使用registerListener註冊的,也只有transmuxer.js使用webworker的時候。關於webworker基礎知識,參考H5 worker 系列三 webworkify處理音視訊解碼
this.e = {
onLoggingConfigChanged: this._onLoggingConfigChanged.bind(this)
};
LoggingControl.registerListener(this.e.onLoggingConfigChanged);
_onLoggingConfigChanged(config) {
if (this._worker) {
this._worker.postMessage({cmd: 'logging_config', param: config});
}
}
然後看看worker裡怎麼處理的:
case 'logging_config': {
let config = e.data.param;
LoggingControl.applyConfig(config);
if (config.enableCallback === true) {
LoggingControl.addLogListener(logcatListener);
} else {
LoggingControl.removeLogListener(logcatListener);
}
break;
applyConfig是啥
static getConfig() {
return {
globalTag: Log.GLOBAL_TAG,
forceGlobalTag: Log.FORCE_GLOBAL_TAG,
enableVerbose: Log.ENABLE_VERBOSE,
enableDebug: Log.ENABLE_DEBUG,
enableInfo: Log.ENABLE_INFO,
enableWarn: Log.ENABLE_WARN,
enableError: Log.ENABLE_ERROR,
enableCallback: Log.ENABLE_CALLBACK
};
}
static applyConfig(config) {
Log.GLOBAL_TAG = config.globalTag;
Log.FORCE_GLOBAL_TAG = config.forceGlobalTag;
Log.ENABLE_VERBOSE = config.enableVerbose;
Log.ENABLE_DEBUG = config.enableDebug;
Log.ENABLE_INFO = config.enableInfo;
Log.ENABLE_WARN = config.enableWarn;
Log.ENABLE_ERROR = config.enableError;
Log.ENABLE_CALLBACK = config.enableCallback;
}
這裡就讓人費解了,在_notifyChange中丟擲的getConfig,繞到worker裡,又把配置用applyConfig寫回去了,莫非worker這個執行緒裡,和主執行緒讀取的不是一份資料?必須通過postMessage這種方式同步麼??
二、測試一下,確實不是一份資料
1.先改一下transmuxing-worker.js偵聽message方法,無論收到啥訊息,在最後console一下
self.addEventListener('message', function (e) {
switch (e.data.cmd) {
...
}
console.log('test' + Log.FORCE_GLOBAL_TAG + ',event:' + e.data.cmd);
這樣執行一下,forceGlobalTag預設值是false,沒問題
2.在transmuxer.js新增一個test方法
test() {
Log.FORCE_GLOBAL_TAG = true;
// LoggingControl.forceGlobalTag = true;
console.log('Log.FORCE_GLOBAL_TAG' + Log.FORCE_GLOBAL_TAG);
this._worker.postMessage({cmd: 'test'});
}
3.在flv-player.js中新增一個test方法
test() {
this._transmuxer.test();
}
4.在index.html中,改一下暫停按鈕的操作
function flv_pause() {
player.test();
}
5.現在結論出來了,直接改Log.FORCE_GLOBAL_TAG之後,在worker中讀取這個值,還是原先的值。而使用LoggingControl.forceGlobalTag觸發_notifyChange這種方式就可以。把LoggingControl.applyConfig(config);
註釋掉,也可以證實這一點。
三、ENABLE_CALLBACK屬性
1.普通模式
從logger.js中可以看出,ENABLE_CALLBACK預設值為false,如果為true的時候,在呼叫logger的幾個輸出方法時,會拋通知出來,比如
static v(tag, msg) {
if (!tag || Log.FORCE_GLOBAL_TAG)
tag = Log.GLOBAL_TAG;
let str = `[${tag}] > ${msg}`;
if (Log.ENABLE_CALLBACK) {
Log.emitter.emit('log', 'verbose', str);
}
...
這個事件在logging-control.js中有處理
static addLogListener(listener) {
Log.emitter.addListener('log', listener);
if (Log.emitter.listenerCount('log') > 0) {
Log.ENABLE_CALLBACK = true;
LoggingControl._notifyChange();
}
}
static removeLogListener(listener) {
Log.emitter.removeListener('log', listener);
if (Log.emitter.listenerCount('log') === 0) {
Log.ENABLE_CALLBACK = false;
LoggingControl._notifyChange();
}
}
使用addLogListener可以新增一個handler,這樣在Log.v等方法呼叫時,就會執行這個handler了。可以測試一下:
//transmuxer.js
addOneLogListener() {
LoggingControl.addLogListener(function (type, str) {
console.log('type:' + type + ',str:' + str);
});
}
mainThreadLogV() {
Log.v('mainThreadLogV', 'msg:');
}
先在html頁面上點選按鈕觸發addOneLogListener,然後點選按鈕觸發mainThreadLogV。一切工作正常!
2.worker模式
當我們執行addLogListener時,還會觸發_notifyChange,根據上面的分析,會觸發worker裡的邏輯:
case 'logging_config': {
let config = e.data.param;
LoggingControl.applyConfig(config);
if (config.enableCallback === true) {
LoggingControl.addLogListener(logcatListener);
} else {
LoggingControl.removeLogListener(logcatListener);
}
break;
這裡在worker裡,又來了一遍addLogListener或removeLogListener,難道與applyConfig一樣,worker中不但資料不同步。剛才新增的logHandler(其實就是console.log('type:' + type + ',str:' + str);
這個function)也不同步麼?做個試驗,先把LoggingControl.addLogListener(logcatListener);
註釋掉,然後在worker中新增一個message的響應:
//transmuxing-worker.js
case 'wokerLogV':
Log.v('wokerLogV', 'msg:');
break;
然後在主執行緒中,去讓worker執行Log.v
//transmuxer.js
wokerLogV() {
this._worker.postMessage({cmd: 'wokerLogV'});
}
再在html頁面上點選觸發wokerLogV,果然不出所料,自己新增的logHandler沒有執行。所以logcatListener怎麼處理的呢?參考上面的config處理,是用logging_config把資料發射出去,然後在worker執行緒中用applyConfig這種方式重寫了一遍新資料。但是logHandler就不方便發射了,只能用worker執行緒把這兩個引數轉發回去。也就是說,不管在main執行緒上使用addLogListener新增了多少個logHandler,在worker執行緒中始終只新增了一個logcatListener,這個執行緒就是負責轉發引數的,發回到main執行緒中,才是真實的logHandler去響應。
let logcatListener = onLogcatCallback.bind(this);
function onLogcatCallback(type, str) {
self.postMessage({
msg: 'logcat_callback',
data: {
type: type,
logcat: str
}
});
}
主執行緒:
case 'logcat_callback':
Log.emitter.emit('log', data.type, data.logcat);
break;
四、總結
可能我一開始就忽略了一些事情,在this._worker = work(TransmuxingWorker);
時,生成的transmuxing-worker.js就完全import了另外的副本:
import Log from '../utils/logger.js';
import LoggingControl from '../utils/logging-control.js';
import Polyfill from '../utils/polyfill.js';
import TransmuxingController from './transmuxing-controller.js';
import TransmuxingEvents from './transmuxing-events.js';
/* post message to worker:
data: {
cmd: string
param: any
}
receive message from worker:
data: {
msg: string,
data: any
}
*/
let TransmuxingWorker = function (self) {
...
雖然transmuxer.js中import的是同樣的logger.js,並且裡面有logger.js很多static公共常量
import EventEmitter from 'events';
import Log from '../utils/logger.js';
import LoggingControl from '../utils/logging-control.js';
import TransmuxingController from './transmuxing-controller.js';
import TransmuxingEvents from './transmuxing-events.js';
import TransmuxingWorker from './transmuxing-worker.js';
import MediaInfo from './media-info.js';
class Transmuxer {
...
說得有點囉嗦,但也印證了,兩個執行緒無法共享資料,必須通過postMessage來互動,並且互動傳遞的只是字串化的副本,也就是傳統上講的深複製。
相關文章
- Giraph原始碼分析(四)—— Master 如何檢查Worker啟動原始碼AST
- Giraph原始碼分析(四)—— Master 如何檢查Worker啟動成功原始碼AST
- Spring Security系列之核心過濾器原始碼分析(四)Spring過濾器原始碼
- Spring Cloud系列(四):Eureka原始碼解析之客戶端SpringCloud原始碼客戶端
- springboot原始碼解析-管中窺豹系列之Initializer(四)Spring Boot原始碼
- Vue原始碼分析系列四:Virtual DOMVue原始碼
- 【spring原始碼系列】之【BeanDefinition】Spring原始碼Bean
- Spring Boot系列(四):Spring Boot原始碼解析Spring Boot原始碼
- [原始碼解析] 並行分散式框架 Celery 之 worker 啟動 (2)原始碼並行分散式框架
- [原始碼解析] 並行分散式框架 Celery 之 worker 啟動 (1)原始碼並行分散式框架
- JDK原始碼解析系列之objectJDK原始碼Object
- 【spring原始碼系列】之【xml解析】Spring原始碼XML
- Spring原始碼系列:依賴注入(四)-總結Spring原始碼依賴注入
- 人人都能懂的Vue原始碼系列(四)—mergeOptionsVue原始碼
- OkHttp 原始碼剖析系列(四)——連線建立概述HTTP原始碼
- Java 集合系列之 LinkedList原始碼分析Java原始碼
- angular原始碼剖析之Provider系列--QProviderAngular原始碼IDE
- angular原始碼剖析之Provider系列--CacheFactoryProviderAngular原始碼IDE
- [Vue CLI 3] 原始碼系列之useTaobaoRegistryVue原始碼
- Redis原始碼系列之rename講解Redis原始碼
- 深入理解MySQL系列之redo log、undo log和binlogMySql
- redux原始碼分析之四:compose函式Redux原始碼函式
- Spring原始碼之Bean的載入(四)Spring原始碼Bean
- Java併發之ReentrantLock原始碼解析(四)JavaReentrantLock原始碼
- vue-loader 原始碼解析系列之 selectorVue原始碼
- JDK原始碼解析系列之String 之一JDK原始碼
- 【spring原始碼系列】之【Bean的銷燬】Spring原始碼Bean
- RxJava 原始碼分析系列(四) -操作符變換原理RxJava原始碼
- 容器類原始碼解析系列(四)---SparseArray分析(最新版)原始碼
- Linux核心原始碼分析之setup_arch (四)Linux原始碼
- Giraph原始碼分析(二)—啟動Master/Worker服務原始碼AST
- vue原始碼分析系列之入口檔案分析Vue原始碼
- D3原始碼解讀系列之Chord原始碼
- D3原始碼解讀系列之Dispatches原始碼
- D3原始碼解讀系列之Force原始碼
- D3原始碼解讀系列之Hierarchies原始碼
- D3原始碼解讀系列之Path原始碼
- D3原始碼解讀系列之Quadtrees原始碼