質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.48%

  • Go

    655questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

go言語でタイマーAPIの作成について

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 729

ts.tubasa

score 111

Go言語と、Framework [echo]を利用してAPIを作成しております。

特定のデータごとのタイマーを、Web APIを利用して実装したいと考えております。

処理の流れとしましては下記のような実装を考えております。

1.タイマー開始用のAPIにパラメーター付きでアクセスし、goroutineで、別プロセスを起動
2.タイマー停止用のAPIにパラメーター付きでアクセスし、既に動いているgoroutineを検知し、プロセスを停止

実際に実装を行い別プロセス毎に、動かせているような実装はできました?…。

しかし、2の処理を行うと、プログラムが停止しません。

そもそも、実現しようとしていることに無理があるのかもしれませんが、
アドバイスを頂けると幸いです。

大変、お手数をおかけしますが、宜しくお願い致します。

▼main.go

package main

import (
    "sample/handler"

    _ "github.com/go-sql-driver/mysql"
    "github.com/labstack/echo"
    "sample/data"
)

func main() {

    // トップディレクトリ
    e := echo.New()

    ch := make(chan bool)

    // 製品を監視するタイマー
    var ids = make([]*data.Ids, 0)

    e.GET("start", handler.Start(ch, ids))
    e.GET("stop", handler.Stop(ch, ids))

    e.Logger.Fatal(e.Start(":1323"))
}

 

▼handler/start.go

package handler

import (
    "github.com/labstack/echo"
    "net/http"
    "fmt"
    "time"
    "strconv"
    "sample/data"
)

func Start(ch chan bool, ids []*data.Ids) echo.HandlerFunc {

    return func(c echo.Context) error {

        id := c.QueryParam("id")
        limit, _ := strconv.Atoi(c.QueryParam("limit"))

        go func() {

            //result = append(result, &(data.Ids{Id: v.Id}))
            ids = append(ids, &(data.Ids{Id:id}))

            t := time.NewTicker(3 * time.Second) // 1秒おきにチェック
            n := 0
            for {
                select {
                case <-t.C:
                    if ( limit < n) {
                        fmt.Print("over")
                    }
                    fmt.Print("id =" + id)
                    fmt.Println(n)
                    n++
                case <-ch:
                    fmt.Println("stop")

                    t.Stop()
                }
            }

        }()

        return c.JSON(http.StatusCreated, "ok")
    }

}

▼handler/stop.go

package handler

import (
    "github.com/labstack/echo"
    "net/http"
    "sample/data"
    "fmt"
)

func Stop(ch chan bool, ids []*data.Ids) echo.HandlerFunc {

    return func(c echo.Context) error {
        id := c.QueryParam("id")
        arrayRemove(ids, id)

        ch <- true
        return c.JSON(http.StatusCreated, "ok")
    }

}

func arrayRemove(arr []*data.Ids, str string) []*data.Ids {

    fmt.Println("remove")

    result := make([]*data.Ids, 0)
    for _, v := range arr {
        if v.Id != str {
            result = append(result, &(data.Ids{Id: v.Id}))
            fmt.Println(v.Id)
        }
    }
    return result
}

▼sample/data/ids.go

package data

type Ids struct {
    Id string
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

タイマーがID単位で発行されていますが、停止が1つのチャネルで行われているのでもし id=1 と id=2 で別のタイマーが作られたとしても ch を先にどちらが読むかは分かりません(タイミング次第です)。
また、id=1 に関するタイマーがリクエスト毎に作られてしまいますので、期待しない動作になります。
さらに goroutine のループが抜けていませんのでリクエストする度に goroutine が追加されます。

指摘ばかりだと分かりにくいかもしれませんので例を。

▼ids.go

package main

import "sync"
import "time"

type IdInfo struct {
    Id string
    t  *time.Ticker
}

type IdMap map[string]*IdInfo

var mu sync.Mutex

func (idmap IdMap) StartTimer(id string) <-chan time.Time {
    mu.Lock()
    defer mu.Unlock()

    info, ok := idmap[id]
    if ok && info.t != nil {
        // 既にあればどこかでループが回っているはず
        return nil
    }
    idmap[id] = &IdInfo{Id: id, t: time.NewTicker(time.Second)}
    return idmap[id].t.C
}

func (idmap IdMap) StopTimer(id string) {
    mu.Lock()
    defer mu.Unlock()

    idinfo, ok := idmap[id]
    if !ok {
        return
    }
    idinfo.t.Stop()
    idinfo.t = nil
}

IdMap は全てのIDを管理するmapで、StartTimer/StopTimer に与えた引数に従って各々のタイマーを生成します。無ければ作ります。StopTimer で nil クリアしており完全にタイマーが停止するまで次のタイマーは作られません。

▼start.go

package main

import (
    "fmt"
    "net/http"
    "strconv"

    "github.com/labstack/echo"
)

func Start(ids IdMap) echo.HandlerFunc {

    return func(c echo.Context) error {
        id := c.QueryParam("id")
        if id == "" {
            return c.JSON(http.StatusBadRequest, "ng")
        }
        limit, err := strconv.Atoi(c.QueryParam("limit"))
        if err != nil {
            limit = 3
        }
        t := ids.StartTimer(id)
        if t == nil {
            return c.JSON(http.StatusBadRequest, "ng")
        }

        go func() {
            defer ids.StopTimer(id)

            n := 0
            for {
                _, ok := <-t
                if !ok {
                    break
                }
                if limit < n {
                    fmt.Printf("id = %v, over\n", id)
                    break
                }
                fmt.Printf("id = %v, count = %v\n", id, n)
                n++
            }
        }()

        return c.JSON(http.StatusCreated, "ok")
    }

}


既にタイマーが起動していれば ng を返す様にしています。

▼stop.go

package main

import (
    "net/http"

    "github.com/labstack/echo"
)

func Stop(ids IdMap) echo.HandlerFunc {

    return func(c echo.Context) error {
        id := c.QueryParam("id")

        ids.StopTimer(id)

        return c.JSON(http.StatusCreated, "ok")
    }

}

▼main.go

package main

import "github.com/labstack/echo"

func main() {
    // トップディレクトリ
    e := echo.New()

    // 製品を監視するタイマー
    var ids = make(IdMap)

    e.GET("start", Start(ids))
    e.GET("stop", Stop(ids))

    e.Start(":1323")
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

同じタグがついた質問を見る

  • Go

    655questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。