こんにちはHabr! 最近、私はGO言語の開発に非常に興味を持っています。 エレガントで表現力豊かなプログラミング言語。 私は長い間何か役に立つことをしたいと思っていました。 私の仕事の詳細については、PACSのDACOM画像の医療アーカイブを使用する必要があります。
Github: github.com/Loafter/dtools
Linuxバージョン-amd64 : github.com/Loafter/dtools/releases/download/1.0/dcmjsser
次の標準操作を実行できる(ブラックジャック..)Webインターフェースを備えたdicomクライアントを作成する時が来たと判断しました。
- Dicom ping;
- 研究のダウンロード。
- 研究ダウンロード、
- 詳細で検索するだけでなく
(それぞれc-echo、c-move、c-store、c-find)。
GrassRoot SDKライブラリーがdicomライブラリーとして選択されました。 クライアントはタスクを並列化します。 go言語はこれによく適合しています。
同様の作業シナリオがhabrahabr.ru/post/198150によって記述されました 。
シナリオは少し異なります。
dicomサービスのタスクを受け取り、実行の可能性をチェックし、それらを非同期に実行する特定のタスクバランサーがあります。 1000個のタスクが並行して実行される状況を回避するために、アクティブなタスクとスリープ状態のタスクがあるようにタスクキューを実装します。 デフォルトでは、10個のタスクのみがアクティブになります。 そうでなければ、バランサーがまったくなくても、制御なしで愚かにも1000個のタスクを並行して実行できます。
すべてのバランサーコードはjob_ballancer.goファイルにあります。
最初は、ハンドラーインターフェイスの説明です。 作業が正常に完了した場合、エラーが返された場合、およびタスクを処理するプロセス。
type JobDispatcher interface { Dispatch(interface{}) (interface{}, error) } type ErrDispatcher interface { DispatchError(FaJob) error } type CompDispatcher interface { DispatchSuccess(CompJob) error }
ディスパッチャをインスタンス化するとき、適切なハンドラでディスパッチャを初期化します。
srv.jbBal.Init(&srv.dDisp, srv, srv) // type JobBallancer struct { jChan chan interface{} // acJob map[string]Job // slJob map[string]Job // errDisp ErrDispatcher // error jobDisp JobDispatcher // compDisp CompDispatcher // JbDone sync.WaitGroup // aJobC int // () } // func (jbal *JobBallancer) Init(jdis JobDispatcher, cmd CompDispatcher, erd ErrDispatcher) { jbal.errDisp = erd jbal.jobDisp = jdis jbal.compDisp = cmd jbal.acJob = make(map[string]Job) jbal.slJob = make(map[string]Job) jbal.aJobC = 10 jbal.jChan = make(chan interface{}) go jbal.takeJob() // // log.Println("info: job ballancer inited") } . , .. , takeJob . func (jbal *JobBallancer) PushJob(jdat interface{}) error { if jbal.checkInit() { return errors.New("error: JobChan is not inited") } uid := genUid() job := Job{JobId: uid, Data: jdat} jbal.jChan <- job return nil } func (jbal *JobBallancer) takeJob() { for { // recivedTask := <-jbal.jChan log.Println("info: job taken") switch job := recivedTask.(type) { case TermJob: // log.Println("info: recive terminate dispatch singal") return case Job: // ( ) if len(jbal.acJob) < jbal.aJobC { jbal.JbDone.Add(1) jbal.addActiveJob(job) go jbal.startJob(job) log.Println("info: normal dispatch") } else { jbal.addSleepJob(job) jbal.JbDone.Add(1) log.Println("info: attend maximum active job") } case CompJob: // if err := jbal.compDisp.DispatchSuccess(job); err != nil { log.Println("error: failed dispatch success" + job.Job.JobId) } // jbal.removeJob(job.Job.JobId) jbal.JbDone.Done() jbal.resumeJobs() case FaJob: // if err := jbal.errDisp.DispatchError(job); err != nil { log.Println("error: failed dispatch error" + job.Job.JobId) } // jbal.removeJob(job.Job.JobId) jbal.JbDone.Done() jbal.resumeJobs() default: log.Fatalln("error: unknown job type") jbal.JbDone.Done() } } } // func (jbal *JobBallancer) removeJob(jid string) error { if _, isFind := jbal.acJob[jid]; isFind { delete(jbal.acJob, jid) } else { return errors.New("error: can't remove job because job with id not found") } return nil } // , , func (jbal *JobBallancer) TerminateTakeJob() error { if jbal.checkInit() { return errors.New("error: is not inited") } jbal.JbDone.Wait() jbal.jChan <- TermJob{} close(jbal.jChan) if len(jbal.acJob) > 0 { return errors.New("error: list job is not empty") } log.Println("info: greacefully terminate take job") return nil }
残りの補助機能は考慮しません。 完全なコードを表示できます
github.com/Loafter/dtools/blob/master/dcmjsser/job_ballancer.go
コードは複雑ではないという事実にもかかわらず、私は長い間考えていました。 それでも、信頼性を確認するために、数十のタスクの負荷テストを実装しました。
testJobDispatcher := TestJobDispatcher{} testErrorDispatcher := TestErrorDispatcher{} testSuccessDispatcher := TestCompletedDispatcher{} jobBallancer := JobBallancer{} jobBallancer.Init(&testJobDispatcher, &testSuccessDispatcher, &testErrorDispatcher) for i := 0; i < 40; i++ { jobBallancer.PushJob("data: " + strconv.Itoa(i)) } jobBallancer.TerminateTakeJob()
彼はうまく働いた。 すべてのタスクが完了し、すべてのタスクが完了するとTerminateTakeJob関数が完了しました。 完了したタスクを制御するには、sync.WaitGroup JbDone同期オブジェクトを使用して、完了した作業の数をカウントします。 上で述べたように、バランサーコードは普遍的であり、バランサーが異なる動作をするためには、適切なハンドラーでインスタンス化するだけです。
以前の記事のように)(http://habrahabr.ru/post/247727/)アプリケーションインターフェイスをWebインターフェイスの形式で実装しました。
テストには、パブリックdicomアーカイブ213.165.94.158:11112を使用しました。 直接IPがあり、クライアント側でポート11112が開いている場合は、そこから調査をダウンロードできます。
Linuxの動作バージョンをビルドできましたが、残念ながら未亡人の下でビルドできませんでした。 grassrootライブラリは正常にビルドされましたが、アプリケーション自体をリンクするときにエラーが発生します。
cmd / ld:不正な形式のPEファイル:PEセクションの予期しないフラグ。
このエラーについてはgithub.com/golang/go/issues/4069に多くのことが書かれています。
残念ながら、私はアセンブリの複雑さにあまり詳しくないので、バージョンはLinuxでのみ判明しました。 たぶん「ハブラ効果」はこの問題を根本から取り除くでしょう。 それがどのように機能するかを確認したいWindowsユーザーのために、CoreOS(https://yadi.sk/d/y81KC-tyfar6A)に基づいて仮想マシンを準備しました。 デモ機では、dicomクライアントはsystemdサービスとして機能します。
必要に応じて、たとえば、さまざまなdicomノードから調査をダウンロードし、zipアーカイブにアップロードしてダウンロードするサービスを実装できます。 GUIと同様に、jsonメッセージを使用してサービスを管理できます。
そして、あなたは私がしたことをすることができます:いくつかのhtml5ベースのWebビューアーをアプリケーションに固定します。