1. 程式人生 > >docker pull命令實現與映象儲存(3)

docker pull命令實現與映象儲存(3)

在《pull命令實現與映象儲存(1)》和《pull命令實現與映象儲存(2)》我們分析pull命令在docker客戶端的實現部分,最後我們瞭解到客戶端將結構化引數傳送到服務端的URL:/images/create。接下來我們將分析在服務端的實現部分,將從該URL入手。
我們在《dockerd路由和初始化》中瞭解了docker的API是如何初始化的,實現在docker\cmd\dockerd\daemon.go我們回顧下:

func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
    decoder := runconfig.ContainerDecoder
{} routers := []router.Router{} // we need to add the checkpoint router before the container router or the DELETE gets masked routers = addExperimentalRouters(routers, d, decoder) routers = append(routers, []router.Router{ container.NewRouter(d, decoder), //關於容器命令的API image.NewRouter
(d, decoder), //關於鏡命令的API systemrouter.NewRouter(d, c), //關於繫命令的API api/server/router/system被重新命名了 volume.NewRouter(d), //關於卷命令的API build.NewRouter(dockerfile.NewBuildManager(d)),//關於構建命令的API swarmrouter.NewRouter(c), }...) if d.NetworkControllerEnabled
() { routers = append(routers, network.NewRouter(d, c)) } s.InitRouter(utils.IsDebugEnabled(), routers...) }

可以看到有關於映象的API “ image.NewRouter(d, decoder)”,實現在docker\api\server\router\image\image.go:

// NewRouter initializes a new image router
func NewRouter(backend Backend, decoder httputils.ContainerDecoder) router.Router {
    r := &imageRouter{
        backend: backend,
        decoder: decoder,
    }
    r.initRoutes()
    return r
}

// Routes returns the available routes to the image controller
func (r *imageRouter) Routes() []router.Route {
    return r.routes
}

// initRoutes initializes the routes in the image router
func (r *imageRouter) initRoutes() {
    r.routes = []router.Route{
        // GET
        router.NewGetRoute("/images/json", r.getImagesJSON),
        router.NewGetRoute("/images/search", r.getImagesSearch),
        router.NewGetRoute("/images/get", r.getImagesGet),
        router.NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
        router.NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
        router.NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
        // POST
        router.NewPostRoute("/commit", r.postCommit),
        router.NewPostRoute("/images/load", r.postImagesLoad),
        router.Cancellable(router.NewPostRoute("/images/create", r.postImagesCreate)),
        router.Cancellable(router.NewPostRoute("/images/{name:.*}/push", r.postImagesPush)),
        router.NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
        router.NewPostRoute("/images/prune", r.postImagesPrune),
        // DELETE
        router.NewDeleteRoute("/images/{name:.*}", r.deleteImages),
    }
}

可以看到函式呼叫過程:NewRouter->initRoutes,且我們上面提到的pull命令的API:/images/create赫然在列。這裡已經很明瞭了,pull命令在服務端將由r.postImagesCreate處理,實現在docker\api\server\router\image\image_routes.go,我們分析下該函式:


// Creates an image from Pull or from Import
func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    if err := httputils.ParseForm(r); err != nil {
        return err
    }
    // Calling POST /v1.25/images/create?fromImage=gplang&tag=latest
    var (
        image   = r.Form.Get("fromImage")
        repo    = r.Form.Get("repo")
        tag     = r.Form.Get("tag")
        message = r.Form.Get("message")
        err     error
        output  = ioutils.NewWriteFlusher(w)
    )
    defer output.Close()
    //設定迴應的http頭,說明資料是json
    w.Header().Set("Content-Type", "application/json")
      //映象名不存在
    if image != "" { //pull
        metaHeaders := map[string][]string{}
        for k, v := range r.Header {
            if strings.HasPrefix(k, "X-Meta-") {
                metaHeaders[k] = v
            }
        }

        authEncoded := r.Header.Get("X-Registry-Auth")
        authConfig := &types.AuthConfig{}
        if authEncoded != "" {
            authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
            if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
                // for a pull it is not an error if no auth was given
                // to increase compatibility with the existing api it is defaulting to be empty
                authConfig = &types.AuthConfig{}
            }
        }
        // Backend is all the methods that need to be implemented
        // to provide image specific functionality(功能).
        //在Daemon型別實現了該API介面,在docker/daemon/image_pull.go
        err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output)


    } else { //import
        src := r.Form.Get("fromSrc")
        // 'err' MUST NOT be defined within this block, we need any error
        // generated from the download to be available to the output
        // stream processing below
        err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"])
    }
    if err != nil {
        if !output.Flushed() {
            return err
        }
        sf := streamformatter.NewJSONStreamFormatter()
        output.Write(sf.FormatError(err))
    }

    return nil
}

—————————————2016.12.09 22:03 更新—————————————————

可以看到主要是從http引數中解析出映象名和tag,分了有映象名和無映象名兩個分支。我們拉取映象時我們傳入了ubuntu這個映象名,所以走if分支(image 為空的情況不知是什麼情況,暫時不去深究)。從上面的程式碼中我們可以看到以映象名,tag以及授權資訊等引數呼叫函式s.backend.PullImage。可是backend這個是什麼呢?backend是介面Backend的例項,我們要找其實現類。

type Backend interface {
    containerBackend
    imageBackend
    importExportBackend
    registryBackend
}

我們回到映象相關的API初始化的程式碼:

// NewRouter initializes a new image router
func NewRouter(backend Backend, decoder httputils.ContainerDecoder) router.Router {
    r := &imageRouter{
        backend: backend,
        decoder: decoder,
    }
    r.initRoutes()
    return r
}

可以看到是NewRouter的時候傳入的,我們看下呼叫程式碼,在docker\cmd\dockerd\daemon.go的 initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) 函式,有:

image.NewRouter(d, decoder),

我們再往上看initRouter的呼叫程式碼,在檔案docker\cmd\dockerd\daemon.go的star函式:

    initRouter(api, d, c)

原來是這裡的d,再看下d是如何來的:

    d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)

返回的是一個Daemon物件指標。這下我們我們可以知道s.backend.PullImage實際上呼叫的是Daemon的成員PullImage函式。實際上Daemon不僅實現了image相關的介面,而是實現了所有docker的操作的介面。往後我們分析的介面都可以在那裡找到實現。我現在去看下PullImage函式的實現,在檔案docker\daemon\image_pull.go:

// PullImage initiates a pull operation. image is the repository name to pull, and
// tag may be either empty, or indicate a specific tag to pull.
func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
    // Special case: "pull -a" may send an image name with a
    // trailing :. This is ugly, but let's not break API
    // compatibility.
    image = strings.TrimSuffix(image, ":")
    //fromImage=gplang&tag=latest
    //name格式: xxx:yyy | @zzz  xxx 代表映象名,如果沒有加上倉庫地址:docker.io,會使用預設的倉庫地址, yyy :代表版本 zzz: 代表摘要
    ref, err := reference.ParseNamed(image)
    if err != nil {
        return err
    }
    //如果tag不為空,則要看標籤還是摘要,或者什麼也不是
    if tag != "" {
        // The "tag" could actually be a digest.
        var dgst digest.Digest
        dgst, err = digest.ParseDigest(tag)
        if err == nil {
            ref, err = reference.WithDigest(ref, dgst)
        } else {
            ref, err = reference.WithTag(ref, tag)
        }
        if err != nil {
            return err
        }
    }

    return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream)
}

是不是看到熟悉的東西,對這裡又將映象名等解析了一遍,如果我們傳入的是tag就得到一個reference.NamedTagged物件ref。然後交給pullImageWithReference:

func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
    // Include a buffer so that slow client connections don't affect
    // transfer performance.
    progressChan := make(chan progress.Progress, 100)

    writesDone := make(chan struct{})

    ctx, cancelFunc := context.WithCancel(ctx)

    go func() {
        writeDistributionProgress(cancelFunc, outStream, progressChan)
        close(writesDone)
    }()
        //注意這裡有很多重要的介面
    imagePullConfig := &distribution.ImagePullConfig{
        MetaHeaders:      metaHeaders,
        AuthConfig:       authConfig,
        ProgressOutput:   progress.ChanOutput(progressChan),
        RegistryService:  daemon.RegistryService,//預設regist服務介面實現的例項
        ImageEventLogger: daemon.LogImageEvent,
        MetadataStore:    daemon.distributionMetadataStore,
        ImageStore:       daemon.imageStore,
        ReferenceStore:   daemon.referenceStore,
        DownloadManager:  daemon.downloadManager,
    }

    err := distribution.Pull(ctx, ref, imagePullConfig)
    close(progressChan)
    <-writesDone
    return err
}

這裡再呼叫distribution.Pull,還有就是要注意這裡構造了一個imagePullConfig 物件,裡面包含了很多拉取映象時要用到的介面(我們暫且先記下,後面分析到的時候再回過頭來看)。如此繞來繞去,想必是有點暈頭轉向了。在繼續之前我們先說下docker的程式碼風格,如果瞭解了docker的程式碼風格,我想我們就知道docker解決問題的套路,這樣即使我們沒有完全掌握docker原始碼,我們也可以根據我們看過的docker原始碼推測出其他邏輯。我們先就以即將要分析的distribution.Pull中的Service為例。
這裡寫圖片描述
可以看到在檔案docker\registry\service.goService中定義了Service介面,介面中有一些映象倉庫相關的方法。接著在介面定義的檔案中定義了Service介面的預設實現。他們是怎麼關聯在一起的呢(不是指go語法上的關聯)。一般在這個檔案中為定義NewXXX的方法,該方法返回的就是了介面實現物件的指標:

// NewService returns a new instance of DefaultService ready to be
// installed into an engine.
func NewService(options ServiceOptions) *DefaultService {
    return &DefaultService{
        config: newServiceConfig(options),
    }
}

明白了這個套路,我們接著分析distribution.Pull:

/ Pull initiates a pull operation. image is the repository name to pull, and
// tag may be either empty, or indicate a specific tag to pull.
func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
    // Resolve the Repository name from fqn to RepositoryInfo

    //在/docker/registry/config.go的 newServiceConfig初始化倉庫地址和倉庫映象地址,其中有官方的和通過選項insecure-registry自定義的私有倉庫,實質是通過IndexName找到IndexInfo,有用的也只有IndexName
    //這裡的imagePullConfig.RegistryService為daemon.RegistryService,也即是docker\registry\service.go的DefaultService
    //初始化時,會將insecure-registry選項和registry-mirrors存入ServiceOptions,在NewService函式被呼叫時,作為參入傳入

    //repoInfo為RepositoryInfo物件,其實是對reference.Named物件的封裝,添加了映象成員和官方標示
    repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
    if err != nil {
        return err
    }

    // makes sure name is not empty or `scratch`
    //為了確保不為空或?
    if err := ValidateRepoName(repoInfo.Name()); err != nil {
        return err
    }
    // APIEndpoint represents a remote API endpoint
    // /docker/cmd/dockerddaemon.go----大約125 和 248
    //如果沒有映象倉庫伺服器地址,預設使用V2倉庫地址registry-1.docker.io
    //Hostname()函式來源於Named

    //實質上如果Hostname()返回的是官方倉庫地址,則endpoint的URL將是registry-1.docker.io,如果有映象則會新增映象作為endpoint
    // 否則就是私有地址的兩種型別:http和https

    //V2的介面具體程式碼在Zdocker\registry\service_v2.go的函式lookupV2Endpoints
    //
    logrus.Debugf("Get endpoint from:%s", repoInfo.Hostname())
    endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(repoInfo.Hostname())
    if err != nil {
        return err
    }

    var (
        lastErr error

        // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
        // By default it is false, which means that if an ErrNoSupport error is encountered, it will be saved in lastErr.
        // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
        // any subsequent ErrNoSupport errors in lastErr.
        // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
        // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
        // error is the ones from v2 endpoints not v1.
        discardNoSupportErrors bool

        // confirmedV2 is set to true if a pull attempt managed to
        // confirm that it was talking to a v2 registry. This will
        // prevent fallback to the v1 protocol.
        confirmedV2 bool

        // confirmedTLSRegistries is a map indicating which registries
        // are known to be using TLS. There should never be a plaintext
        // retry for any of these.
        confirmedTLSRegistries = make(map[string]struct{})
    )
    //如果設定了映象伺服器地址,且使用了官方預設的映象倉庫,則endpoints包含官方倉庫地址和映象伺服器地址,否則就是私有倉庫地址的http和https形式
    for _, endpoint := range endpoints {
        logrus.Debugf("Endpoint API version:%d", endpoint.Version)
        if confirmedV2 && endpoint.Version == registry.APIVersion1 {
            logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
            continue
        }

        if endpoint.URL.Scheme != "https" {
            if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
                logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
                continue
            }
        }

        logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
        //針對每一個endpoint,建立一個Puller,newPuller會根據endpoint的形式(endpoint應該遵循restful api的設計,url中含有版本號),決定採用version1還是version2版本
        //imagePullConfig是個很重要的物件,包含了很多映象操作相關的物件
        puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
        if err != nil {
            lastErr = err
            continue
        }
        if err := puller.Pull(ctx, ref); err != nil {
            // Was this pull cancelled? If so, don't try to fall
            // back.
            fallback := false
            select {
            case <-ctx.Done():
            default:
                if fallbackErr, ok := err.(fallbackError); ok {
                    fallback = true
                    confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
                    if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
                        confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
                    }
                    err = fallbackErr.err
                }
            }
            if fallback {
                if _, ok := err.(ErrNoSupport); !ok {
                    // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
                    discardNoSupportErrors = true
                    // append subsequent errors
                    lastErr = err
                } else if !discardNoSupportErrors {
                    // Save the ErrNoSupport error, because it's either the first error or all encountered errors
                    // were also ErrNoSupport errors.
                    // append subsequent errors
                    lastErr = err
                }
                logrus.Errorf("Attempting next endpoint for pull after error: %v", err)
                continue
            }
            logrus.Errorf("Not continuing with pull after error: %v", err)
            return err
        }

        imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
        return nil
    }

    if lastErr == nil {
        lastErr = fmt.Errorf("no endpoints found for %s", ref.String())
    }

    return lastErr
}

程式碼比較多,總結起來就是映象倉庫資訊repoInfo–>端點資訊endpoints–>puller拉取映象。這是應該有很多疑問,映象倉庫資訊是個什麼東西?端點資訊是什麼?如何拉取?我們逐個分析。首先我們看下映象倉庫資訊的定義以及例子(在docker\api\types\registry\registry.go):

type RepositoryInfo struct {
    reference.Named
    // Index points to registry information
    Index *registrytypes.IndexInfo
    // Official indicates whether the repository is considered official.
    // If the registry is official, and the normalized name does not
    // contain a '/' (e.g. "foo"), then it is considered an official repo.
    //表示是否官方的地址,實際上只要拉取映象時只傳入映象的資訊
    //而沒有倉庫的資訊,就會使用官方預設的倉庫地址,這時Official成員就是true
    Official bool
}

// RepositoryInfo Examples:
// {
//   "Index" : {
//     "Name" : "docker.io",
//     "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"],
//     "Secure" : true,
//     "Official" : true,
//   },
//   "RemoteName" : "library/debian",
//   "LocalName" : "debian",
//   "CanonicalName" : "docker.io/debian"
//   "Official" : true,
// }
//
// {
//   "Index" : {
//     "Name" : "127.0.0.1:5000",
//     "Mirrors" : [],
//     "Secure" : false,
//     "Official" : false,
//   },
//   "RemoteName" : "user/repo",
//   "LocalName" : "127.0.0.1:5000/user/repo",
//   "CanonicalName" : "127.0.0.1:5000/user/repo",
//   "Official" : false,
// }

結合程式碼中的註釋,我想我們可以知道RepositoryInfo其實是就是包含了所有可用倉庫地址(倉庫映象地址也算)的結構.
———————2016.12.10 22:31更新————————————-
好了,現在我們看下這個結構式如何被填充的.RegistryService實際上是DefaultService.看下imagePullConfig.RegistryService.ResolveRepository(ref),實現在docker\registry\service.go:

func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
    return newRepositoryInfo(s.config, name)
}

// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
    // newIndexInfo returns IndexInfo configuration from indexName
    index, err := newIndexInfo(config, name.Hostname())
    if err != nil {
        return nil, err
    }
    official := !strings.ContainsRune(name.Name(), '/')
    return &RepositoryInfo{name, index, official}, nil
}

// newIndexInfo returns IndexInfo configuration from indexName
func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
    var err error
    indexName, err = ValidateIndexName(indexName)
    if err != nil {
        return nil, err
    }

    // Return any configured index info, first.
    //config是在上面NewService函式中通過傳入的ServiceOptions選項生成的
    //serviceConfig,在docker\registry\config.go的InstallCliFlags被初始化
    //index其實就是映象的倉庫地址,或倉庫的映象地址
        //
    if index, ok := config.IndexConfigs[indexName]; ok {
        return index, nil
    }

    // Construct a non-configured index info.
    index := &registrytypes.IndexInfo{
        Name:     indexName,
        Mirrors:  make([]string, 0),
        Official: false,
    }
    index.Secure = isSecureIndex(config, indexName)
    return index, nil
}

三個成員,Name就是根據引數(ubuntu:latest)解析出來的Named物件,Official 如果我們只傳入類似ubuntu:latest則使用官方預設的,該成員就是true,就剩下Index了.可以看到Index來源於config.IndexConfigs.那config.IndexConfigs是什麼呢?容易發現config.IndexConfigs來源於DefaultService的config。DefaultService的config則來源於NewService時的ServiceOptions。先看下ServiceOptions,實現在docker\registry\config.go:

// ServiceOptions holds command line options.
type ServiceOptions struct {
    Mirrors            []string `json:"registry-mirrors,omitempty"`
    InsecureRegistries []string `json:"insecure-registries,omitempty"`

    // V2Only controls access to legacy registries.  If it is set to true via the
    // command line flag the daemon will not attempt to contact v1 legacy registries
    V2Only bool `json:"disable-legacy-registry,omitempty"`
}

相關推薦

docker pull命令實現映象儲存3

在《pull命令實現與映象儲存(1)》和《pull命令實現與映象儲存(2)》我們分析pull命令在docker客戶端的實現部分,最後我們瞭解到客戶端將結構化引數傳送到服務端的URL:/images/create。接下來我們將分析在服務端的實現部分,將從該URL入

虛擬機器下安裝docker,並且ssh的連線centos6--docker筆記

當前環境:win10->vmware->centos6.5(86_64) 不用管太多,先安裝docker。後面會有注意點。 1. yum install http://mirrors.yun-idc.com/epel/6/i386/epel-release-

Linux命令shell指令碼20--例項:備份檔案

建立一個配置檔案,該檔案包含了要備份的每個目錄或檔案 $ cat files_backup_config /Users/chenhong/Desktop/shell_workspace/my

Linux命令shell指令碼12--控制指令碼

處理訊號 Ctrl+C組合鍵會產生SIGINT訊號,會停止shell中當前執行的程序 Crtl+Z組建鍵會產生SIGTSTP訊號,停止shell中執行的任何程序,停止程序會讓程式繼續保留在記憶體中,

Linux命令shell指令碼14--在函式中使用陣列

陣列變數和函式 傳遞陣列給函式 function testit(){ local new_array; new_array=(`echo [email

Docker埠對映及建立映象演示--技術流ken

前言 在上一篇部落格《Docker介紹及常用操作演示--技術流ken》中,已經詳細介紹了docker相關內容以及有關映象和容器的使用命令演示。 現在我們已經可以自己下載映象,以及建立容器了。 但是現在有這樣一個問題,我們建立的容器可以被其他人或者另外一臺伺服器訪問嗎? 基於上一篇部落格中容器的

十二 web服務javaweb結合3

pack ppi web工程 配置 time star con http ont 一、需求 上一章節雖然將webservice和web項目綁定在了一起,但是還是不能共同一個端口,本章講解webservice和web項目綁定且共同端口。 二、案例   2.1  創建w

25、【opencv入門】輪廓查找繪制3——凸包

ise 技術分享 bool and s函數 span spa push_back 返回 一簡介 1、凸包   凸包(Convex Hull)是一個計算機幾何圖形學中的概念, 簡單來說, 給定二維平面點集, 凸包就是能夠將最外層的點連接起來構成的凸多邊形, 它能夠包含點集中所

pythonzmq系列3

本篇部落格將介紹zmq應答模式,所謂應答模式,就是一問一答,規則有這麼幾條         1、 必須先提問,後回答         2、 對於一個提問,只能回答一次

android的資料儲存3(LitePal)

在上一章的SQLiteDatebase來操作資料庫好用嗎?不同的人有不同的答案,接下來你將接觸一個開源庫LitePal,它採用了物件關係對映的(ORM)的模式,並將我們平常用到的資料庫功能進行封裝,使用一行sql語句就可以完成各種建表和增刪改查的操作。   一、配置LitePal

C語言面向物件程式設計:虛擬函式多型3

 在《 C++ 程式設計思想》一書中對虛擬函式的實現機制有詳細的描述,一般的編譯器通過虛擬函式表,在編譯時插入一段隱藏的程式碼,儲存型別資訊和虛擬函式地址,而在呼叫時,這段隱藏的程式碼可以找到和實際物件一致的虛擬函式實現。     我們在這裡提供

python之類對象3

target 實例化 定義 方法 int () a* 創建 但是 4. 類的初始化函數__init__(): 本章參考:https://blog.csdn.net/hellocsz/article/details/82795514 原作者: hellocsz 總結

BPMN-JSAngular整合3

    前面兩章已將原生BPMN 與Angular整合完成,這章介紹一下如何定製左側的調色盤,使其新增或減少可拖拽生成的元素。     有時業務開發需要,如只需要定製工作流中的部分任務,同時,有些元素如Service Task原生的操作太隱蔽,需要直接放到調色盤上面可以操作

3章 處理機排程死鎖3

LLF演算法例題: 一個實時系統中兩個週期型實時任務A、B 週期 執行時間長 任務A:20ms, 10ms; 任務B:50ms, 25ms; t=0時 :A1的鬆弛度為20-10=10ms,B1的鬆弛度為50-25=25ms,所以執行A1任務; t=10時

基於雙端堆實現的優先順序佇列3:外觀

   本文講述雙端堆的5個公開泛型操作演算法:make_dheap(原位構造雙端堆)、push_dheap(插入元素)、pop_max_dheap(刪除最大元素)、pop_min_dheap(刪除最小元素),is_dheap(堆驗證),每個演算法都提供<小於運算子和仿函式比較2個版本,要注意

資料結構演算法複習3—— 線段樹

http://www.cppblog.com/MemoryGarden/archive/2009/04/11/79565.aspx http://www.notonlysuccess.com/?p=59 http://edu.codepub.com/2009/1125/18163.php POJ 2104,

python3理解寫程式碼基本型別3

3.1 什麼是程式碼 什麼是寫程式碼 程式碼是現實世界事物在計算機世界中的對映 寫程式碼是將現實世界中的事物用計算機語言來描述 程式碼和寫程式碼就像畫家畫畫,攝影師拍照 如果我們需要在一個世界中描述另外一個世界裡面的一些事物,我們就需要一些基本的元素和素材,在計

SVD LSI教程3: 計算矩陣的全部奇異值

/**********************作者資訊****************/ Dr. E. Garcia Mi Islita.com Email | Last Update: 01/07/07 /**********************

Android的程序執行緒3執行緒安全問題

當一個程式啟動的時候,系統會為程式建立一個名為main的執行緒。這個執行緒重要性在於它負責把事件分發給適合的使用者元件,這些事件包括繪製事件。並且這個執行緒也是你的程式與Android UI工具包中的元件(比如android.widget和android.view包中的元件

基於Redis實現分散式訊息佇列3

1、Redis是什麼鬼? Redis是一個簡單的,高效的,分散式的,基於記憶體的快取工具。 假設好伺服器後,通過網路連線(類似資料庫),提供Key-Value式快取服務。 簡單,是Redis突出的特色。 簡單可以保證核心功能的穩定和優異。 2、效能