網絡編程-Netty-Reactor模型

目錄

  • # 摘要
  • 高性能服務器
  • Reactor模式
    • Reactor單線程模型設計
    • Reactor多線程模型設計
    • 主從Reactor多線程模型設計
    • Netty Reactor模型設計
  • 參考
  • 你的鼓勵也是我創作的動力
  • Posted by 微博@Yangsc_o
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

# 摘要

在前兩篇《快速理解Linux網絡I_O》、《java的I_O模型-BIO&NIO&AIO》兩邊中介紹了Linux下的I/O模型和java中的I/O模型,今天我們介紹Reactor模型,並探究Netty的實現

高性能服務器

在互聯網時代,我們使用的軟件基本上全是C/S架構,C/S架構的軟件一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。C/S架構可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務器):服務器管理某種資源,並且通過操作這種資源來為它的客戶端提供某種服務

那服務器如何能快速的處理用戶的請求呢?在我看來高性能服務器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基於此服務器進行業務開發需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態

而滿足如上需求的一個基礎就是高性能的IO!

Reactor模式

什麼是Reactor模式?

兩種I/O多路復用模式:Reactor和Proactor,兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

在Proactor模式中,處理器–或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩衝區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。

說人話的方式理解:

  • reactor:能收了你跟俺說一聲。
  • proactor: 你給我收十個字節,收好了跟俺說一聲。

Doug Lea是這樣類比的

  • Reactor通過調度適當的處理程序來響應IO事件;
  • 處理程序執行非阻塞操作
  • 通過將處理程序綁定到事件來管理;

Reactor單線程模型設計

單線程版本Java NIO的支持:

  • Channels:與支持非阻塞讀取的文件,套接字等的連接

  • Buffers:類似於數組的對象,可由Channels直接讀取或寫入

  • Selectors:通知一組通道中哪一個有IO事件

  • SelectionKeys:維護IO事件狀態和綁定

  • Reactor 代碼如下

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        key.attach(new Acceptor());
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    dispatch(selectionKey);
                }
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    new Handler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(
                new Reactor(1234)
        ).start();
    }

}
  • Handler代碼如下:
public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }


    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                System.out.println("接收到來自客戶端(" + socketChannel.socket().getInetAddress().getHostAddress()
                        + ")的消息:" + new String(inputBuffer.array()));
                seletionKey.attach(new Sender());
                seletionKey.interestOps(SelectionKey.OP_WRITE);
                seletionKey.selector().wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

}

這個模型和上面的NIO流程很類似,只是將消息相關處理獨立到了Handler中去了!雖然說到NIO一個線程就可以支持所有的IO處理。但是瓶頸也是顯而易見的!如果這個客戶端多次進行請求,如果在Handler中的處理速度較慢,那麼後續的客戶端請求都會被積壓,導致響應變慢!所以引入了Reactor多線程模型!

Reactor多線程模型設計

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!

Reactor保持不變,僅需要改動Handler代碼:

public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;
    private Selector selector;


    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.selector = selector;
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }

    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                executorService.execute(new Processer());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

    synchronized void processAndHandOff() {
        process();
        // or rebind attachment
        state = SENDING;
        seletionKey.interestOps(SelectionKey.OP_WRITE);
        selector.wakeup();
    }

    class Processer implements Runnable {
        @Override
        public void run() {
            processAndHandOff();
        }
    }

}

主從Reactor多線程模型設計

主從Reactor多線程模型是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同:

Handler保持不變,僅需要改動Reactor代碼:

public class Reactor {
    // also create threads
    Selector[] selectors;
    AtomicInteger next = new AtomicInteger(0);
    final ServerSocketChannel serverSocketChannel;

    private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;

    public Reactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selectors = new Selector[4];
        for (int i = 0; i < selectors.length; i++) {
            Selector selector = selectors[i];
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            key.attach(new Acceptor());
            new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        selector.select();
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            dispatch(selectionKey);
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }



    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Reactor(1234);
    }

}

以上是三種不同的設計思路,接下來看一下Netty這個一個高性能NIO框架,其是如何實現Reactor模型的!

Netty Reactor模型設計

  • 看一個最簡單的Netty服務端代碼
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務器代碼來看,與Reactor模型進行對應!

  • EventLoopGroup就相當於是Reactor,bossGroup對應主Reactor,workerGroup對應從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel,非child開頭的方法配置的是服務端channel

當然Netty的線程模型並不是固定的,它支持Reactor單線程模型、Reactor多線程模型、主從模型,上面的例子是一個主從模型的,下面進行詳細的分析,如圖所示:

服務啟動時,創建了兩個EventLoopGroup,它們實際上是兩個Reactor線程池,一個用於接收TCP連接、一個用於處理I/O相關的讀寫操作、或者執行系統task、定時task等;

  • Netty用於接收客戶端請求連接池職責如下:
    • 接收客戶端請求並初始化channel參數;
    • 講鏈路變更事件通知給ChannelPipiline;
  • Netty用於處理I/O連接池職責如下:
    • 異步讀取通信對端的數據報,發送讀事件到ChannelPipiline;
    • 異步發送消息對端的數據報,調用ChannelPipiline的消息發送接口;
    • 執行系統調用task;
    • 執行系統定時任務task,例如鏈路空閑狀態檢測定時任務;

參考

Scalable IO in Java

高性能Server—Reactor模型

NIO技術概覽

《Netty 權威指南》第二版 — 李林峰

你的鼓勵也是我創作的動力

打賞地址

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

python3 源碼閱讀-虛擬機運行原理

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterState和PyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

Google 推出「碳智慧運算平台」 讓資料中心根據天氣調配綠能使用

摘錄自2020年5月26日 科技報橘報導

Google 已在全球建立 20 個資料中心。Google 為達到讓自己的資料中心的運作朝向零碳排放的長期目標,近期展開了新的計畫,成了立一組團隊推出「碳智慧運算平台」,並與丹麥綠能科技公司 Tomorrow 合作,透過天氣預測即時調配何時適合使用再生能源,讓資料中心能充分利用、處理能再生能源的電力來運作資料運行。

Google 表示,這個平台不需要增外的硬體運作,也不會影響 Google 本身既有的服務,會受調整的資料運行工作是如 Google Photo 濾鏡的新功能的製作,或是增加 Google 翻譯的新單字等等,透過減少碳足跡,來讓 24 小時無碳能源供應的願景更近一步。

Google 透過調節資料運作時間來減少碳排放的計畫聽起來新穎且聰慧,不過 Google 尚未提供相關研究成果的詳細數據,根據 Techchruch 的報導,該計劃的技術代表 Ana Radovanovic 表示,初步的結果證明這樣的方式是有效的,不過資料運算轉移方法與觀測成效等相關研究結果,要等到預計今年稍後才會進行發表。

能源議題
能源節約
能源轉型
國際新聞
美國
綠能
智慧型節能
零碳排放

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

巴西肉品包裝廠成病毒溫床 2400員工染疫!

摘錄自2020年6月02日ETtoday報導

巴西勞工檢察官辦公室今(2日)表示,境內最南端的南大河州累計武漢肺炎(COVID-19)確診病例約9000例,其中超過1/4為肉品工廠工人。

路透社報導,巴西勞工檢察官聲明表示,約有2399名分散在南大河州(Rio Grande do Sul)18個城市、24座屠宰場的員工遭到感染。根據巴西衛生部統計數據,這個數字相當於南大河州累計到昨天為止共9332起病例的25.7%。

這項發現證實肉品包裝工廠已成巴西疫情溫床。巴西肉品包裝業巨頭JBS SA和BRF SA都在南大河州設有工廠。兩家公司位於當地的工廠先前都曾因爆發新型冠狀病毒疫情而暫時關閉。

生活環境
國際新聞
巴西
武漢肺炎
群聚感染
肉品加工

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

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

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

地球生態亮紅燈!最深海溝驚見「新物種」 體內藏大量塑膠纖維

摘錄自2020年6月3日CTWANT報導

太平洋馬里亞納海溝(Mariana Trench)是全球最深的海溝,其海平面深度甚至大於聖母峰的高度,在如此神秘的海域中,卻發現了一項全新物種,經研究過後,竟發現牠的體內充滿了塑膠微粒,可見塑膠的禍害已經嚴重污染地球生態,就連海洋深處也遭受其害。

綜合外媒報導,英國紐卡索大學(Newcastle University)在海平面下近7公里處發現新物種,並採集4隻帶回研究分析,沒想到竟在其中1隻體內發現塑膠纖維,與寶特瓶材質「聚對苯二甲酸乙二酯(PET)」相似度高達60%,因此將這新物種命名為「塑膠鉤蝦(Eurythenes plasticus)」。

世界自然基金會(WWF)德國海洋計畫主任維斯普(HeikeVesper)強調,這項驚人的發現證明了「廢棄塑膠」已經嚴重污染海洋,儘管物質已變成細小微粒,但還是充斥在空氣、飲用水之中。這項研究已刊登在動物生物分類學科學期刊《Zootaxa》,警示人們應立即採取行動,阻止傷害繼續發生。

生態保育
海洋
公害污染
污染治理
生物多樣性
國際新聞
馬里亞納海溝
新物種
海洋汙染
廢棄物

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

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

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

南非野生動物交易黑市 高價賣往中國的真相:淪落動物園、實驗室、肉品市場

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS 編按:接續
上篇,近期一份報告「南非與中國間可恥的野生動物貿易」揭露南非政府放任野生動物活體市場、違反國際公約的真相。報告由南非非政府組織「EMS基金會」和「禁止動物貿易(Ban Animal Trading)」發表,他們指控,包括黑猩猩、孟加拉虎、狼、非洲野犬和獅子在內,數千種瀕臨滅絕的野生動物從南非銷往中國,法規和審核流程毫無作用、動物福利和自然保護原則遭到忽視,並提供確切犯罪證據。

動物福利問題

雖然瀕危物種貿易公約(CITES)應該保證出口的野生動物到達「適當和可接受的目的地」,但從南非銷往中國的野生動物最終可能成為寵物、古董、食品、傳統藥材、動物園展示動物或實驗動物。

報告指出,由於許多動物是由最初的進口商出售給未知的第三方,通常無法確定其最終目的地。

在中國,進口野生動物的飼養設施通常品質不良。以出售給北京野生動物公園的黑猩猩為例,牠們抵達中國時,籠舍等設施還沒完工,也沒有合格的員工來照顧牠們。

來自南非的黑猩猩被關在北京野生動物公園中狹窄的展示空間內。
報告截圖

這些動物中有許多是為了娛樂目的而運往中國成千上萬個公有或私有「野生動物園」、主題公園和馬戲團。根據該報告,其中有些機構涉嫌虐待動物、環境設備條件惡劣、訓練野生動物表演以及非法購買野生動物。

從南非出口的大多數非靈長類動物,包括數百隻狨屬猴子,是賣給中間商、批發商和繁殖場,最終進入會執行活體切片的生物醫學、化妝品和製藥實驗室。

道德破產的政策

報告中提供的證據清楚地顯示,南非的野生動物貿易沒有任何保育作用。加上牽涉到瀕危物種,中國的最終目的地大多很可疑以及貿易活動刺激需求成長,這條供應鏈對生物多樣性和物種生存的影響,比較可能是弊遠大於利。

這個產業的真正動機並不難發現。根據這份報告,一間南非公司出於所謂非商業目的向一間中國公司出口的18隻黑猩猩,價格超過750萬南非幣(約新台幣1287萬元),100隻狐獴的行情價格為60萬南非幣(約新台幣102萬元),57頭長頸鹿700萬南非幣(約新台幣1201萬元)和18隻非洲野犬100萬南非幣(約新台幣171萬元)。

多年來,南非政府一直在積極推動這個獲利導向的行業,無視CITES及其自己的出口法規,還大力推廣「永續利用」,將野生動物視為養殖和出口商品,將保育責任推給國際市場。

100隻狐獴的行情價格為60萬南非幣(3萬4460美元)。照片來源:Jarod Carruthers(CC BY-NC-ND 2.0)

EMS基金會負責人皮克佛(Michele Pickover)表示,這份報告的前一個版本已經寄給南非各相關政府部門和官員,裡面有清楚的非法獅骨貿易證據,「但我們從未收到任何人的任何回應,這不是無能,是無視。」

考慮到武漢肺炎(COVID-19)危機,忽視貿易問題將牽涉刑事責任。南非政府若繼續支持和合法化這個危害國內生態系統和生物多樣性的產業,就是使全世界暴露於新型人畜共通傳染病的風險,就應對相關生態破壞、人類健康和財務後果承擔某些責任。

作者呼籲南非政府放棄有爭議的野生動植物貿易政策,並禁止出口活體野生動物及其身體部位。

「報告明確指出,任何人工繁殖和貿易都使消費合法化和常態化,影響減少需求的呼籲互相矛盾,使野生物種面臨進一步開發的風險。」

作者呼籲用新的全球協議代替CITES,以「野生動物貿易不適當、適得其反、不道德和根本非永續」為基本指導原則。

South Africa’s Live Wild Animal Trade to China Exposed CAPE TOWN, South Africa, May 26, 2020(ENS)

Animal Welfare Concerns

While CITES rules are supposed to guarantee that exported live wild animals go to “appropriate and acceptable destinations,” those traded from South Africa to China may end up being used as pets, curios, food, ingredients in traditional medical practices, zoo exhibits or laboratory test subjects.

The report notes that since many animals are sold on to unknown third parties by the initial importers, their final destination is frequently impossible to ascertain.

Facilities housing imported wild animals in China are often of an inferior standard. In the case of the chimpanzees sold to the Beijing Wild Animal Park, for example, their accommodation was not yet completed on their arrival in the country and the facility did not have qualified staff to take care of them.

Many of the animals are destined for China’s thousands of government-run or privately-owned “safari parks,” zoos, theme parks and circuses for the sole purpose of entertainment. According to the report, several of these institutions have been exposed for animal abuse, poor conditions and facilities, training wild animals to perform for audiences and illegally buying wild-caught animals.

Most of the non-human primates exported from South Africa, including hundreds of marmosets, are sold to brokers, wholesalers and breeding farms, and many of them end up in laboratories conducting experiments, including vivisection, for the biomedical, cosmetic and pharmaceutical industries.

Morally Bankrupt Government Policies

The evidence presented in the report makes it abundantly clear that South Africa’s trade in live wild animals has no conservation value whatsoever. Given the involvement of endangered species, the frequently dubious final destinations in China and the fact that the trade stimulates growing demand, it’s more likely to have a detrimental impact on biodiversity and species survival.

The true motivation for this industry is not hard to find. The 18 chimps exported from a commercial entity in South Africa for supposedly non-commercial purposes to a commercial entity in China came at a cost of over R7.5 million (US$430,758). The report lists the going price for 100 meerkats at R600,000 (US$34,460), 57 giraffes at R7million (US$402,000) and 18 African Wild Dogs at R1 million (US$57,430).

The South African government has been actively enabling this profit-driven industry for years. It has done so through its laissez-faire disregard for CITES and its own export regulations and through its aggressive promotion of a “sustainable use” philosophy that treats wild animals as mere commodities to be bred and sold while leaving conservation concerns to the supposed benevolence of international markets.

According to Michele Pickover, director of the EMS Foundation, copies of an earlier and equally damaging EMS/Ban Animal Trading report on South Africa’s trade in lion bones clearly detailing illegal activities was sent to various domestic authorities and individuals in government.

“We never received any response from any of them,” Pickover says. “This is not a case of incompetence. They are ignoring us.”

Given the COVID-19 disaster, ignoring this issue amounts to criminal negligence. As long as the South African government continues to support and legitimise an industry that endangers the biodiversity of domestic ecosystems while exposing the entire world to novel zoonotic diseases, it bears some responsibility for the devastating ecological, human health and financial consequences it causes.

The authors call for the government to abandon its controversial wildlife trade policies and to ban the export of living wild animals and their body parts.

They write, “The report makes clear that any captive breeding and trade legitimises and normalises consumption, which renders demand reduction campaigns incoherent and ineffective, and puts wild species at further risk of exploitation.”

The authors call for the replacement of CITES with a different global agreement that would have as its fundamental guiding principle that “the trade in wild animals is inappropriate, counter-productive, unethical and fundamentally unsustainable.”

※ 全文及圖片詳見:ENS

出售野生動物
CITES
走私
活體動物
動物福利
道德
國際新聞
南非
美國
生態保育
生物多樣性

作者

姜唯

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

林大利

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

※回頭車貨運收費標準

長灘島上幾乎沒有蝙蝠了 菲律賓度假勝地的生態危機:狐蝠族群消逝中

環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

※回頭車貨運收費標準

新能源曙光 科學家開發出鈉電池

摘錄自2020年6月1日中時電子報報導

華盛頓州立大學(WSU)和西北太平洋國家實驗室(PNNL)的研究人員,創建了一種鈉離子電池,有望成為新一代的充電電池,而且比現行的鋰離子電池有個巨大的優勢,就是原料相當廉價。

科技探險(techxplore)報導,功效良好的鋰離子電池,幾乎可說「改變了整個時代」,它幾乎達到「無處不在」的地步,舉凡手機、筆記型電腦、和電動汽車,都需要用到鋰離子電池。但是鋰離子電池一直都有個隱憂,就是稀缺。鋰電池的材料來自鈷、鋰等稀少、昂貴的材料製成,所以未來這些材料將變得更難獲得,有朝一日可能會遇到材料枯竭的危機,因此鈉電池就是一種希望。

美國能源部電力辦公室儲能研究室主管伊默 ‧古克博士(Imre Gyuk)說:「這是鈉離子電池的一項重大發展,人們會希望用鈉離子電池,代替鋰離子電池。」

能源議題
能源轉型
國際新聞
電池
鋰電池

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

首度超車燃煤 再生能源躍美主要電力

摘錄自2020年6月1日工商時報報導

根據能源資訊管理局(EIA)的調查指出,2019年美國再生能源發電量創下1885年來首次超越燃煤紀錄,這也意味再生能源已成為美國主要電力來源。

EIA表示,去年燃煤發電量不僅較前一年減少15%至11.3千兆英制熱單位,創下1964年來最低,並且為連續第六季下滑。同時期再生能源發電量則是年增1%到11.5千兆英制熱單位,寫下紀錄新高。

當中又以風力與太陽能發電量出現最大成長,目前在美國再生能源最常被使用的風力發電,在去年已首度超越水力發電。

再生能源
能源議題
能源轉型
國際新聞
美國
發電量

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

※台中搬家公司費用怎麼算?

自從開上了寶駿310,媽媽再也不擔心我養不起車!

寶駿的保養成本,每天只需1。9元對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

誰又會想過一部IpHONE 7的價格能完成一輛車的首付。至少在寶駿310面世以前,沒有人去假設過這一可能性。3.68萬的起售價,4.98萬的頂配封頂,寶駿310讓“年輕人的第一輛車“的門檻降低到了讓人詫異的地步。而在頗有吸引力的價格背後,我們看到了年輕動感的設計、豐富多元的配置、寬敞舒適的空間,這也直接造就了寶駿310在11月收穫高達15006輛的銷量。“年輕人的第一輛車”這個稱號,頗有幾分份量。

不過很顯然,寶駿310希望把“年輕人的第一輛車”這個名號更徹底地貫徹下去。所以在用車成本這項決定着用戶是“用一輛車”還是“養一輛車”的問題上,寶駿310在整個產品的生命周期內為年輕人提供了幾乎零壓力的用車成本。對於尚處於奮鬥的年輕人來說:沒有比這更讓人愉快的事情了。

超長的保修政策!完全沒有後顧之憂!

保修期的重要性,我想每一個人都很清楚。小到手機,大到冰箱,再到汽車,無一例外。這不僅是關乎消費者對產品本身的信心,更重要的是:萬一產品因為質量而壞了,消費者能夠找到一個可靠的人來把它免費修好。而寶駿310,也堪稱豪爽地給出了2年或5萬公里的三包有效期以及5年10萬公里的整車保修期。

正所謂沒有對比就沒有傷害。本田飛度的整車保修期為3年或10萬公里;豐田卡羅拉的整車保修期為3年或10萬公里。寶駿310的整車保修期足足比這些主流的合資車型多出了2年的時間。2年的時間能夠做些什麼?或許你能夠成為老闆;或者結婚成為愛情奴;又或者把歐洲都走上一遍。而這兩年的時間,你壓根不用擔心你的寶駿310。

而更貼心的是,寶駿310的免費首保服務(3個月或5000公里)包含了機油更換、機油濾芯更換以及工時費在內的全部保養費用。首保,沒有任何一分的費用。

修不起車?!寶駿的保養成本,每天只需1.9元

對於第一輛車,每一個人都恨不得給予其最好的養護。對此,寶駿可謂善解人意地為寶駿310制定了極為低廉的養護費用標準,這無疑能夠讓每一個寶駿310的車主節省很大的一筆錢。到底有多省,我們按照寶駿310維修保養手冊的標準算一算便清晰可知。

按照寶駿310維修保養手冊的標準,寶駿310三年下來的正常養護費用只需要2128元(按每年行駛15000公里為標準)。倘若把筆養護費用按天來計算,呵呵,那真是嚇壞寶寶—1.9元。相信我,這不是電視購物的忽悠。

加不起油?!寶駿310的油費低到你不信

比起正常養護,或許油費更能引起大家的關注,畢竟加油的頻率要比養護要高太多。不過比起其他車主對於油價的膽顫心驚,寶駿310車主絕對要淡定許多。原因無他,寶駿310搭載了一台高效的1.2L自然吸氣四缸發動機(最大馬力82ps,最大扭矩116Nm),並且配備了一台5檔的手動變速箱。按照寶駿310在工信部的認定油耗來計算(5.3L/百公里,實際車主反映也符合這一數據),三年下來(每年行駛15000公里),寶駿310的油耗只需要14167元(92號汽油)。對比起那些油耗動輒8L、9L的轎車、SUV來說,寶駿310的油耗恐怕不是一個省字能夠概括。所以我想那些寶駿310的車主一定都是些喜歡四處亂逛的人,畢竟他們都不需要擔心油費。

媽媽再也不用擔心我買不起保險了

寶駿310的價格優勢除了直接拉低了年輕人的購買門檻以外,也使得年輕人許多負擔的車險費用大大降低。以售價4.98萬的寶駿310 【2016款 1.2L 手動豪華型】為例,在第三者50萬以及險種齊全的情況下,交強險加上商業險的總費用只需要5000元/一年。而且按照現在的保險法,假如車主出險的次數較少,這一商業險費用還有很大的下降空間。

沒有車比寶駿310更勝任“年輕人的第一台車”

很明顯,寶駿310是很能夠代表“年輕人的第一台車”的,不僅因為其低得讓人詫異的售價,更因為其自身擁有着匹配於年輕人經濟能力的用車成本。對於購買寶駿310的消費者來說,使用寶駿310是一種毫無壓力的出行方式,而不是給自身帶來額外的經濟壓力。而這正是現時許多年輕人所真真切切需要的。

吃一頓賣相稍好的快餐,買一張講求緣分的打折電影票,買一件摻雜着劣質尼龍面料的T恤。而現在,同等的價值你可以選擇駕駛寶駿310去各種你想去的地方,這才是年輕人該做的事。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準