tensorflow-TFRecord 文件詳解

TFRecord 是 tensorflow 內置的文件格式,它是一種二進制文件,具有以下優點:

1. 統一各種輸入文件的操作

2. 更好的利用內存,方便複製和移動

3. 將二進制數據和標籤(label)存儲在同一個文件中

 

引言

在了解如下操作後進一步詳細講解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每個元素轉換成 key-value 形式,

注意,輸入必須是 list,且 list 中元素類型要相同,且與 Int 保持一致;

# value = tf.constant([1, 2])     ### 這會報錯的
ss = 1               ### Int64List 對應的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同類型的 方法還有 2 個

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 構建 一種類型的特徵集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他類型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 構建 多種類型 的特徵集,可以 dict 格式表達 多種類型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是創建一個 樣本,Example 對應一個樣本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅圖總結一下上面的代碼

 

Example 協議塊

它其實是一種 數據存儲的 格式,類似於 xml、json 等;

用上述方法實現該格式;

一個 Example 協議塊對應一個樣本,一個樣本有多種特徵,每種特徵下有多個元素,可參看上圖;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example協議塊 格式 存儲的;

 

TFRecord 文件

該類文件具有寫功能,且可以把其他類型的文件轉換成該類型文件,其實相當於先讀取其他文件,再寫入 TFRecord 文件;

該類文件也具有讀功能;

 

TFRecord 存儲

存儲分兩步:

1.建立存儲器 

2. 構造每個樣本的 Example 協議塊

 

tf.python_io.TFRecordWriter(file_name)

構造存儲器,存儲器有兩個常用方法

  • write(record):向文件中寫入一個樣本
  • close():關閉存儲器

注意:此處的 record 為一個序列化的 Example,通過 Example.SerializeToString()來實現,它的作用是將 Example 中的 map 壓縮為二進制,節約大量空間

 

示例代碼1:將 MNIST 數據集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整數型的屬性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串類型的屬性,也就是圖像的內容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 讀取圖像數據 和一些屬性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 寫入器

for index in range(num_examples):
    ### 遍歷樣本
    image_raw = images[index].tostring()        ### 圖片轉成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 寫入 TFRecord
writer.close()

 

示例代碼2:將 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:將 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 讀取

參考鏈接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立讀取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析單個 Example 協議塊

  • serialized : 標量字符串的Tensor,一個序列化的Example,文件經過文件閱讀器之後的value
  • features :字典數據,key為讀取的名字,value為FixedLenFeature
  • return : 一個鍵值對組成的字典,鍵為讀取的名字

features中的value還可以為tf.VarLenFeature(),但是這種方式用的比較少,它返回的是SparseTensor數據,這是一種只存儲非零部分的數據格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 輸入數據的形狀,一般不指定,為空列表
  • dtype : 輸入數據類型,與存儲進文件的類型要一致,類型只能是float32,int 64, string
  • return : 返回一個定長的 Tensor (即使有零的部分也存儲)

 

示例代碼

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必須和 寫入時 一致,數據類型也必須一致,shape 可為 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 將 string、bytes 轉換成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

參考資料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

,

TFRecord 是 tensorflow 內置的文件格式,它是一種二進制文件,具有以下優點:

1. 統一各種輸入文件的操作

2. 更好的利用內存,方便複製和移動

3. 將二進制數據和標籤(label)存儲在同一個文件中

 

引言

在了解如下操作後進一步詳細講解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每個元素轉換成 key-value 形式,

注意,輸入必須是 list,且 list 中元素類型要相同,且與 Int 保持一致;

# value = tf.constant([1, 2])     ### 這會報錯的
ss = 1               ### Int64List 對應的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同類型的 方法還有 2 個

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 構建 一種類型的特徵集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他類型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 構建 多種類型 的特徵集,可以 dict 格式表達 多種類型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是創建一個 樣本,Example 對應一個樣本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅圖總結一下上面的代碼

 

Example 協議塊

它其實是一種 數據存儲的 格式,類似於 xml、json 等;

用上述方法實現該格式;

一個 Example 協議塊對應一個樣本,一個樣本有多種特徵,每種特徵下有多個元素,可參看上圖;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example協議塊 格式 存儲的;

 

TFRecord 文件

該類文件具有寫功能,且可以把其他類型的文件轉換成該類型文件,其實相當於先讀取其他文件,再寫入 TFRecord 文件;

該類文件也具有讀功能;

 

TFRecord 存儲

存儲分兩步:

1.建立存儲器 

2. 構造每個樣本的 Example 協議塊

 

tf.python_io.TFRecordWriter(file_name)

構造存儲器,存儲器有兩個常用方法

  • write(record):向文件中寫入一個樣本
  • close():關閉存儲器

注意:此處的 record 為一個序列化的 Example,通過 Example.SerializeToString()來實現,它的作用是將 Example 中的 map 壓縮為二進制,節約大量空間

 

示例代碼1:將 MNIST 數據集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整數型的屬性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串類型的屬性,也就是圖像的內容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 讀取圖像數據 和一些屬性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 寫入器

for index in range(num_examples):
    ### 遍歷樣本
    image_raw = images[index].tostring()        ### 圖片轉成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 寫入 TFRecord
writer.close()

 

示例代碼2:將 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:將 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 讀取

參考鏈接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立讀取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析單個 Example 協議塊

  • serialized : 標量字符串的Tensor,一個序列化的Example,文件經過文件閱讀器之後的value
  • features :字典數據,key為讀取的名字,value為FixedLenFeature
  • return : 一個鍵值對組成的字典,鍵為讀取的名字

features中的value還可以為tf.VarLenFeature(),但是這種方式用的比較少,它返回的是SparseTensor數據,這是一種只存儲非零部分的數據格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 輸入數據的形狀,一般不指定,為空列表
  • dtype : 輸入數據類型,與存儲進文件的類型要一致,類型只能是float32,int 64, string
  • return : 返回一個定長的 Tensor (即使有零的部分也存儲)

 

示例代碼

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必須和 寫入時 一致,數據類型也必須一致,shape 可為 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 將 string、bytes 轉換成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

參考資料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

聚甘新

為什麼Web開發人員在2020年不用最新的CSS功能

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

原文出處:https://dzone.com/articles/why-masses-are-not-using-latest-css-features-in-20

 

儘管CSS每年都會發布全新的特性,但實際上這些新功能很少會被web開發人員實際在生產項目中使用到,甚至去了解它們的動力也不會比去多完成幾個需求更多。那究竟是什麼原因導致的呢? 

1.使用最新特性不是優先事項

在一個新項目的初期階段,它用到的可能只是幾條CSS規則,但隨着項目的持續更新和迭代,項目中使用到的規則就會變得越來越複雜,CSS也會越來越複雜尺寸也會隨之不斷膨脹。因此,作為項目優化的第一要務,作為資源的CSS需要盡可能的精簡和減少複雜度,第一是為了便於更好地理解和維護,第二也是為了加載更為高效。那麼,更實用且可投入生產環境的一些CSS特性會被高頻使用,其他的特性則會被暫時擱置一旁。

並且,在一般情況下,樣式和品牌在一段時間內都會相對固定,完成任務的需求要比使用最新CSS的特性要更緊迫。 

 

 

(圖片來源於網絡)

 

2.預算限制

預算成本是影響了所有項目的主要因素。它在開發階段會高度影響事項的優先級。集成新的CSS功能需要時間,而開發團隊來說,增加的這部分時間成本會影響到項目的整體進度。因此,開發進度會重視在優化其他功能(而不是CSS功能)時花費的時間成本。

另外,引入了最新的CSS特性,還可能會使開發團隊把一部分精力放在解決瀏覽器兼容性問題上。這點和JavaScript不同,JavaScript有Babel來完成編譯,而CSS沒有提供類似功能。

 

 

 (圖片來源於網絡)

 

3.社區發展還未跟上

JavaScript每隔一段時間舉行一次會議。同樣,Vue和React也會為了幫助開發人員跟上社區的步伐而定期舉行會議。但是,對於CSS而言,它們根本沒有這樣的活動!因此,開發人員很難掌握其功能和路線圖。他們應該如何保持對新功能發展趨勢的了解? 沒有版本發布說明,也沒有定期的發布會,這根本不能點燃社區用戶的學習激情。 

對普通用戶而言,既然舊的技術已經滿足了需求,那麼又何必那麼麻煩閱讀文檔學習新的功能呢?

和框架和其他編程語言不同,CSS沒有針對安全問題的補丁程序。他只是一套標準,反正大多數客戶只需要關心網站看起來UI差不多就行了。

4.很難提升簡歷的含金量

即使你在掌握CSS方面付出了很多的努力,對CSS的新特性也了如指掌,但你也很難向你的客戶或老闆證明這一點,因為類似像這樣“熟練掌握CSS3以外的CSS特性”對他人而講是沒有意義的,因為它不是CSS3。在CSS開發領域,CSS3的出現是很有意義的,因為它完成了前端領域的統一:

  • Web開發人員提升了技能
  • 加速瀏覽器廠商統一支持了全新的CSS標準
  • 企業的技術棧更新

巨大的需求帶來了巨大的機會。除了大量的書籍、課程和視頻來幫助人們了解CSS3外,還催生了全新的布局模型,如Flexbox和Grid,儘管它們不是CSS3的一部分。

但這裏我們指的是CSS3外的特性,它們本身除了認可程度很低外,對開發團隊來講也是個相對不熟悉的東西,因此,開發團隊很難會把時間花在對市場沒有意義的事情上,客戶也不會關心你到底用不用新的技術。

5.缺乏時間

編寫CSS的主要目的是使你的網站的表現內容的形式更美觀及易於理解。CSS通過控制兩類事物來幫助開發人員去實現這個目標:布局和設計。布局(Layout)負責元素列和行排布,而設計(design)指顏色、字體、間距、動畫和邊框等基礎外觀。

但目前,舊的特性已經能處理的很好了,為什麼要花更多時間去使用新特性去替代已經很好的形式呢?

總結

CSS發布周期沒有固定的周期和計劃,導致一切都來的很突然 ,另外舊的CSS特性已經能很好的完成日常工作了,這讓很多Web開發人員沒有特別的動力去升級它們。

另外,新的特性知名度也不高,對最終用戶的吸引力也不足,很難從需求層面驅動使用。所以這就是為什麼都2020年了,CSS的新特性仍然使用的人較少的原因。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司“嚨底家”!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

Python進階——詳解元類,metaclass的原理和用法

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是Python專題第18篇文章,我們來繼續聊聊Python當中的元類。

在上上篇文章當中我們介紹了type元類的用法,在上一篇文章當中我們介紹了__new__函數與__init__函數的區別,以及它在一些設計模式當中的運用。這篇文章我們來看看metacalss與元類,以及__new__函數在元類當中的使用。

上一篇文章非常重要,是這一篇的基礎,如果錯過了上篇文章,推薦回顧一下:

Python面試常見問題,__init__是構造函數嗎?

metaclass

metaclass的英文直譯過來就是元類,這既是一個概念也可以認為是Python當中的一個關鍵字,不管怎麼理解,對它的內核含義並沒有什麼影響。我們可以不必糾結,就認為它是類的類的意思即可。在這個用法當中,支持我們自己定義一個類,使得它是後面某一個類的元類。

之前使用type動態創建類的時候,我們傳入了類名,和父類的tuple以及屬性的dict。在metaclass用法當中,其實核心相差不大,只是表現形式有所區別。我們來看一個例子即可:

class AddInfo(type):
    def __new__(cls, name, bases, attr):
        attr['info'] = 'add by metaclass'
        return super().__new__(cls, name, bases, attr)
        
        
class Test(metaclass=AddInfo):
    pass

在這個例子當中,我們首先創建了一個類叫做AddInfo,這是我們定義的一個元類。由於我們希望通過它來實現元類的功能,所以我們需要它繼承type類。我們在之前的文章當中說過,在Python面向對象當中,所有的類的根本來源就是type。也就是說Python當中的每一個類都是type的實例。

我們在這個類當中重載了__new__方法,我們在__new__方法當中傳入了四個參數。眼尖一點的小夥伴一定已經看出來了,這個函數的四個參數,正是我們調用type創建類的時候傳入的參數。其實我們調用type的方法來創建類的時候,就是調用的__new__這個函數完成的,這兩種寫法對應的邏輯是完全一樣的。

我們之後又創建了一個新的類叫做Test,這個當中沒有任何邏輯,直接pass。但是我們在創建類的時候指定了一個參數metaclass=AddInfo,這裏這個參數其實就是指定的這個類的元類,也就是指定這個類的創建邏輯。雖然我們用代碼寫了類的定義,但是在實際執行的時候,這個類是以metaclass為元類創建的。

根據上面的邏輯,我們可以知道,Test類在創建的時候就被賦予了類屬性info。我們可以驗證一下:

拓展類功能

上面這段就是元類的基本用法了,其實本質上和我們之前介紹的type的動態類創建是一樣的,只不過展現的形式不同。那麼我們就有一個問題要問了,我們使用元類究竟能夠做什麼呢?

這裡有一個經典的例子,我們都知道Python原生的list是沒有’add’這個方法的。假設我們習慣了Java當中list的使用,習慣用add來為它添加元素。我們希望創建一個新的類,在這個新的類當中,我們可以通過add來添加函數。通過元類可以很方便地使用這一點。

class ListMeta(type):
    def __new__(cls, name, bases, attrs):
        # 在類屬性當中添加了add函數
        # 通過匿名函數映射到append函數上
        attrs['add'] = lambda self, value: self.append(value)
        return super().__new__(cls, name, bases, attrs)
    
    
class MyList(list, metaclass=ListMeta):
    pass

我們首先是定義了一個叫做ListMeta的元類,在這個元類當中我們給類添加了一個屬性叫做add。它只是包裝了一下而已,底層是通過append方法實現的。我們來實驗一下:

從結果來看也沒什麼問題,我們成功通過調用add方法往list當中插入了元素。這裏藏着一個小細節,我們在ListMeta當中為attrs添加了一個名叫’add’的屬性。這個屬性是添加給類的,而不是類初始化出來的實例的。所以如果我們print出MyList這個類當中的所有屬性,也能看到add的存在。

如果我們直接去通過MyList去訪問add方法的話會引起報錯,因為我們實現add這個方法邏輯的匿名函數限制了需要傳入兩個參數。第一個參數是實例的對象self,第二個參數才是添加的元素value。如果我們通過MyList的類屬性去訪問它的話會觸發一個錯誤,因為缺少了一個參數。因為類當中的屬性實例也是可以調用的,並且Python會在參數前面自動添加self這個參數,就剛好滿足了要求。

搞明白了這些我們只是解決了可能性問題,我們明白了元類可以實現這樣的操作,但沒有解決我們為什麼必須要使用元類呢?就拿剛才的例子來說,我們完全可以繼承list這個類,然後在其中再開發我們想要的方法,為什麼一定要使用元類呢?

就剛才這個場景來說,的確,我們是找不出任何理由的。完全沒有理由不使用繼承,而非要用元類。但是在有些場景和有些問題當中,我們必須要使用元類不可。就是涉及類屬性變更和類創建的時候,我們來看下面這個例子。

控制實例的創建

還記得我們上篇文章介紹的工廠設計模式的例子嗎?就是我們可以通過參數來得到不同類的實例。

我們創建了三種遊戲的類和一個工廠類,我們重載了工廠類的__new__函數。使得我們可以根據實例化時傳入的參數返回不同類型的實例。

class Last_of_us:
    def play(self):
        print('the Last Of Us is really funny')
        
        
class Uncharted:
    def play(self):
        print('the Uncharted is really funny')
        

class PSGame:
    def play(self):
        print('PS has many games')
        
        
class GameFactory:
    games = {'last_of_us': Last_of_us, 'uncharted': Uncharted}
    def __new__(cls, name):
        if name in cls.games:
            return cls.games[name]()
        else:
            return PSGame()
        

uncharted = GameFactory('uncharted')
last_of_us = GameFactory('last_of_us')

假設這個需求完成得很好順利上線了,但是運行了一段時間之後我們發現下游有的時候為了偷懶會不通過工廠類來創建實例,而是直接對需要的類做實例化。原本這沒有問題,但是現在產品想要在工廠類當中加上一些埋點,統計出訪問我們工廠的訪問量。所以我們需要限制這些遊戲類不能直接實例化,必須要通過工廠返回實例

那麼這個功能我們怎麼實現呢?

我們分析一下問題就會發現,這一次不是需要我們在創建實例的時候做動態的添加,而是直接限制一些類不允許直接調用進行創建。限制的方法比較常用的一種就是拋出異常,所以我們希望可以給這些類加上一個邏輯,實例化類的時候傳入一個參數,表明是否是通過工廠類進行的,如果不是,則拋出異常

這裏,我們需要用到另外一個默認函數,叫做__call__,它是允許將類實例當做函數調用。我們通過類名來實例化,其實也是一個調用邏輯。這個__call__的邏輯並不難寫,我們隨手就來:

def __call__(self, *args, **kwargs):
    if len(args) == 0 or args[0] != 'factory':
        raise TypeError("Can't instantiate directly")

但問題是這個__call__函數並不能直接加在類當中,因為它的應用範圍是實例,而不是類。而我們希望的是在創建實例的時候進行限制,而不是對調用實例的時候進行限制,所以這段邏輯只能通過元類實現

我們直接創建類的時候就會觸發異常,因為不是通過工廠創建的。我們這裏判斷是否是工廠創建的邏輯簡化掉了,只是通過一個簡單的字符串來進行的判斷,實際上會用一些更加複雜的邏輯,這不是本文的重點,我們了解即可。

整體運行的邏輯和我們設想的一樣,說明這樣實現是正確的。

總結

我們日常開發當中用到元類的情況非常罕見,一般都是在一些高端開發的場景當中。比如說開發一些框架或者是中間件,為了方便下游的使用,需要創建一些關於類屬性的動態邏輯,才會用到元類。對於普通開發者而言,如果你無法理解元類的含義以及應用,也沒有關係,使用頻率非常低。

另外,元類的概念和動態類、動態語言的概念有關,Python語言的動態特性很多正是通過這一點體現的。所以隨着我們對於Python動態特性理解的加深,理解元類也會變得越來越容易,同樣也會理解越來越深刻。如果我們把Python的元類和裝飾器做一個類比的話,會發現兩者的核心邏輯是很類似的。本質上都是在原有的邏輯之外封裝新的邏輯,只不過裝飾器針對的是一段邏輯,而元類針對的是類的屬性和創建過程。

仔細思考,我相信一定會有靈光乍現的感覺。

今天的文章就到這裏,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

.NET Framework、.NET Core 和 .NET 5+ 的產品生命周期

.NET Framework、.NET Core 和 .NET 5+ 的產品生命周期

本文整理記錄了 .NET Framework、.NET Core 和 .NET 各個版本的產品支持周期和操作系統兼容性。

早於 .NET Framework 2.0 和 .NET Core 2.1 的 .NET 版本以及 .NET Core 2.2、.NET Core 3.0 已經停止了任何形式的支持,因此本文不做討論。

相關文章:

  • .NET Framework、.NET Core 和 .NET 5+ 的產品生命周期(本文)
  • Visual Studio 的產品生命周期
  • DevExpress 各個版本與 .NET、Visual Studio 的版本兼容性

.NET 版本說明

  • .NET Framework 4.5 是可替換計算機上的 .NET Framework 4 的就地更新,同樣,.NET Framework 4.5.1、4.5.2、4.6、4.6.1、4.6.2、4.7、4.7.1、4.7.2 和 4.8 是對 .NET Framework 4.5 的就地更新。 就地更新意味着它們使用相同的運行時版本,但是程序集版本會更新,並且包括新類型和成員。 安裝其中一個更新后,.NET Framework 4、.NET Framework 4.5、.NET Framework 4.6 或 .NET Framework 4.7 應用應繼續運行,而無需重新編譯。 但是,反過來則不行。 建議不要在較早版本的 .NET Framework 上運行面向更高版本的 .NET Framework 的應用。 例如,建議在 .NET Framework 4.5 上運行面向 .NET Framework 4.6 的應用。(參見 版本 4.5 及更高版本的備註)

  • .NET Core 為一個全新的跨平台框架,支持 Windows、Linux 和 macOS 等多種平台,以 MIT 協議完全開源。.NET Core 最早於 2014 年公布,首個版本 .NET Core 1.0 發佈於 2016 年。隨着版本的演進,.NET Core 不斷加入 .NET Framework 原有的功能,直到 .NET Core 3.1,完成這一進程。剩餘少量 .NET Core 不支持的 .NET Framework 技術 不再向 .NET Core 移植。

  • .NET Core 3.1 之後,隨着技術移植進程的結束,.NET Core 的後續版本將作為 .NET Framework 和 .NET Core 的繼任者,移除 “Core” 字樣直接稱作 “.NET”,版本號自 “.NET 5” 開始。因此,.NET Framework 4.8 將會是 .NET Framework 的最後一個版本。

.NET 支持政策

.NET Core 和 .NET 的版本類型

本節整理自 .NET Core 支持政策。

.NET Core/.NET 分為長期支持 (LTS) 版本和最新 (Current) 版本。自 .NET Core 2.2 起,兩者的支持周期如下:

  • LTS (Long Term Support): 在初始發布后的三年內受支持。
  • Current:在初始發布后,直到下一個 Current 或 LTS 版本發布后的三個月內受支持。

.NET Core 和 .NET 發布節奏

本節整理自 .NET 5 簡介

  • 自 2020 年起,每年 11 月為 .NET 發布新的主要版本,版本號自 5.0 起遞增。即 2020 年 11 月發布 .NET 5.0,2021 年 11 月發布 .NET 6.0,以此類推。

  • 自 2021 年起,將隔年發布的主要版本標記為 LTS 版本(即偶數的主要版本為 LTS 版本,奇數的為 Current 版本)。

.NET Core 和 .NET 生命周期詳情

本節整理自 .NET 5 簡介 以及 .NET Core 支持政策,僅列出了生命周期尚未結束的版本。

版本 初始發布時間 支持級別 結束支持時間
.NET 8 2023 年 11 月(預計) LTS 2025 年 11 月(預計)
.NET 7 2022 年 11 月(預計) Current 2023 年 2 月(預計)
.NET 6 2021 年 11 月(預計) LTS 2024 年 11 月(預計)
.NET 5 2020 年 11 月(預計) Current 2022 年 2 月(預計)
.NET Core 3.1 2019 年 12 月 3 日 LTS 2022 年 12 月 3 日
.NET Core 2.1 2018 年 5 月 30 日 LTS 2021 年 8 月 21 日

.NET Framework 各版本支持政策

本節內容整理自 .NET Framework 生命周期常見問題。

  • .NET Framework 4.5.2 及以上版本被定義為 Windows 操作系統的一個組件,與其父產品獲得相同的生命周期,詳見下節內容。

  • .NET Framework 4、4.5 和 4.5.1 已於 2016 年 1 月 12 日停止支持,客戶和開發人員必須就地更新到 .NET Framework 4.5.2 及以上版本,才能技術獲得技術支持和安全更新。

  • .NET Framework 3.5 SP1 在 Windows 10 v1809 和 Windows Server 2019 及以上版本中作為獨立的產品存在,自 2018 年 10 月 2 日起獲得 5 年主流支持和 5 年擴展支持。

  • .NET Framework 3.5 SP1 在 Windows 10 v1809 和 Windows Server 2019 以前的操作系統中,作為操作系統的組件存在,其生命周期與其父產品相同。

  • .NET Framework 2.0、3.0 和 3.5 已先後停止支持,客戶和開發人員可就地更新到 .NET Framework 3.5 SP1,以便在後者的生命周期內獲得技術支持和安全更新。由於 .NET Framework 3.5 依賴於 2.0 以及 3.0,因此 .NET Framework 2.0 SP2 和 .NET Framework 3.0 SP2 組件在安裝了 .NET Framework 3.5 SP1 環境當中得到支持。

.NET Framework 版本和操作系統版本的關係

本節內容整理自 .NET Framework 版本和依賴關係 以及 .NET Framework 系統需求。

  • .NET Framework 4.5 預裝在了 Windows 8 和 Windows Server 2012 中。此後,每個版本的 Windows 操作系統都包含了特定版本的 .NET Framework 4.x。

  • .NET Framework 4.8 預裝在了 Windows 10 v1903 (build 18362) 及以上版本中。

  • .NET Framework 4.6.1 及以上版本可以安裝在 Windows 7 和 Windows Server 2008 R2 SP1 及以上版本的 Windows 操作系統中,但不可以安裝低於預裝於操作系統中的版本。

  • .NET Framework 4.6 為 Windows Vista 和 Windows 2008 SP2 支持的最高版本。

  • .NET Framework 4.0.3 為 Windows XP 和 Windows Server 2003 支持的最高版本。(參見 在 Windows XP 和 Windows Server 2003 上安裝 .NET Framework)

    注意:.NET Framework 4、.NET Framework 4.0.3 和 Windows XP、Windows Server 2003 已經停止支持,不會再接收任何形式的安全更新。

  • .NET Framework 3.5 SP1 在 Windows XP 和 Windows Server 2013 及以上版本的 Windows 操作系統中,可以使用 安裝程序 進行安裝。

  • .NET Framework 3.5 SP1 在 Windows 10,Window Server 2016 和 Windows Server 2019 中可以通過控制面板直接啟用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

聚甘新

Java 源碼刨析 – 線程的狀態有哪些?它是如何工作的?

線程(Thread)是併發編程的基礎,也是程序執行的最小單元,它依託進程而存在

一個進程中可以包含多個線程,多線程可以共享一塊內存空間和一組系統資源,因此線程之間的切換更加節省資源、更加輕量化,也因此被稱為輕量級的進程。

   

線程的狀態在 JDK 1.5 之後以枚舉的方式被定義在 Thread 的源碼中,它總共包含以下 6 個狀態:

NEW新建狀態,線程被創建出來,但尚未啟動時的線程狀態;

RUNNABLE就緒狀態,表示可以運行的線程狀態,它可能正在運行,或者是在排隊等待操作系統給它分配 CPU 資源;

BLOCKED阻塞等待鎖的線程狀態,表示處於阻塞狀態的線程正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者使用 synchronized 標記的方法;

WAITING等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作,比如,一個線程調用了 Object.wait() 方法,那它就在等待另一個線程調用 Object.notify() Object.notifyAll() 方法;

TIMED_WAITING計時等待狀態,和等待狀態(WAITING)類似,它只是多了超時時間,比如調用了有超時時間設置的方法 Object.wait(long timeout) Thread.join(long timeout) 等這些方法時,它才會進入此狀態;

TERMINATED終止狀態,表示線程已經執行完成。

線程狀態的源代碼如下:

public enum State {

    /**

     * 新建狀態,線程被創建出來,但尚未啟動時的線程狀態

     */

    NEW,

   

    /**

     * 就緒狀態,表示可以運行的線程狀態,但它在排隊等待來自操作系統的 CPU 資源

     */

    RUNNABLE,

   

    /**

     * 阻塞等待鎖的線程狀態,表示正在處於阻塞狀態的線程

     * 正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者

     * 使用 synchronized 標記的方法

     */

    BLOCKED,

   

    /**

     * 等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作。

     * 例如,一個線程調用了 Object.wait() 它在等待另一個線程調用

     * Object.notify() 或 Object.notifyAll()

     */

    WAITING,

   

    /**

     * 計時等待狀態,和等待狀態 (WAITING) 類似,只是多了超時時間,比如

     * 調用了有超時時間設置的方法 Object.wait(long timeout) 和 

     * Thread.join(long timeout) 就會進入此狀態

     */

    TIMED_WAITING,

   

    /**

     * 終止狀態,表示線程已經執行完成

     */

}

   

線程的工作模式是,首先先要創建線程並指定線程需要執行的業務方法,然後再調用線程的 start() 方法,此時線程就從 NEW(新建)狀態變成了 RUNNABLE(就緒)狀態;

然後線程會判斷要執行的方法中有沒有 synchronized 同步代碼塊,如果有並且其他線程也在使用此鎖,那麼線程就會變為 BLOCKED(阻塞等待)狀態,當其他線程使用完此鎖之後,線程會繼續執行剩餘的方法。

   

當遇到 Object.wait() Thread.join() 方法時,線程會變為 WAITING(等待狀態)狀態;

如果是帶了超時時間的等待方法,那麼線程會進入 TIMED_WAITING(計時等待)狀態;

當有其他線程執行了 notify() notifyAll() 方法之後,線程被喚醒繼續執行剩餘的業務方法,直到方法執行完成為止,此時整個線程的流程就執行完了,執行流程如下圖所示:

BLOCKED WAITING 的區別】

雖然 BLOCKED WAITING 都有等待的含義,但二者有着本質的區別。

首先它們狀態形成的調用方法不同

其次 BLOCKED 可以理解為當前線程還處於活躍狀態,只是在阻塞等待其他線程使用完某個鎖資源

WAITING 則是因為自身調用 Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而進入等待狀態,只能等待其他線程執行某個特定的動作才能被繼續喚醒。

比如當線程因為調用了 Object.wait() 而進入 WAITING 狀態之後,則需要等待另一個線程執行 Object.notify() Object.notifyAll() 才能被喚醒。

   

start() run() 的區別】

首先從 Thread 源碼來看,start() 方法屬於 Thread 自身的方法,並且使用了 synchronized 來保證線程安全,源碼如下:

public synchronized void start() {

    // 狀態驗證,不等於 NEW 的狀態會拋出異常

    if (threadStatus != 0)

        throw new IllegalThreadStateException();

    // 通知線程組,此線程即將啟動

   

    group.add(this);

    boolean started = false;

    try {

        start0();

        started = true;

    } finally {

        try {

            if (!started) {

                group.threadStartFailed(this);

            }

        } catch (Throwable ignore) {

            // 不處理任何異常,如果 start0 拋出異常,則它將被傳遞到調用堆棧上

        }

    }

}

   

run() 方法為 Runnable 的抽象方法,必須由調用類重寫此方法,重寫的 run() 方法其實就是此線程要執行的業務方法,源碼如下:

public class Thread implements Runnable {

 // 忽略其他方法……

  private Runnable target;

  @Override

  public void run() {

      if (target != null) {

          target.run();

      }

  }

}

@FunctionalInterface

public interface Runnable {

    public abstract void run();

}

   

從執行的效果來說,start() 方法可以開啟多線程,讓線程從 NEW 狀態轉換成 RUNNABLE 狀態,而 run() 方法只是一個普通的方法。

   

其次,它們可調用的次數不同,start() 方法不能被多次調用,否則會拋出 java.lang.IllegalStateException;而 run() 方法可以進行多次調用,因為它只是一個普通的方法而已。

   

【線程優先級】

Thread 源碼中和線程優先級相關的屬性有 3 個:

// 線程可以擁有的最小優先級

public final static int MIN_PRIORITY = 1;

   

// 線程默認優先級

public final static int NORM_PRIORITY = 5;

   

// 線程可以擁有的最大優先級

public final static int MAX_PRIORITY = 10

   

線程的優先級可以理解為線程搶佔 CPU 時間片的概率,優先級越高的線程優先執行的概率就越大,但並不能保證優先級高的線程一定先執行。

   

在程序中我們可以通過 Thread.setPriority() 來設置優先級,setPriority() 源碼如下:

public final void setPriority(int newPriority) {

    ThreadGroup g;

    checkAccess();

    // 先驗證優先級的合理性

    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {

        throw new IllegalArgumentException();

    }

    if((g = getThreadGroup()) != null) {

        // 優先級如果超過線程組的最高優先級,則把優先級設置為線程組的最高優先級

        if (newPriority > g.getMaxPriority()) {

            newPriority = g.getMaxPriority();

        }

        setPriority0(priority = newPriority);

    }

}

   

【線程的常用方法】

線程的常用方法有以下幾個。

   

join()

一個線程中調用 other.join() ,這時候當前線程會讓出執行權給 other 線程,直到 other 線程執行完或者過了超時時間之後再繼續執行當前線程,join() 源碼如下:

public final synchronized void join(long millis)

throws InterruptedException {

    long base = System.currentTimeMillis();

    long now = 0;

    // 超時時間不能小於 0

    if (millis < 0) {

        throw new IllegalArgumentException(“timeout value is negative”);

    }

    // 等於 0 表示無限等待,直到線程執行完為之

    if (millis == 0) {

        // 判斷子線程 (其他線程) 為活躍線程,則一直等待

        while (isAlive()) {

            wait(0);

        }

    } else {

        // 循環判斷

        while (isAlive()) {

            long delay = millis  now;

            if (delay <= 0) {

                break;

            }

            wait(delay);

            now = System.currentTimeMillis()  base;

        }

    }

}

   

從源碼中可以看出 join() 方法底層還是通過 wait() 方法來實現的。

   

例如,在未使用 join() 時,代碼如下:

public class ThreadExample {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {

            for (int i = 1; i < 6; i++) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println(子線程睡眠: + i + 秒。);

            }

        });

        thread.start(); // 開啟線程

        // 主線程執行

        for (int i = 1; i < 4; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(主線程睡眠: + i + 秒。);

        }

    }

}

程序執行結果為:

複製主線程睡眠:1秒。

子線程睡眠:1秒。

主線程睡眠:2秒。

子線程睡眠:2秒。

主線程睡眠:3秒。

子線程睡眠:3秒。

子線程睡眠:4秒。

子線程睡眠:5秒。

   

從結果可以看出,在未使用 join() 時主子線程會交替執行。

   

然後我們再把 join() 方法加入到代碼中,代碼如下:

public class ThreadExample {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {

            for (int i = 1; i < 6; i++) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println(子線程睡眠: + i + 秒。);

            }

        });

        thread.start(); // 開啟線程

        thread.join(2000); // 等待子線程先執行 2 秒鐘

        // 主線程執行

        for (int i = 1; i < 4; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(主線程睡眠: + i + 秒。);

        }

    }

}

程序執行結果為:

   

複製子線程睡眠:1秒。

子線程睡眠:2秒。

主線程睡眠:1秒。 

// thread.join(2000); 等待 2 秒之後,主線程和子線程再交替執行

子線程睡眠:3秒。

主線程睡眠:2秒。

子線程睡眠:4秒。

子線程睡眠:5秒。

主線程睡眠:3秒。

從執行結果可以看出,添加 join() 方法之後,主線程會先等子線程執行 2 秒之後才繼續執行。

   

yield()

Thread 的源碼可以知道 yield() 為本地方法,也就是說 yield() 是由 C C++ 實現的,源碼如下:

public static native void yield();

   

yield() 方法表示給線程調度器一個當前線程願意出讓 CPU 使用權的暗示,但是線程調度器可能會忽略這個暗示。

   

比如我們執行這段包含了 yield() 方法的代碼,如下所示:

public static void main(String[] args) throws InterruptedException {

    Runnable runnable = new Runnable() {

        @Override

        public void run() {

            for (int i = 0; i < 10; i++) {

                System.out.println(線程: +

                        Thread.currentThread().getName() +  I + i);

                if (i == 5) {

                    Thread.yield();

                }

            }

        }

    };

    Thread t1 = new Thread(runnable, “T1”);

    Thread t2 = new Thread(runnable, “T2”);

    t1.start();

    t2.start();

}

   

當我們把這段代碼執行多次之後會發現,每次執行的結果都不相同,這是因為 yield() 執行非常不穩定,線程調度器不一定會採納 yield() 出讓 CPU 使用權的建議,從而導致了這樣的結果。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

一文梳理JavaScript 事件循環(Event Loop)

事件循環(Event Loop),是每個JS開發者都會接觸到的概念,但是剛接觸時可能會存在各種疑惑。

眾所周知,JS是單線程的,即同一時間只能運行一個任務。一般情況下這不會引發問題,但是如果我們有一個耗時較多的任務,我們必須等該任務執行完畢才能進入下一個任務,然而等待的這段時間常常讓我們無法忍受,因為我們這段時間什麼都不能做,包括頁面也是鎖死狀態。

好在,時代在進步,瀏覽器向我們提供了JS引擎不具備的特性:Web API。Web API包括DOM API、定時器、HTTP請求等特性,可以幫助我們實現異步、非阻塞的行為。我們可以通過異步執行任務的方法來解決單線程的弊端,事件循環為此而生

提問QAQ:為什麼JavaScript是單線程的?

多個線程表示您可以同時獨立執行程序的多個部分。確定一種語言是單線程還是多線程的最簡單方法是看它擁有有多少個調用堆棧。JS 只有一個,所以它是單線程語言。

將JS設計為單線程是由其用途運行環境等因素決定的,作為瀏覽器腳本語言,JS的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。同時,單線程執行效率高。

1. Event Loop舊印象

大家熟悉的關於事件循環的機制說法大概是:主進程執行完了之後,每次從任務隊列里取一個任務執行。如圖所示,所有的任務分為同步任務和異步任務,同步任務直接進入任務隊列–>主程序執行;異步任務則會掛起,等待其有返回值時進入任務隊列從而被主程序執行。異步任務會通過任務隊列的機制(先進先出的機制)來進行協調。具體如圖所示:

同步和異步任務分別進入不同的執行環境,同步的進入主線程,即主執行棧,異步的進入任務隊列。主線程內的任務執行完畢為空,會去任務隊列讀取對應的任務,推入主線程執行。 上述過程的不斷重複就是我們所熟悉的Event Loop (事件循環)。但是promise出現之後,這個說法就不太準確了

2. Event Loop 後印象

2.1 理論

這裏首先用一張圖展示JavaScript的事件循環:

直接看這張圖,可能黑人問號已經出現在同學的腦海。。。

這裏將task分為兩大類,分別是macroTask(宏任務)和microTask(微任務).一次事件循環:先運行macroTask隊列中的一個,然後運行microTask隊列中的所有任務。接着開始下一次循環(只是針對macroTask和microTask,一次完整的事件循環會比這個複雜的多)。

那什麼是macroTask?什麼是microTask呢?

JavaScript引擎把我們的所有任務分門別類,一部分歸為macroTask,另外一部分歸為microTack,下面是類別劃分:

macroTask:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

microTask:

  • process.nextTick
  • Promise
  • Object.observe
  • MutationObserver

我們所熟悉的定時器就屬於macroTask,僅僅了解macroTask的機制還是不夠的。為直觀感受兩種隊列的區別,下面上代碼進行實踐感知。

2.2 實踐

以setTimeout、process.nextTick、promise為例直觀感受下兩種任務隊列的運行方式。

console.log('main1');

process.nextTick(function() {
    console.log('process.nextTick1');
});

setTimeout(function() {
    console.log('setTimeout');
    process.nextTick(function() {
        console.log('process.nextTick2');
    });
}, 0);

new Promise(function(resolve, reject) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise then');
});

console.log('main2');

別著急看答案,先以上面的理論自己想想,運行結果會是啥?

最終結果是這樣的:

main1
promise
main2
process.nextTick1
promise then

// 第二次事件循環
setTimeout
process.nextTick2

process.nextTick 和 promise then在 setTimeout 前面輸出,已經證明了macroTask和microTask的執行順序。但是有一點必須要指出的是。上面的圖容易給人一個錯覺,就是主進程的代碼執行之後,會先調用macroTask,再調用microTask,這樣在第一個循環里一定是macroTask在前,microTask在後。

但是最終的實踐證明:在第一個循環里,process.nextTick1和promise then這兩個microTask是在setTimeout這個macroTask里之前輸出的,這是因為Promises/A+規範規定主進程的代碼也屬於macroTask。

主進程這個macroTask(也就是main1、promise和main2)執行完了,自然會去執行process.nextTick1和promise then這兩個microTask。這是第一個循環。之後的setTimeout和process.nextTick2屬於第二個循環

別看上面那段代碼好像特別繞,把原理弄清楚了,都一樣 ~

requestAnimationFrame、Object.observe(已廢棄) 和 MutationObserver這三個任務的運行機制大家可以從上面看到,不同的只是具體用法不同。重點說下UI rendering。在HTML規範:event-loop-processing-model里敘述了一次事件循環的處理過程,在處理了macroTask和microTask之後,會進行一次Update the rendering,其中細節比較多,總的來說會進行一次UI的重新渲染。

3. 小結

總而言之,記住一次事件循環:先運行macroTask隊列中的一個,然後運行microTask隊列中的所有任務。接着開始下一次循環。

參考文獻:

  • 總是一知半解的Event Loop
  • 深入理解事件循環機制
  • JavaScript運行機制

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

聚甘新

福斯汽車、SMA Solar傳聯手發展電動車

福斯汽車(Volkswagen)深陷柴油車排氣造假案使得品牌形象受重創,為了早點擺脫「柴油門」陰影,轉移消費者的眼光,福斯近來大力推動電動車,雄心勃勃的發展電動車的同時,傳出找來德國逆變器大廠SMA Solar 結盟。

SMA Solar 成立於1981 年,公司名來自「系統、測量、系統工程」(System, Mess and Anlagentechnik)德文簡寫,為全球最大逆變器廠,逆變器應用於直流電交流電轉換,不僅應用於太陽能,也應用於電池能源儲存系統,SMA Solar 市場地位穩固,特斯拉(Tesla)於2016 年5 月宣布Powerwall 將進行小規模規格提升,其重點之一就是改為可支援SMA Solar 產品。

福斯在「柴油門」事件後,積極將事業目標轉向電動車,以便洗刷受到作假事件影響的聲譽,投注數十億歐元資金,大力發展零碳排放的純電動車以及隨選共乘服務,計劃在2025 年以前推出30 款電動車,攻佔四分之一的全球電動車市場。

但是分析師指出,福斯在2015 年只不過出貨6.7 萬輛電動車與油電混合車,以這樣的市佔與進度,要在2025 年攻佔四分之一電動車市場的目標可能很難達成,尤其是競爭對手早已起步,為了追上豐田(Toyota)與雷諾(Renault)日產(Nissan)聯盟,福斯需要找尋強大的合作夥伴。

德國地方日報《黑森下薩克森大眾報》(Hessische Niedersaechsische Allgemeine)於2016 年8 月初報導,福斯尋求能源儲存與綠能大廠SMA Solar 的合作。

福斯的電動車廠表示,SMA Solar 在靜態能源儲存領域相當強勢,對福斯來說,尋求正確的合作夥伴,將是發展電動車的重要關鍵。不過,SMA Solar 目前已經與賓士製造商戴姆勒(Daimler)以及特斯拉在能源儲存方面合作,目前對於福斯的可能合作消息不表示意見。

(本文授權自《》──〈〉。照片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司“嚨底家”!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新

新能源車夯,比亞迪前三季獲利有望跳增九成

中國電動汽車商比亞迪(BYD)周日公布,2016年前九個月盈餘最高有望年增逾九成(91%),主要受惠於中國政策大力推動節能汽車。

比亞迪有股神巴菲特光環加持,上個月又獲得三星入股,可謂左右逢源。據比亞迪表示,上半年獲利來到23億人民幣,較去年同期跳增384%,若連同本季獲利,今年前三季預估將成長83-91%。

比亞迪以製造電池起家,2003年併購秦川汽車後跨足汽車市場,並為日後進軍電動車鋪路。憑藉著電池技術領先同業,比亞迪在中國新能源車領域已先立於不敗之地,近年隨著環保法規轉嚴,比亞迪生產之油電混合車與全電動車業績更是蒸蒸日上。

三星七月下旬以約30億人民幣參與比亞迪新一輪募資,亦是著眼於中國新能源車的商機。不過在三星正式入股後,巴菲特掌舵的柏克夏海瑟威公司(Berkshire Hathaway)對比亞迪的持股比例,則從原先的9.1%稀釋至8.25%。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

IEA:今之電動車有如10年前的太陽能

英國金融時報31日報導,瑞銀(UBS)預估2021年歐洲未經補貼的純電動車整體持有成本將與傳統內燃機汽車相當、中國也可望在2025年達到這項里程碑,美國則因油價相對低廉、在可預見的未來都無法看到。國際能源署(IEA)首席經濟學家Laszlo Varro指出,電動車目前對商品(原油)市場的影響力大約就像是10年前的太陽能一樣。他說,太陽能現在已是一個規模達數百億美元的市場、擁有龐大的影響力。Varro提到,電動車需達5千萬至1億台的規模、才能取代相當於100萬桶的石油日消費量。

IEA數據顯示,2009年全球40個國家合計僅有不到6千台的電動車、去年已升至120萬台。麥格理集團全球能源策略師Vikas Dwivedi指出,沙烏地阿拉伯對電動車的長期發展存有戒心,這可能就是它為何宣布將讓沙烏地阿拉伯國家石油公司(Saudi Aramco)初次公開發行(IPO)的原因之一。

CNNMoney去年底報導,石油輸出國組織(OPEC)發表的年度「世界石油展望(World Oil Outlook)」報告顯示,2040年高達94%的使用中車輛仍將是依靠石油燃料。OPEC報告顯示,除非出現重大技術性突破,否則在可預見的未來電動車將難以大幅取得市佔率。油國組織預估2040年僅有1%的車輛銷售量是來自純電動車款。

華爾街日報6月報導,根據能源咨詢公司Wood Mackenzie發布的報告,未來20年電動車的普及可能會導致美國汽油需求縮減5-20%。美國目前平均每天的汽油使用量超過900萬桶。報告顯示,如果電動車2035年市占比重因特斯拉(Tesla)等廠商開始推出較低價車款以及續航力和便利性(註:目前大約每100英里就得充電一次)顯著突破而站上35%,美國日需求量可能會減少200萬桶。

即便電動車現在還沒有茁壯到可以侵蝕汽油需求的地步,美國依舊面臨油品供給過剩的問題。

美國石油協會(API)8月18日公布,2016年7月衡量消費者汽油需求的指標(汽油交運量)創下史上最高紀錄、日均量接近970萬桶。美國7月汽油日均產量同樣創下歷史新高、年增1.9%(月增1.3%)至1,020萬桶!

能源情報署(EIA)公布,截至2016年8月19日為止當週美國商用汽油庫存報2.327億桶、較去年同期高出8.5%。EIA公布的數據顯示,美國一般汽油每加侖零售均價8月22日報2.193美元,較一年前短少0.444美元。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

聚甘新

中國大陸推排放標準,控管油電混和車排放

大陸環保部30日發布涉及船舶發動機、摩托車、輕便摩托車、輕型混合動力電動汽車和燒鹼、聚氯乙烯(PVC)工業排放的五項國家污染物排放標準,分別為《船舶發動機排氣污染物排放限值及測量方法(中國第1、2階段)》、《摩托車污染物排放限值及測量方法(中國第4階段)》、《輕便摩托車污染物排放限值及測量方法(中國第4階段)》、《輕型混合動力電動汽車污染物排放控制要求及測量方法》和《燒鹼、聚氯乙烯工業污染物排放標準》。上述標準旨在落實《大氣污染防治行動計畫》和《水污染防治行動計畫》,透過制定、修訂重點行業的排放標準力促產業轉型升級。

大陸環保部科技標準司司長鄒首民表示,實施這五項標準將可望大幅削減顆粒物(PM)、氮氧化物(NOx)、二氧化硫(SO2)的污染,有效促進行業技術進步和環境品質的改善。他指出,新標準適用於具有中國船籍在大陸水域航行或作業的船舶裝用的額定淨功率大於37千瓦、新生產船用發動機的環境管理,不適用於遠洋船舶,遠洋運輸船舶執行國際公約的相關規定。

鄒首民指出,上述新標準實施後,按照每年新增船機1,000萬千瓦,壽命期為25年計算,實施第一階段標準3年,所製造投入使用的船機在全壽命期內將減排NOX約140萬噸,PM約40萬噸;若實施第二階段標準3年,也將進一步減排NOX約115萬噸,PM約6萬噸。

另外,對於產量增長迅速的摩托車污染問題也正在引起重視。鄒首民說,為能有效控制摩托車污染,環保部制定了摩托車和輕便摩托車國四標準,自2019年7月1日起,所有新銷售和註冊登記的摩托車和輕便摩托車,應滿足新的標準;以國四標準實施3年估算,這期間,新生產的全部摩托車在其整個使用壽命內,將比實施國三標準減少CO排放約650萬噸、HC排放約200萬噸、NOx排放約30萬噸。

他也指出,近年來大陸國家積極鼓勵發展包括混合動力電動汽車在內的節能與新能源汽車,但由於有電能的輔助,傳統汽車的測量方法無法準確評判混合動力電動汽車的污染物排放狀況;新標準是對《輕型混合動力電動汽車污染物排放測量方法》的修訂,規定了輕型混合動力電動汽車的污染控制要求和測量方法,新標準適用於國四、國五階段的輕型混合動力汽車的環保管理,該測量方法標準的實施,將不會帶來額外的車輛技術升級成本。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聚甘新