Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
А если будет возможность адаптировать сборщик мусора к этому случаю, чтобы память после удаления не освобождалась до тех пор, пока в программе есть живая ссылка на удаленный элемент, тогда вообще всё замечательно и нет никаких проблем с безопасностью.
Это высоконагруженное веб-приложение, обрабатывающее… больше 10к запросов в секунду.
…
кластер на Go будет уже из шести [машин]…
…
обработать 10к… запросов за одну секунду на одной машине…
(блок continue будет выполняться даже при раннем прерывании текущей итерации через оператор next). В Go для этого приходится использовать некошерный goto, который к тому же принуждает прописывать все декларации переменных перед ним, даже тех, которые не используются после метки переходаДля этого можно использовать кошерный defer, кстати, не нужно городить «for ;;», конструкция «for {}» для этого существует.
println("start for{}")
for i := 0; i < 3; i ++ {
defer func(){ println(i) }()
}
println("finish for{}")
start for{}
0
1
2
finish for{}
println("start for{}")
for i := 0; i < 3; i ++ {
defer func(i int){ println(i) }(i)
}
println("finish for{}")
Do not communicate by sharing memory; instead, share memory by communicating.Я уверен, что Вы в состоянии корректно перевести эту цитату, но все же позволю себе заметить, что она утверждает по сути то же, что и я: каналы есть зер гут, но не слишком фанатейте.
This approach can be taken too far. Reference counts may be best done by putting a mutex around an integer variable, for instance. But as a high-level approach, using channels to control access makes it easier to write clear, correct programs.
So which should you use?
Use whichever is most expressive and/or most simple.
for req := range ch {
...
req.resp <- C.GoString(C.crypt(req.cryptStr, req.cryptSalt))[len(salt):]
...
}
try ... catch
не работает с асинхронным кодом. Вместо этого объект исключения передается в колбек первым параметром, и его наличие нужно проверять отдельно руками. По началу мне это тоже казалось чем-то жутко диким и неудобным, но со временем я понял, что с точки зрения того, сколько кода занимает обработка ошибок и сколько когнитивных усилий приходится на нее затрачивать, разницы вообще никакой нет. Думаю, с Go та же история.if (error) { doErrorHandling(error); }
try-catch
блока, а вместо этого нужно писать логику того, что делать с каждой возможной ошибкой прямо в точке ее появления, т.д.try-catch
, абсолютно одинаковые.my_fun(val)
— так записывается вызов функцииmy_macro!(val)
— так записывается обращение к макросу.fn do_smth() -> Result {
let res1 = try!(do_sub1());
let res2 = try!(do_sub2());
return Ok(res1 + res2)
}
try!
— это макрос, после разворота которого функция будет выглядеть так:fn do_smth() -> Result {
let res1 = match do_sub1() {
Ok(v) => v,
Err(e) => return Err(e)
};
let res2 = match do_sub2() {
Ok(v) => v,
Err(e) => return Err(e)
};
return Ok(res1 + res2)
}
do_subX()
вернула ошибку, то она возвращается из функции с помощью return
, а если вернулось нормальное значение — оно присваивается переменной для дальнейшего использования.unsafe
и прочие костыли.Например, сейчас если у вас есть функция, которая принимает параметром коллекцию и не меняет ее (например, считает что-то на основе данных в коллекции), то в Rust вам придется написать две версии такой функции: одну для immutable коллекции, и одну — для mutable, — потому что так сейчас устроена система типов.
&mut self
. Методы, которые не меняют коллекцию, будут принимать просто &self
. После этого вы можете вызывать изменяющие и неизменяющие методы, если структура лежит в мутабельной переменной, и только неизменяющие, если структура лежит в немутабельной переменной. Поэтому если вы не собираетесь менять коллекцию, вам понадобится только одна функция, которая принимает &
; если же вы хотите менять коллекцию, в функцию вам понадобится передавать &mut
:pub struct SomeCollection<T> {
// ...
}
impl<T: Copy> SomeCollection<T> {
fn get(&self, i: uint) -> T { ... }
fn set(&mut self, i: uint, v: T) { ... }
}
fn compute_something(coll: &SomeCollection<int>) -> int {
// здесь можно вызвать coll.get():
let x = coll.get(0);
// но вызвать coll.set() нельзя, будет ошибка компиляции
// coll.set(0, x+1);
x
}
fn compute_and_modify(coll: &mut SomeCollection<int>) -> int {
// здесь можно и то, и другое
let x = coll.get(0);
coll.set(0, x+1);
x
}
fn main() {
// s1 - неизменяемый слот (переменная)
let s1: SomeCollection<int> = SomeCollection { ... };
// можно вызвать compute_something(), но не compute_and_modify()
println!("{}", compute_something(&s1));
// println!("{}", compute_and_modify(&mut s1));
let mut s2 = s1; // перемещаем в мутабельный слот
// можно вызвать и то, и другое
println!("{}", compute_something(&s2));
println!("{}", compute_and_modify(&mut s2));
}
A common issue I've come upon in Rust is the following: you have some structure with a fairly complicated, or at least non-trivial, search procedure. The search procedure logically requires no mutation, and returns an immutable reference. However, you would also like to provide a variant that takes in a mutable version of the structure and returns a mutable reference. This does not affect the actual search procedure at all, it just changes the signature of the function (and maybe the signature of some temporary variables). However, the only way to do this in Rust at the moment is to duplicate the implementation.
The implementation ofstd::collections::TreeMap
is a great example.find
andfind_mut
are pretty much exactly the same logic, but the logic's been written out twice.
find
и find_mut
и аналогичные методы, возвращающие ссылки на внутренние элементы структуры данных — это чуть ли ни единственное, что страдает в этом случае. Более того, я бы сказал, что это специфично для обобщённых коллекций вроде того TreeMap; для собственных структур данных эта проблема будет возникать ещё реже.Это вполне может быть из-за неоптимальности кода программы. Например, cgocall/System занимает много времени, тк вы делаете 6 cgo вызовов вместо 1 или 2.Или наоборот, когда код настолько прост и быстр, что узким местом являются уже библиотеки и встроенные функции )
На самом деле есть вариант без дедлока с апгрейдом через блокирующийся TryLock(), который либо проапгрейдит блокировку, либо вылетит, если кто-то ещё захочет это сделать до того, как остальные поснимают RLock(). Хотя, согласен, в общем случае лучше просто снять RLock и перелочиться.Ещё больше расстраивает невозможность проапгрейдить блокировку RWMutexЭто принципиально нельзя сделать.
Пардон, но работать как-то нужно, а с паузами в несколько секунд это чертовски сложно (и дорого). Про настройку GOGC в курсе, но в доке не совсем внятно описано, как именно она работает, а экспериментировать не было времени — нужно было гарантированно работающее решение. Разброс времени паузы сейчас меньше 50%.Решение — запускать GC() почаще, для надежности лучше самостоятельно из программы.Этого ни в коем случае не надо делать.
Не вижу, как это решает проблему изменившихся данных. И как будет выглядеть код? Если TryUpgrade провалился, то что? Мы все равно делаем RUnlock и Lock? Выглядит бессмысленноСогласен.
Не понимаю связи. Работать — надо. Вызывать runtime.GC не надо. Вместо этого нужно уменьшить GOGC, что приведет к увеличению частоты сборок.А есть гарантии, что уменьшение GOGC уменьшит максимальное время работы GC и разброс длительности паузы? Из документации мне это не очевидно. Но я протестирую, просто пока не было возможности.
О плюсах и минусах Go