
Daniel
29.01.2017
10:08:39
нет, есть на github
но можно просто секретный гист, и ссылку в личку
но давайте погодим пока с кодом
может быть, до него дело не дойдет

Google

Daniel
29.01.2017
10:09:20
итак
у нас есть функция обработки http запроса
ей на вход попадает собственно запрос и - замыканием - map[string]bool
где тут пространство для глобального контекста?
и атомарных его изменений

Slach
29.01.2017
10:10:57
нет ей н вход попадает
указатель *web.Request
в котором есть
req.Form как url.Values map[string][]string

Daniel
29.01.2017
10:11:19
ну - да, это и есть сам запрос

Slach
29.01.2017
10:11:31
сама функция Collect судя по коду фреймворка запускается в горутине

Daniel
29.01.2017
10:11:58
пока все понятно
там в Collect бесконечный цикл, или она отработала и завершилась?
отойду опять, минут на 40

Slach
29.01.2017
10:25:42
Collect должна отработать и завершиться
идем дальше
есть необходимость
из двух горутин с Collect
на вход которым подали пересекающийся набор ключей из запроса
сформировать
CSV файл
в кототром первой строкой заголовском служит перечень полей из субд
(маппинг полей формы в поля СУБД отдельная песня за рамками задачи)
а дальше tab separated данные
итого
если нам пришло два одинаковых набора полей
то CSV файл должен быть один
если два разных
то CSV файлов два
ну вы вот посоветовали завернуть все это в отдельную горутину
я сейчас пытаюсь сгородить такую конструкцию
https://play.golang.org/p/avQlCmcx2h
посмотрим что получится в бенмарке

Daniel
29.01.2017
10:26:24
так

Google

Daniel
29.01.2017
10:27:11
попарсили form - и результат через канал в единственную горутину, и пусть уже она думает о совокупленииъ

Slach
29.01.2017
10:41:33
ну вроде да, выглядит как то так =))
блин ну вот фигня какая то...
теперь оно вообще даже не пытается CSV файлы писаьт
func (g *GA2ClickHouse) Collect(rw web.ResponseWriter, req *web.Request) {
err := req.ParseForm()
if err == nil {
ParseTimestamp(req)
ParseUserAgent(req)
ParseGeoip(req, g)
go func(req *web.Request) {
g.buffered_data <- BufferedData{req: req}
}(req)
}
rw.Header().Set("Access-Control-Allow-Origin", "*")
rw.Header().Set("Content-Type", "image/gif")
rw.Write(GIF1px)
}

Semyon
29.01.2017
10:42:56
Котаны, маленький оффтопик.
Если вы напишете вот так:
func (g *GA2ClickHouse) Collect(rw web.ResponseWriter, req *web.Request) {
...
}
то уважаемый телеграм напечатаейт код моноширинным шрифтом и все будут ужасно ликовать и радоваться

Slach
29.01.2017
10:45:12
странно а у меня вот так
http://take.ms/OKcR4

Semyon
29.01.2017
10:46:56
потому что код без бэктиков
обрати внимание на закорючки вокруг кода, вот такие `

Slach
29.01.2017
10:49:39
ок. спасибо

Мерлин
29.01.2017
12:10:35
https://habrahabr.ru/company/badoo/blog/320724/

Slava
29.01.2017
22:39:36
https://github.com/jbeda?tab=stars кажется это успех =)

Mars
30.01.2017
03:30:36
Признание! Поздравляю

Slach
30.01.2017
06:23:27
Народ, а подскажите пожалуйста
а может канал у которого состояние closed=0
сам собой стать closed=1 ??
ну или не сам собой, а допустим после операции
channel <- *pointer_to_data
канал создаю буфферизированый через make
закрываю через
defer close(channel)
а оно мне panic кидает сцуко, cannot send to closed channel

Mars
30.01.2017
06:29:08


Slach
30.01.2017
06:39:14
нет посылаю я не в теле функции
посылаю я из другой горутины
которая вызывается в теле функции как
go func(req *web.Request) {
g.Lock()
defer g.Unlock()
g.buffered_data <- *req // вот тут падает в panic
}(req)
читаю в еще одной горутине
func (g *GA2ClickHouse) ProcessRequest() {
log.Print("ProcessRequest BEGIN")
for {
select {
case req, ok := <-g.buffered_data:
if ok {
// тут всякое
} else {
log.Println("ProcessRequest END after channel closed")
return
}
}
}
}
и там в ней делаю бесконечный for и return если канал закрыт
defer еще не должен по идее отработать то? или должен?

Mars
30.01.2017
06:44:52
должен, defer сработает до выпонения любой из горутин, вызванных внутри тела функции

Daniel
30.01.2017
06:47:19
Чет это совсем дебри :)

Slach
30.01.2017
06:47:46
ок, понял
спасибо
а как тогда сделать запись в канал асинхронную и при этом выйти из функции??
go func(req *web.Request) {
g.Lock()
defer g.Unlock()
g.buffered_data <- *req // вот тут падает в panic
}(req)
заменил на просто
g.buffered_data <- *req // вот тут падает в panic
и оно подвисло на записи в канал с выходом из теста с exit code = 2

Daniel
30.01.2017
07:06:12
Зачем нужна асинхронная запись в канал?

Google

Slach
30.01.2017
07:07:05
дак если просто в канал писать, то исходная функция которая в канал пишет подвисает? пока принимающая горутина из канала не получит? нет?

Daniel
30.01.2017
07:07:58
Почитай про буферизованные каналы
Короткий ответ - если буфер НЕ переполнен, не подвисает

Slach
30.01.2017
07:11:00
ну я прочитал
видимо ничего не понял, или понял как то не так
ОК я сделал буфферизированый канал
и убрал горутину с замыканием
и оно теперь вообще бенчмарк не запускает а выходит с exit code=2 ;)

Мерлин
30.01.2017
07:17:06


Slach
30.01.2017
07:18:13
func NewGA2ClickHouse() GA2ClickHouse {
return GA2ClickHouse{
schema_version: 1,
form_k_exists_map: make(map[string]bool),
db_field_list: make([]string, 0),
db_field_map: make(map[string]int),
db_to_form_map: make(map[string]string),
nested_tables: make(map[string]NestedTable),
buffered_data: make(chan web.Request, 100),
}
}
func prepareCollectorToBenchmark() GA2ClickHouse {
g := NewGA2ClickHouse()
g.drop_database = true
g.chunk_size = 100
g.data_dir = "./data"
g.geoip_dir = "./geoip"
g.database = "ga2clickhouse_test"
g.cluster_name = "ga2clickhouse_test"
g.table_prefix = ""
g.clickhouse_hosts = []string{"local.ga2clickhouse.clickhouse.pro:8123"}
g.CreateDataDir()
g.InitGeoip()
g.InitBrowseCap()
g.InitClickHouseClient()
g.CreateClickHouseTables()
go g.ProcessRequest()
return g
}
// дальше в бенчмарке
func BenchmarkGA2ClickHouse_CollectSequenced(b *testing.B) {
g := prepareCollectorToBenchmark()
//defer g.ShutdownCollector() убрал чтобы не закрывать канал раньше
log.Printf("b.N=%d", b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := NewTestRequest()
rw := web.ResponseWriter(testResponseWriter{})
g.Collect(rw, &req)
}
}
блин у меня такое ощущение что у меня горутина которая данные принимает не успевает из канала прочитать к тому моменту когда бенчмарк функция выходит
b.N=1
но по идее пока g.ProcessRequest не отработает g не должен быть убить через GC ??? такведь??


Daniel
30.01.2017
07:23:58
но почему должна?
что ты сделал для того, чтобы тест не закончился до того, как все запущенные горутины отработают?

Slach
30.01.2017
07:24:43
а как это сделать???

crxfoz
30.01.2017
07:25:43
Ну простейший вариант waitgroup. В твой код не вдавался, т.ч. не обязательно, что этот способ синхронизации каналов подойдет в твоем кейсе

Daniel
30.01.2017
07:26:18
это не способ синхронизаци каналов, это смособ подсчета горутин

Slach
30.01.2017
07:26:52
вот да, потому что Add(int)
а не Add(func)

crxfoz
30.01.2017
07:29:18
Ну, как я понимаю горутин "принимающие даные" много в его кейсе, а отправляющаю\ждущая обработки - одна. И как раз таки поставив wg.Wait можно дождаться завершения этих "обрабатывающих" горутин и продолжить работы

Slach
30.01.2017
07:29:33
нет горутина принимающая данные ОДНА
но она не успевает прочитать из буферизированного канала
потому что функция бенчмарка, просто выходит раньше при b.N
и GC убивает и горутину и канал?

crxfoz
30.01.2017
07:32:37
Создай еще один канал, пиши в него в ф-ции, которая принимает данные, а в ф-ции где надо ждать читай из этого канала

Google

crxfoz
30.01.2017
07:32:40
или тоже не подходит?

Slach
30.01.2017
07:35:26
выглядит как окстыль...
мне так то надо забенчмаркать как много Collect смогут писать в одну ProcessRequest
а она в свою очередь должна писать в TSV файл

Alexander
30.01.2017
07:42:15
да, именно так
перед запуском рутины делаешь Add

Daniel
30.01.2017
07:42:50

Alexander
30.01.2017
07:42:53
после тог, как она отработала Done. и в конце блока, где надо пождать пишешь Wait
https://nathanleclaire.com/blog/2014/02/15/how-to-wait-for-all-goroutines-to-finish-executing-before-continuing/ - вот с примерами
есть правда ощущение, что так нельзя писать - у него в последнем примере горутина, которая читает из jsonResponses в памяти осталась бы висеть, т.к. канал не закрыт

Admin
ERROR: S client not available

Alexander
30.01.2017
07:46:59
но с WaitGroup код нормальный

Slach
30.01.2017
07:47:07
спасибо за ссылку

Daniel
30.01.2017
07:51:54
у приложения, очевидно, проблемы с архитектурой

Slach
30.01.2017
08:11:00
а я не знал что можно делать range по каналу
какой интерфейс должна реализовывать сущность чтобы по ней можно было делать range ?

Alexander
30.01.2017
08:13:35
по-моему только по slice, map, channel можно range делать

Anton
30.01.2017
08:14:18
https://golang.org/ref/spec#RangeClause

Slach
30.01.2017
08:14:50
а как правильнее делать ?
for {
select
}
или
for i := range channel ?

Google

r
30.01.2017
08:15:54
смотря сколько у тебя каналов
которые ты хочешь слушать
я обысно использую 2 минимум
1 - данные, 2 управляющий
поэтому for {select case}

Slach
30.01.2017
08:16:47
ну пока один
в общем я понял что с точки зрения производительности это без разницы

Alexander
30.01.2017
08:16:50
я бы посоветовал все-таки почитать книжку с примерами организации concurrency

crxfoz
30.01.2017
08:17:34
https://blog.golang.org/pipelines - Тут несколько неплохих примеров есть, чтоб далеко не ходить

Alexander
30.01.2017
08:17:37
просто это основа языка по сути и лучше изучить основные приемы до написания кода - он как минимум будет более чистым с точки зрения языка

r
30.01.2017
08:17:58
+ удобно иметь таймер ожидания, это еще один канал

i
30.01.2017
08:18:42
for i := range channel можно просто закрыть канал

Daniel
30.01.2017
08:25:01
неблокирующийся, как ни странно, иногда нужен
более одного канала - это очень специфический случай

i
30.01.2017
08:28:36

Mars
30.01.2017
08:28:54
Еще одно, из for d := range ch можно выйти только по закрытию канала из другой горутины

i
30.01.2017
08:30:06
Ещё по break

Mars
30.01.2017
08:33:09
Ещё по break
Да, имел ввиду что нельзя выйти если сообщений в канале 0.

r
30.01.2017
08:33:34

Daniel
30.01.2017
08:44:47

Sergey
30.01.2017
08:48:41

Daniel
30.01.2017
08:48:58
нет, это нормально.