WARNING CANCELLER
Имитаторы нагрузки PROBRIGHT Warning Canceller предназначены для компенсации снижения нагрузки на электросистему автомобилей при установке светодиодных модулей линейки SDRL.
Выполняют роль «обманки», благодаря которой, бортовая система контроля исправности ламп не выдаёт ошибок по цепям ДХО и габаритных огней.
Характеристики
Характеристики
Сопротивление контура «ДХО»
Сопротивление контура «Габаритные огни»
Габариты упаковки: 12x8x2,5см,
Масса упаковки: 42г.
Во время работы корпус устройства сильно нагревается. Для обеспечения безопасной работы и продолжительного срока службы, обязательно крепление корпуса на металлическую поверхность.
Гарантия
Гарантия качества и сервисное обслуживание
На всю нашу продукцию распространяется гарантия в 12 месяцев со дня покупки
Установка светодиодных ламп.
Ребят, подскажите. Хотел заменить лампы освещения номерного знака на диодные. Там стоят c5w 36mm. Купил, поставил — компьютер ругается, говорит лампы нет. Купил с обманками — та же история. Может кто сталкивался? Как обмануть бортовой комп.? Или может лампы не те?
Чтобы не засорять форум и не создавать кучу тем, задам еще один вопрос прямо здесь.
Хотел поставить камеру заднего вида (стоит «Команд 2.0», в моей комплектации камеры нет). Различные установщики говорят, что после установки любой камеры дополнительно необходимо программирование «команда». Отправляют к официалам. Официалы же в свою очередь отдельно ничего делать не хотят, только сразу все и за 38 тыров.
Может кто подскажет в Питере спеца?
W164 ML350, 3.5, 2006
VIN:WDC1641861A185731
Falseclock
Пользователь
Сообщения 86 Реакции 25 Авто WDC1641861A169458
лампы у вас не той системы.
WDC1641861A169458
Крымчанин
Пользователь
Сообщения 6.879 Реакции 679 Авто Дизельный Два Город ремонт любого авто с названием MERCEDES
Напиши завтра в личку
Дам номер хорошего друга в питере
по всем вопросам писать в личку .
история и карта данных для проверки Mercedes
Paradist
Пользователь
Сообщения 11 Реакции 8 Город IT специалист (разнорабочий)
Ребят, подскажите. Хотел заменить лампы освещения номерного знака на диодные. Там стоят c5w 36mm. Купил, поставил — компьютер ругается, говорит лампы нет. Купил с обманками — та же история. Может кто сталкивался? Как обмануть бортовой комп.? Или может лампы не те?
Для себя сделал таблицу по лампам (мог допустить ошибки в типах ламп при составлении , рекомендую перепроверить):
Посмотреть вложение Лампы.pdf
Последнее редактирование: 26 Дек 2013
Paradist
Пользователь
Сообщения 11 Реакции 8 Город IT специалист (разнорабочий)
Чтобы не засорять форум и не создавать кучу тем, задам еще один вопрос прямо здесь.
Хотел поставить камеру заднего вида (стоит «Команд 2.0», в моей комплектации камеры нет). Различные установщики говорят, что после установки любой камеры дополнительно необходимо программирование «команда». Отправляют к официалам. Официалы же в свою очередь отдельно ничего делать не хотят, только сразу все и за 38 тыров.
Может кто подскажет в Питере спеца?
W164 ML350, 3.5, 2006
VIN:WDC1641861A185731
Официалов в сад.
активировать камеру сможет любой хороший спец занимающийся прошивкой/перепрошивкой командов на мерсы.
Поставить камеру можно прям по центру машины, в ручку багажника. Если руки у установщиков из нужного места растут, сделают.
Камеру я бы рекомендовал только Kenwood (CCD200, 210). Китайские качеством не радуют.
Собрат написал инфу по командам:
Замена Командов (Магнитофонов) Мои изыскания по Командам ML GL R классов 2005-2011г.
Евро Comand 2.0 (2005-2008)
Плюсы
1. Русский язык на борту
2. Есть возможность подключения камеры заднего вида (зелённый разьём на тыле устройства)
3. Чётный и нечётный шаг FM
4. NAVI только Россия и Европа (Карт Украины не существует)
5. RDS
6. МОЖНО подключить ОРИГИНАЛЬНЫЙ Bluetooth интерфейс, TV тюнер (не дешево)
8. К Команду подключается только оригинальный, только оптический, CD-MP3 чейнджер
9. Без каких либо танцев и дорогостоящих замен есть возможность установить Comand 2.0 в USA Саунд систему HarmanKardon Logic 7 (просто перестегнуть разьёмы, замена за 5 мин)
Минусы
1. Внутренний CD НЕ читает MP3 (только CD audio, или только через оригинал. оптический ченджер MP3)
2. Нет в природе для NAVI карт Украины
3. Нет индикации времени на экране (для пасажиров и для меня, отсутствие Часов это минус)
4. DVD-Video и DVD-Audio нет
Руководство по запуску Функции Azure C# в изолированном рабочем процессе
В этой статье приведены общие сведения о работе с Функции Azure в .NET с помощью изолированной рабочей модели. Эта модель позволяет проекту использовать версии .NET независимо от других компонентов среды выполнения. Сведения о конкретных поддерживаемых версиях .NET см . в поддерживаемой версии.
Чтобы приступить к созданию функций изолированной рабочей модели .NET, воспользуйтесь приведенными ниже ссылками.
Сведения о развертывании изолированного проекта рабочей модели в Azure см. в статье «Развертывание в Функции Azure».
Преимущества изолированной рабочей модели
Существует два режима, в которых можно запускать функции библиотеки классов .NET: либо в том же процессе , что и среда выполнения узла Функций (внутри процесса) или изолированный рабочий процесс. Если функции .NET выполняются в изолированном рабочем процессе, вы можете воспользоваться следующими преимуществами:
- Меньше конфликтов: поскольку функции выполняются в отдельном процессе, сборки, используемые в приложении, не конфликтуют с разными версиями одинаковых сборок, используемых узлом.
- Полный контроль над процессом: вы управляете запуском приложения, что означает, что вы можете управлять конфигурациями, используемыми и запущенным ПО промежуточного слоя.
- Стандартная внедрение зависимостей. Так как у вас есть полный контроль над процессом, можно использовать текущее поведение .NET для внедрения зависимостей и включения ПО промежуточного слоя в приложение-функцию.
- Гибкость версии .NET. Выполнение вне процесса узла означает, что функции могут выполняться в версиях .NET, не поддерживаемых средой выполнения Функций, включая платформа .NET Framework.
Если у вас есть существующее приложение-функция C#, которое выполняется в процессе, необходимо перенести приложение, чтобы воспользоваться преимуществами этих преимуществ. Дополнительные сведения см. в статье «Миграция приложений .NET из модели в процессе» в изолированную рабочую модель.
Поддерживаемые версии
Версии среды выполнения функций поддерживают определенные версии .NET. Дополнительные сведения о версиях службы «Функции Azure» см. в разделе Обзор версий среды выполнения службы «Функции Azure». Поддержка версий также зависит от того, выполняются ли функции в процессе или изолированном рабочем процессе.
Чтобы узнать, как изменить версию среды выполнения Функций, используемую приложением-функцией, обратитесь к разделу Просмотр и обновление текущей версии среды выполнения.
В следующей таблице показан самый высокий уровень .NET или платформа .NET Framework, который можно использовать с определенной версией Функций.
Версия среды выполнения службы «Функции Azure» | Изолированный рабочий процесс (Изолированный процесс .NET) |
Внутрипроцессно (Библиотека класса .NET) |
---|---|---|
Функции 4.x | .NET 8.0 .NET 7.0 1 .NET 6.0 2 платформа .NET Framework 4.8 3 |
.NET 6.0 2 |
Функции 1.x 4 | Н/Д | .NET Framework 4.8 |
1 .NET 7 достигает окончания официальной поддержки 14 мая 2024 года.
2 .NET 6 достигает окончания официальной поддержки 12 ноября 2024 года.
3 Процесс сборки также требует пакета SDK для .NET 6.
4 Поддержка заканчивается для версии 1.x среды выполнения Функции Azure 14 сентября 2026 года. Дополнительные сведения см . в этом объявлении о поддержке. Для дальнейшей полной поддержки следует перенести приложения в версию 4.x.
Последние новости о выпусках службы «Функции Azure», включая удаление отдельных устаревших промежуточных версий, см. в статье Анонсы для службы приложений Azure.
Структура проекта
Проект .NET для Функции Azure с использованием изолированной рабочей модели — это в основном проект консольного приложения .NET, предназначенный для поддерживаемой среды выполнения .NET. Ниже приведены основные файлы, требуемые в любом изолированном проекте .NET.
- Файл проекта C# (.csproj), определяющий проект и зависимости.
- Файл Program.cs, который служит в качестве точки входа в приложение.
- Все файлы кода, определяющие функции.
- host.json файл, определяющий конфигурацию, доступную функциям в проекте.
- local.settings.json файл, определяющий переменные среды, используемые проектом при локальном запуске на компьютере.
Ссылки на пакеты
Проект .NET для Функции Azure с помощью изолированной рабочей модели использует уникальный набор пакетов для основных функций и расширений привязки.
Основные пакеты
Для выполнения функций .NET в изолированном рабочем процессе требуются следующие пакеты:
- Microsoft.Azure.Functions.Worker
- Microsoft.Azure.Functions.Worker.Sdk
Пакеты расширений
Так как функции изолированного рабочего процесса .NET используют различные типы привязки, они требуют уникального набора пакетов расширений привязки.
Запуск и настройка
При использовании изолированных функций .NET у вас есть доступ к запуску приложения-функции, который обычно находится в Program.cs . Вам предстоит самостоятельно создавать и запускать собственный экземпляр узла. Для этого у вас также есть прямой доступ к конвейеру конфигурации приложения. Благодаря изолированному рабочему процессу функций .NET можно гораздо проще добавлять конфигурации, внедрять зависимости и запускать собственное ПО промежуточного слоя.
В коде ниже приведен пример конвейера HostBuilder:
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(s => < s.AddApplicationInsightsTelemetryWorkerService(); s.ConfigureFunctionsApplicationInsights(); s.AddSingleton(); s.Configure(options => < // The Application Insights SDK adds a default logging filter that instructs ILogger to capture only Warning and more severe logs. Application Insights requires an explicit override. // Log levels can also be configured using appsettings.json. For more information, see https://learn.microsoft.com/en-us/azure/azure-monitor/app/worker-service#ilogger-logs LoggerFilterRule toRemove = options.Rules.FirstOrDefault(rule =>rule.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider"); if (toRemove is not null) < options.Rules.Remove(toRemove); >>); >) .Build();
Для этого кода требуется using Microsoft.Extensions.DependencyInjection; .
Перед вызовом HostBuilder необходимо выполнить следующие действия Build() .
- Вызовите либо ConfigureFunctionsWebApplication() при использовании интеграции ASP.NET Core, либо в ConfigureFunctionsWorkerDefaults() противном случае. Дополнительные сведения об этих параметрах см . в триггере HTTP.
Если вы пишете приложение с помощью F#, для некоторых расширений триггеров и привязок требуется дополнительная настройка. См. документацию по настройке расширения BLOB-объектов, расширения таблиц и расширения Cosmos DB при планировании использования этих расширений в приложении F#. - Настройте все службы или конфигурацию приложения, необходимые для проекта. Дополнительные сведения см. в разделе «Конфигурация «.
Если вы планируете использовать приложение Аналитика, необходимо вызвать AddApplicationInsightsTelemetryWorkerService() и ConfigureFunctionsApplicationInsights() в делегате ConfigureServices() . Дополнительные сведения см. в Аналитика приложения.
Если проект предназначен для .NET Framework 4.8, необходимо также добавить FunctionsDebugger.Enable(); перед созданием HostBuilder. Это должна быть первая строка метода Main() . Дополнительные сведения см. в статье Отладка при выборе платформа .NET Framework.
HostBuilder используется для сборки и возврата полностью инициализированного IHost экземпляра, который выполняется асинхронно для запуска приложения-функции.
await host.RunAsync();
Настройка
Метод ConfigureFunctionsWorkerDefaults используется для добавления параметров, необходимых для запуска приложения-функции в изолированном рабочем процессе, который включает следующие функции:
- Набор преобразователей по умолчанию.
- Настройте параметр JsonSerializerOptions по умолчанию таким образом, чтобы он игнорировал регистр в собственных именах.
- Интеграция с ведением журнала Функций Azure.
- ПО промежуточного слоя и функции для выходной привязки.
- ПО промежуточного слоя для выполнения функции.
- Поддержка gRPC по умолчанию.
.ConfigureFunctionsWorkerDefaults()
Поскольку у вас есть доступ к конвейеру сборки узла, во время инициализации вы также можете настроить особые конфигурации для приложения. Вы можете один или несколько раз вызвать метод ConfigureAppConfiguration в HostBuilder, чтобы добавить конфигурации, необходимые вашему приложению-функции. Подробнее о конфигурации приложения см. в разделе Конфигурация в ASP.NET Core.
Эти конфигурации касаются только приложения-функции, выполняемого в отдельном процессе. Чтобы внести изменения в узел функций или конфигурацию триггера и привязки, необходимо использовать файл host.json.
Внедрение зависимостей
Внедрение зависимостей упрощается при сравнении с функциями в процессе .NET, что требует создания класса запуска для регистрации служб.
Для приложения изолированного процесса .NET используется стандартный способ вызова ConfigureServices в построителе узлов и использование методов расширения в IServiceCollection для внедрения определенных служб.
В следующем примере показано, как внедряется зависимость отдельной службы.
.ConfigureServices(services => < services.AddSingleton(); >)
Для этого кода требуется using Microsoft.Extensions.DependencyInjection; . Подробнее см. в разделе Внедрение зависимости в ASP.NET Core.
Регистрация клиентов Azure
Внедрение зависимостей можно использовать для взаимодействия с другими службами Azure. Клиенты из пакета Azure SDK для .NET можно внедрить с помощью пакета Microsoft.Extensions.Azure. После установки пакета зарегистрируйте клиентов, вызвав AddAzureClients() коллекцию служб в Program.cs . В следующем примере настраивается именованный клиент для БОЛЬШИХ двоичных объектов Azure:
using Microsoft.Extensions.Azure; using Microsoft.Extensions.Hosting; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices((hostContext, services) => < services.AddAzureClients(clientBuilder =>< clientBuilder.AddBlobServiceClient(hostContext.Configuration.GetSection("MyStorageConnection")) .WithName("copierOutputBlob"); >); >) .Build(); host.Run();
В следующем примере показано, как использовать эту регистрацию и типы SDK для копирования содержимого BLOB-объектов в виде потока из одного контейнера в другой с помощью внедренного клиента:
using Microsoft.Extensions.Azure; using Microsoft.Extensions.Logging; namespace MyFunctionApp < public class BlobCopier < private readonly ILogger_logger; private readonly BlobContainerClient _copyContainerClient; public BlobCopier(ILogger logger, IAzureClientFactory blobClientFactory) < _logger = logger; _copyContainerClient = blobClientFactory.CreateClient("copierOutputBlob").GetBlobContainerClient("samples-workitems-copy"); _copyContainerClient.CreateIfNotExists(); >[Function("BlobCopier")] public async Task Run([BlobTrigger("samples-workitems/", Connection = "MyStorageConnection")] Stream myBlob, string name) < await _copyContainerClient.UploadBlobAsync(name, myBlob); _logger.LogInformation($"Blob copied!"); > > >
В примере используется литеральная строка для имени клиента в обоих Program.cs и функциях. Вместо этого следует использовать общую строку констант, определенную в классе функции. Например, можно добавить public const string CopyStorageClientName = nameof(_copyContainerClient); и ссылаться BlobCopier.CopyStorageClientName в обоих расположениях. Можно также определить имя раздела конфигурации с функцией, а не в Program.cs .
ПО промежуточного слоя
При изоляции .NET также поддерживает регистрацию ПО промежуточного слоя, используя такую же модель, как в ASP.NET. Эта модель позволяет внедрить логику в конвейер вызова, а также до и после выполнения функций.
Как мы видим на примере ниже, метод расширения ConfigureFunctionsWorkerDefaults имеет перегрузку, позволяющую регистрировать собственное ПО промежуточного слоя.
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults(workerApplication => < // Register our custom middlewares with the worker workerApplication.UseMiddleware(); workerApplication.UseMiddleware(); workerApplication.UseWhen((context) => < // We want to use this middleware only for http trigger invocations. return context.FunctionDefinition.InputBindings.Values .First(a =>a.Type.EndsWith("Trigger")).Type == "httpTrigger"; >); >) .Build();
UseWhen Метод расширения можно использовать для регистрации ПО промежуточного слоя, которое выполняется условно. Необходимо передать в этот метод предикат, возвращающий логическое значение, и ПО промежуточного слоя участвует в конвейере обработки вызовов при значении возвращаемого значения предиката true .
Следующие методы расширения в FunctionContext упрощают работу с ПО промежуточного слоя в изолированной модели.
Метод | Description |
---|---|
GetHttpRequestDataAsync | Возвращает экземпляр HttpRequestData при вызове триггером HTTP. Этот метод возвращает экземпляр ValueTask , который полезен при чтении данных сообщения, таких как заголовки запросов и файлы cookie. |
GetHttpResponseData | Возвращает экземпляр HttpResponseData при вызове триггером HTTP. |
GetInvocationResult | Возвращает экземпляр InvocationResult , представляющий результат выполнения текущей функции. Используйте свойство Value , чтобы получить или задать значение по мере необходимости. |
GetOutputBindings | Возвращает записи выходной привязки для текущего выполнения функции. Каждая запись в результате этого метода относится к типу OutputBindingData . Вы можете использовать свойство Value , чтобы получить или задать значение по мере необходимости. |
BindInputAsync | Привязывает элемент входной привязки для запрошенного экземпляра BindingMetadata . Например, этот метод можно использовать при наличии функции с входной BlobInput привязкой, которая должна использоваться ПО промежуточного слоя. |
Это пример реализации ПО промежуточного слоя, которая считывает HttpRequestData экземпляр и обновляет HttpResponseData экземпляр во время выполнения функции:
internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware < public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) < var requestData = await context.GetHttpRequestDataAsync(); string correlationId; if (requestData!.Headers.TryGetValues("x-correlationId", out var values)) < correlationId = values.First(); >else < correlationId = Guid.NewGuid().ToString(); >await next(context); context.GetHttpResponseData()?.Headers.Add("x-correlationId", correlationId); > >
Это ПО промежуточного слоя проверяет наличие определенного заголовка запроса (x-correlationId) и, если он есть, использует его значение для пометки заголовка ответа. В противном случае оно создает новое значение GUID и использует его для пометки заголовка ответа. Более подробный пример пользовательского ПО промежуточного слоя в приложении-функции см. в эталонном образце.
Методы, распознаваемые как функции
Метод функции — это открытый метод общедоступного класса с атрибутом Function , примененным к методу, и атрибут триггера, примененный к входным параметру, как показано в следующем примере:
[Function(nameof(QueueFunction))] [QueueOutput("output-queue")] public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context)
Атрибут триггера указывает тип триггера и привязывает входные данные к параметру метода. В предыдущем примере функция активируется посредством сообщения очереди, которое передается методу в параметре myQueueItem .
Атрибут Function помечает метод как точку входа функции. Имя в проекте должно быть уникальным, начинаться с буквы и содержать только буквы, цифры, _ и — , а его длина не должна превышать 127 знаков. Шаблоны проектов часто создают метод Run , но метод может иметь любое допустимое имя для метода C#. Метод должен быть общедоступным членом общедоступного класса. Как правило, это метод экземпляра, чтобы службы могли передаваться через внедрение зависимостей.
Параметры функции
Ниже приведены некоторые параметры, которые можно включить в сигнатуру метода функции:
- Привязки, которые помечены как такие, декорируя параметры как атрибуты. Функция должна содержать ровно один параметр триггера.
- Объект контекста выполнения, предоставляющий сведения о текущем вызове.
- Маркер отмены, используемый для корректного завершения работы.
Контекст выполнения
При изоляции .NET передает объект FunctionContext вашим методам функций. Этот объект позволяет получить ILogger экземпляр для записи в журналы, вызвав метод GetLogger и предоставив categoryName строку. Этот контекст можно использовать для получения ILogger без необходимости внедрения зависимостей. Подробнее см. в разделе Ведение журнала.
Токены отмены
Функция может принимать параметр CancellationToken, который позволяет операционной системе передавать в ваш код сведения о том, что выполнение функции будет завершено. Это уведомление можно использовать для предотвращения ситуации, когда выполнение функции завершается неожиданно, оставляя данные в несогласованном состоянии.
Маркеры отмены поддерживаются в функциях .NET при выполнении в изолированном рабочем процессе. В следующем примере возникает исключение при получении запроса на отмену:
[Function(nameof(ThrowOnCancellation))] public async Task ThrowOnCancellation( [EventHubTrigger("sample-workitem-1", Connection = "EventHubConnection")] string[] messages, FunctionContext context, CancellationToken cancellationToken) < _logger.LogInformation("C# EventHub trigger function processing a request.", nameof(ThrowOnCancellation)); foreach (var message in messages) < cancellationToken.ThrowIfCancellationRequested(); await Task.Delay(6000); // task delay to simulate message processing _logger.LogInformation("Message '' was processed.", message); > >
В следующем примере выполняются действия очистки при получении запроса на отмену:
[Function(nameof(HandleCancellationCleanup))] public async Task HandleCancellationCleanup( [EventHubTrigger("sample-workitem-2", Connection = "EventHubConnection")] string[] messages, FunctionContext context, CancellationToken cancellationToken) < _logger.LogInformation("C# EventHub trigger function processing a request.", nameof(HandleCancellationCleanup)); foreach (var message in messages) < if (cancellationToken.IsCancellationRequested) < _logger.LogInformation("A cancellation token was received, taking precautionary actions."); // Take precautions like noting how far along you are with processing the batch _logger.LogInformation("Precautionary activities complete."); break; >await Task.Delay(6000); // task delay to simulate message processing _logger.LogInformation("Message '' was processed.", message); > >
Привязки
Привязки определяются по атрибутам методов, параметров и типов возвращаемого значения. Привязки могут предоставлять данные в виде строк, массивов и сериализуемых типов, таких как обычные объекты класса (POC). Для некоторых расширений привязки можно также привязать к типам , определенным в пакетах SDK службы.
Сведения о триггерах HTTP см. в разделе триггеров HTTP.
Полный набор эталонных примеров с помощью триггеров и привязок с изолированными функциями рабочего процесса см . в эталонном примере расширений привязки.
Входные привязки
Функция может не иметь совсем или иметь несколько входных привязок, которые могут передавать данные функции. Как и триггеры, входные привязки определяются путем применения атрибута привязки к входному параметру. При выполнении функции среда выполнения пытается получить данные, указанные в привязке. Запрашиваемые данные нередко зависят от сведений, полученных от триггера с помощью параметров привязки.
Выходные привязки
Чтобы записать в выходную привязку, необходимо применить атрибут выходной привязки к методу функции, который определяет, как записывать в связанную службу. Значение, возвращаемое методом, записывается в выходную привязку. Например, в следующем примере строковое значение записывается в очередь сообщений output-queue с помощью выходной привязки:
[Function(nameof(QueueFunction))] [QueueOutput("output-queue")] public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context) < // Use a string array to return more than one message. string[] messages = < $"Album name = ", $"Album songs = ">; _logger.LogInformation(",", messages[0], messages[1]); // Queue Output messages return messages; >
Несколько выходных привязок
В выходную привязку всегда записываются данные, являющиеся возвращаемым значением функции. Чтобы выполнить запись в более чем одну выходную привязку, вам понадобится создать пользовательский тип возвращаемого значения. Этот тип возвращаемого значения должен иметь атрибут выходной привязки, примененный к одному или нескольким свойствам класса. В следующем примере HTTP-триггера запись осуществляется как в HTTP-ответ, так и в выходную привязку очереди:
public static class MultiOutput < [Function(nameof(MultiOutput))] public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req, FunctionContext context) < var response = req.CreateResponse(HttpStatusCode.OK); response.WriteString("Success!"); string myQueueOutput = "some output"; return new MyOutputType() < Name = myQueueOutput, HttpResponse = response >; > > public class MyOutputType < [QueueOutput("myQueue")] public string Name < get; set; >public HttpResponseData HttpResponse < get; set; >>
Ответ от HTTP-триггера всегда считается выходным, поэтому атрибут возвращаемого значения не требуется.
Типы пакетов SDK
Для некоторых типов привязки для конкретной службы данные привязки можно предоставить с помощью типов пакетов SDK и платформ службы. Они предоставляют больше возможностей, помимо того, что может предложить сериализованная строка или обычный объект CLR (POCO). Чтобы использовать новые типы, проект необходимо обновить для использования более новых версий основных зависимостей.
Dependency | Требование к версии |
---|---|
Microsoft.Azure.Functions.Worker | 1.18.0 или более поздней версии |
Microsoft.Azure.Functions.Worker.Sdk | 1.13.0 или более поздней версии |
При локальном тестировании типов SDK на компьютере также необходимо использовать Функции Azure Core Tools версии 4.0.5000 или более поздней. Текущую версию можно проверка с помощью func version команды.
Каждый триггер и расширение привязки также имеют собственное минимальное требование версии, описанное в справочных статьях по расширению. Следующие привязки для конкретной службы предоставляют типы пакетов SDK:
Service | Триггер | Входные привязки | Выходные привязки |
---|---|---|---|
Большие двоичные объекты Azure | Общедоступная версия | Общедоступная версия | Типы пакетов SDK не рекомендуется. 1 |
Azure Queues | Общедоступная версия | Входная привязка не существует | Типы пакетов SDK не рекомендуется. 1 |
Служебная шина Azure | Общедоступная версия | Входная привязка не существует | Типы пакетов SDK не рекомендуется. 1 |
Центры событий Azure | Общедоступная версия | Входная привязка не существует | Типы пакетов SDK не рекомендуется. 1 |
Azure Cosmos DB | Типы пакетов SDK не используются 2 | Общедоступная версия | Типы пакетов SDK не рекомендуется. 1 |
Таблицы Azure | Триггер не существует | Общедоступная версия | Типы пакетов SDK не рекомендуется. 1 |
Сетка событий Azure | Общедоступная версия | Входная привязка не существует | Типы пакетов SDK не рекомендуется. 1 |
1 Для выходных сценариев, в которых используется тип пакета SDK, следует создавать и работать с клиентами SDK непосредственно вместо использования выходной привязки. Пример внедрения зависимостей см. в разделе «Регистрация клиентов Azure».
2 Триггер Cosmos DB использует веб-канал изменений Azure Cosmos DB и предоставляет элементы канала изменений в виде сериализуемых типов JSON. Отсутствие типов SDK выполняется путем разработки для этого сценария.
При использовании выражений привязки, использующих данные триггера , нельзя использовать типы пакетов SDK для самого триггера.
Триггер HTTP
Триггеры HTTP позволяют вызывать функцию с помощью HTTP-запроса. Существует два различных подхода, которые можно использовать:
- Модель интеграции ASP.NET Core, использующая понятия, знакомые для разработчиков ASP.NET Core
- Встроенная модель, которая не требует дополнительных зависимостей и использует пользовательские типы для HTTP-запросов и ответов. Этот подход поддерживается для обратной совместимости с предыдущими изолированными рабочими приложениями .NET.
интеграция ASP.NET Core
Не все функции ASP.NET Core предоставляются этой моделью. В частности, возможности конвейера промежуточного слоя и маршрутизации по промежуточному поверх ядра ASP.NET недоступны. ASP.NET интеграция Core требует использования обновленных пакетов.
Чтобы включить интеграцию ASP.NET Core для HTTP:
- Добавьте ссылку в проект в пакет Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore версии 1.0.0 или более поздней версии.
- Обновите проект, чтобы использовать следующие версии пакетов:
- Microsoft.Azure.Functions.Worker.Sdk версии 1.11.0. или более поздние версии.
- Microsoft.Azure.Functions.Worker, версия 1.16.0 или более поздняя.
- Program.cs В файле обновите конфигурацию построителя узлов, чтобы использовать ConfigureFunctionsWebApplication() вместо нее ConfigureFunctionsWorkerDefaults() . В следующем примере показана минимальная настройка без других настроек:
using Microsoft.Extensions.Hosting; using Microsoft.Azure.Functions.Worker; var host = new HostBuilder() .ConfigureFunctionsWebApplication() .Build(); host.Run();
[Function("HttpFunction")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req) < return new OkObjectResult($"Welcome to Azure Functions, !"); >
Встроенная модель HTTP
В встроенной модели система преобразует входящее сообщение HTTP-запроса в объект HttpRequestData , передаваемый функции. Этот объект предоставляет данные из запроса, включая Headers , Cookies , Identities и URL при необходимости сообщение Body . Этот объект представляет HTTP-запрос, но не подключен непосредственно к базовому прослушивателю HTTP или полученному сообщению.
По этой логике функция возвращает объект HttpResponseData, который предоставляет данные, используемые для создания HTTP-ответа, включая сообщения StatusCode , Headers и — необязательно — сообщение Body .
В следующем примере показано использование HttpRequestData и HttpResponseData :
[Function(nameof(HttpFunction))] public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionContext executionContext) < var logger = executionContext.GetLogger(nameof(HttpFunction)); logger.LogInformation("message logged"); var response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); response.WriteString("Welcome to .NET isolated worker !!"); return response; >
Ведение журнала
public class MyFunction < private readonly ILogger_logger; public MyFunction(ILogger logger) < _logger = logger; >[Function(nameof(MyFunction))] public void Run([BlobTrigger("samples-workitems/", Connection = "")] string myBlob, string name) < _logger.LogInformation($"C# Blob trigger function Processed blob\n Name: \n Data: "); > >
Средство ведения журнала также можно получить из объекта FunctionContext, переданного в функцию. Вызовите метод GetLogger T> или GetLogger
Используйте методы и ILogger для записи различных уровней ILogger журнала, таких как LogWarning или LogError . Подробнее об уровнях журнала см. в статье об отслеживании. Уровни журнала для компонентов, добавленных в код, можно настроить, регистрируя фильтры в рамках HostBuilder конфигурации:
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => < // Registers IHttpClientFactory. // By default this sends a lot of Information-level logs. services.AddHttpClient(); >) .ConfigureLogging(logging => < // Disable IHttpClientFactory Informational logs. // Note -- you can also remove the handler that does the logging: https://github.com/aspnet/HttpClientFactory/issues/196#issuecomment-432755765 logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning); >) .Build();
В рамках настройки приложения Program.cs можно также определить поведение для отображения ошибок в журналах. По умолчанию исключения, создаваемые кодом, могут быть заключены в оболочку RpcException . Чтобы удалить этот дополнительный слой, задайте EnableUserCodeException для свойства значение true в рамках настройки построителя:
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults(builder => <>, options => < options.EnableUserCodeException = true; >) .Build();
Application Insights
Приложение изолированного процесса можно настроить для отправки журналов непосредственно в приложение Аналитика. Это поведение заменяет поведение по умолчанию для ретрансляции журналов через узел и рекомендуется, так как оно обеспечивает контроль над тем, как создаются эти журналы.
Установка пакетов
Чтобы записывать журналы непосредственно в приложение Аналитика из кода, добавьте ссылки на эти пакеты в проекте:
- Microsoft.Azure.Functions.Worker.Application Аналитика версии 1.0.0 или более поздней.
- Microsoft.Application Аналитика. WorkerService.
Чтобы добавить эти ссылки в проект, можно выполнить следующие команды:
dotnet add package Microsoft.ApplicationInsights.WorkerService dotnet add package Microsoft.Azure.Functions.Worker.ApplicationInsights
Настройка запуска
При установке пакетов необходимо вызвать AddApplicationInsightsTelemetryWorkerService() и ConfigureFunctionsApplicationInsights() во время настройки службы в Program.cs файле, как в следующем примере:
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => < services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); >) .Build(); host.Run();
Вызов ConfigureFunctionsApplicationInsights() добавления , ITelemetryModule который прослушивает определяемые ActivitySource функциями. Это создает данные телеметрии зависимостей, необходимые для поддержки распределенной трассировки. Дополнительные сведения о том, как его использовать, см. в AddApplicationInsightsTelemetryWorkerService() разделе «Приложения Аналитика для приложений службы рабочей роли».
Управление уровнями журнала
Узел Функций и рабочая роль изолированного процесса имеют отдельную конфигурацию для уровней журнала и т. д. Любая конфигурация приложения Аналитика в host.json не повлияет на ведение журнала из рабочей роли, а конфигурация, сделанная в рабочем коде, не будет влиять на ведение журнала с узла. Необходимо применить изменения в обоих местах, если сценарий требует настройки на обоих уровнях.
Остальная часть приложения продолжает работать с ILogger и ILogger . Однако по умолчанию пакет SDK для приложения Аналитика добавляет фильтр ведения журнала, который предписывает средству ведения журнала записывать только предупреждения и более серьезные журналы. Если вы хотите отключить это поведение, удалите правило фильтра в составе конфигурации службы:
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => < services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); >) .ConfigureLogging(logging => < logging.Services.Configure(options => < LoggerFilterRule defaultRule = options.Rules.FirstOrDefault(rule =>rule.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider"); if (defaultRule is not null) < options.Rules.Remove(defaultRule); >>); >) .Build(); host.Run();
Оптимизация производительности
В этом разделе описаны параметры, которые позволяют повысить производительность при холодном запуске.
В общем случае приложение должно использовать последние версии его основных зависимостей. Как минимум, необходимо обновить проект следующим образом:
- Обновите Microsoft.Azure.Functions.Worker до версии 1.19.0 или более поздней.
- Обновите Microsoft.Azure.Functions.Worker.Sdk до версии 1.16.4 или более поздней.
- Добавьте ссылку Microsoft.AspNetCore.App на платформу, если приложение не предназначено для платформа .NET Framework.
В следующем фрагменте кода показана эта конфигурация в контексте файла проекта:
Заполнители
Заполнители — это возможность платформы, которая улучшает холодный запуск для приложений, предназначенных для .NET 6 или более поздней версии. Чтобы использовать эту оптимизацию, необходимо явно включить заполнители, выполнив следующие действия:
- Обновите конфигурацию проекта, чтобы использовать последние версии зависимостей, как описано в предыдущем разделе.
- WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED Задайте для параметра 1 приложения значение, которое можно сделать с помощью команды az functionapp config appsettings set:
az functionapp config appsettings set -g -n --settings 'WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED=1'
az functionapp config set -g -n --net-framework-version
az functionapp config set -g -n --use-32bit-worker-process false
Если задано WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED 1 значение, все остальные конфигурации приложений-функций должны быть заданы правильно. В противном случае приложение-функция может не запуститься.
Оптимизированный исполнитель
Исполнитель функции — это компонент платформы, которая вызывает вызовы. Оптимизированная версия этого компонента включена по умолчанию начиная с версии 1.16.2 пакета SDK. Дальнейшая настройка не требуется.
ReadyToRun
Приложение-функцию можно скомпилировать как двоичные файлы ReadytoRun. ReadyToRun — это форма предварительной компиляции, которая может повысить производительность запуска, чтобы снизить влияние холодного запуска при выполнении в плане потребления. ReadyToRun доступен в .NET 6 и более поздних версиях и требует версии 4.0 или более поздней версии среды выполнения Функции Azure.
ReadyToRun требует, чтобы проект был построен на основе архитектуры среды выполнения приложения размещения. Если они не согласованы, приложение столкнется с ошибкой при запуске. Выберите идентификатор среды выполнения из этой таблицы:
Операционная система | Приложение равно 32-разрядному 1 | Идентификатор среды выполнения |
---|---|---|
Windows | Истина | win-x86 |
Windows | False | win-x64 |
Linux | Истина | Не поддерживается. |
Linux | False | linux-x64 |
1 Только 64-разрядные приложения имеют право на некоторые другие оптимизации производительности.
Чтобы проверка, если приложение Windows имеет 32-разрядную или 64-разрядную версию, можно выполнить следующую команду CLI, подставив имя группы ресурсов и имя приложения. Выходные данные true указывают на то, что приложение имеет 32-разрядную версию, а значение false — 64-разрядную версию.
az functionapp config show -g -n --query "use32BitWorkerProcess"
Вы можете изменить приложение на 64-разрядную с помощью следующей команды, используя те же подстановки:
az functionapp config set -g -n --use-32bit-worker-process false`
Чтобы скомпилировать проект как ReadyToRun, обновите файл проекта, добавив элементы и . В следующем примере показана конфигурация публикации в приложении-функции Windows 64-разрядной версии.
net8.0 v4 win-x64 true
Если вы не хотите задать в качестве части файла проекта, можно также настроить его как часть самого жеста публикации. Например, с приложением-функцией Windows 64-разрядной версии команда .NET CLI будет:
dotnet publish --runtime win-x64
В Visual Studio параметр целевой среды выполнения в профиле публикации должен иметь правильный идентификатор среды выполнения. Если задано значение по умолчанию Portable, ReadyToRun не используется.
Развертывание в Функции Azure
При запуске в Azure проект кода функции должен выполняться в приложении-функции или в контейнере Linux. Приложение-функция и другие необходимые ресурсы Azure должны существовать перед развертыванием кода.
Вы также можете развернуть приложение-функцию в контейнере Linux. Дополнительные сведения см. в статье «Работа с контейнерами и Функции Azure».
Создание ресурсов Azure
Вы можете создать приложение-функцию и другие необходимые ресурсы в Azure с помощью одного из следующих методов:
- Visual Studio: Visual Studio может создавать ресурсы для вас во время процесса публикации кода.
- Visual Studio Code: Visual Studio Code может подключаться к подписке, создавать ресурсы, необходимые приложению, а затем публиковать код.
- Azure CLI. Вы можете использовать Azure CLI для создания необходимых ресурсов в Azure.
- Azure PowerShell. С помощью Azure PowerShell можно создать необходимые ресурсы в Azure.
- Шаблоны развертывания. Вы можете использовать шаблоны ARM и файлы Bicep для автоматизации развертывания необходимых ресурсов в Azure. Убедитесь, что шаблон содержит все необходимые параметры.
- портал Azure. В портал Azure можно создать необходимые ресурсы.
Опубликовать проект кода
После создания приложения-функции и других необходимых ресурсов в Azure можно развернуть проект кода в Azure с помощью одного из следующих методов:
- Visual Studio: простое развертывание вручную во время разработки.
- Visual Studio Code: простое развертывание вручную во время разработки.
- Функции Azure Core Tools: развертывание файла проекта из командной строки.
- Непрерывное развертывание: полезно для постоянного обслуживания, часто в промежуточный слот.
- Шаблоны развертывания. Для автоматизации развертываний пакетов можно использовать шаблоны ARM или файлы Bicep.
Требования к развертыванию
Существует несколько требований для выполнения функций .NET в изолированной рабочей модели в Azure в зависимости от операционной системы:
- Windows
- Linux
- FUNCTIONS_WORKER_RUNTIME должно быть задано значение dotnet-isolated .
- netFrameworkVersion должен иметь нужную версию.
- FUNCTIONS_WORKER_RUNTIME должно быть задано значение dotnet-isolated .
- linuxFxVersion необходимо задать правильное базовое изображение, например DOTNET-ISOLATED|8.0 .
При создании приложения-функции в Azure с помощью методов, описанных в предыдущем разделе, эти необходимые параметры добавляются для вас. При создании этих ресурсов с помощью шаблонов ARM или Bicep-файлов для автоматизации необходимо установить их в шаблоне.
Отладка
При локальном запуске с помощью Visual Studio или Visual Studio Code вы можете выполнить отладку изолированного рабочего проекта .NET как обычно. Однако существует два сценария отладки, которые не работают должным образом.
Удаленная отладка с помощью Visual Studio
Так как приложение изолированного рабочего процесса выполняется вне среды выполнения Функций, необходимо подключить удаленный отладчик к отдельному процессу. Дополнительные сведения об отладке с помощью Visual Studio см. в статье Удаленная отладка.
Отладка при ориентации на .NET Framework
Если изолированный проект предназначен для .NET Framework 4.8, для области текущей предварительной версии необходимо вручную выполнить действия по включению отладки. Эти действия не требуются, если используется другая целевая платформа.
Приложение должно начинаться с вызова FunctionsDebugger.Enable(); в качестве первой операции. Это происходит в методе Main() перед инициализацией HostBuilder. Файл Program.cs должен выглядеть следующим образом:
using System; using System.Diagnostics; using Microsoft.Extensions.Hosting; using Microsoft.Azure.Functions.Worker; using NetFxWorker; namespace MyDotnetFrameworkProject < internal class Program < static void Main(string[] args) < FunctionsDebugger.Enable(); var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .Build(); host.Run(); >> >
Затем необходимо вручную подключиться к процессу с помощью отладчика .NET Framework. Visual Studio не делает это автоматически для изолированных рабочих процессов, платформа .NET Framework приложений, и следует избежать операции «Начать отладку».
В каталоге проекта (или выходном каталоге его сборки) выполните следующую команду:
func host start --dotnet-isolated-debug
Это запускает рабочую роль, и процесс останавливается со следующим сообщением:
Azure Functions .NET Worker (PID: ) initialized in debug mode. Waiting for debugger to attach.
Где — идентификатор рабочего процесса. Теперь можно использовать Visual Studio для ручного присоединения к процессу. Инструкции по этой операции см. в разделе Подключение к запущенному процессу.
После подключения отладчика выполнение процесса возобновляется, и вы сможете выполнить отладку.
Предварительная версия .NET
До общедоступного выпуска версия .NET может быть выпущена в режиме предварительной версии или в режиме go-live. Дополнительные сведения об этих состояниях см. в политике поддержки .NET.
Хотя может оказаться возможным использовать указанный выпуск из локального проекта Функций, приложения-функции, размещенные в Azure, могут не иметь этого выпуска. Функции Azure можно использовать только с предварительными версиями или выпусками Go-live, указанными в этом разделе.
Функции Azure в настоящее время не работает с выпусками .NET предварительной версии или Go-live. Список общедоступных выпусков, которые можно использовать, см. в поддерживаемых версиях .
Использование пакета SDK для .NET предварительной версии
Чтобы использовать Функции Azure с предварительной версией .NET, необходимо обновить проект следующим образом:
- Установка соответствующей версии пакета SDK для .NET в разработке
- TargetFramework Изменение параметра в .csproj файле
При развертывании в приложении-функции в Azure также необходимо убедиться, что платформа доступна приложению. Для этого в Windows можно использовать следующую команду CLI. Замените именем группы ресурсов и замените именем приложения-функции. Замените соответствующую строку версии, например v8.0 .
az functionapp config set -g -n --net-framework-version
Рекомендации по использованию предварительных версий .NET
Учитывайте эти рекомендации при использовании функций с предварительными версиями .NET:
- При создании функций в Visual Studio необходимо использовать Предварительную версию Visual Studio, которая поддерживает создание проектов Функции Azure с помощью пакетов SDK предварительной версии .NET.
- Убедитесь, что у вас есть последние инструменты и шаблоны функций. Чтобы обновить средства, выполните приведенные далее действия.
- Перейдите к параметрам инструментов>, выберите Функции Azure в разделе «Проекты и решения».
- Выберите «Проверить наличие обновлений » и установить обновления по запросу.
- В течение периода предварительной версии среда разработки может иметь более последнюю версию предварительной версии .NET, чем размещенную службу. Это может привести к сбою приложения-функции при развертывании. Для этого можно указать версию пакета SDK для использования global.json .
- dotnet —list-sdks Выполните команду и запишите предварительную версию, которую вы используете в настоящее время во время локальной разработки.
- dotnet new globaljson —sdk-version —force Выполните команду, где используется локальная версия. Например, dotnet new globaljson —sdk-version dotnet-sdk-8.0.100-preview.7.23376.3 —force система будет использовать пакет SDK для .NET 8 предварительной версии 7 при создании проекта.
Из-за JIT-загрузки платформ предварительной версии приложения-функции, работающие в Windows, могут увеличивать время холодного запуска при сравнении с более ранними версиями общедоступной версии.
50 оттенков Go: ловушки, подводные камни и распространённые ошибки новичков
Go — простой и забавный язык. Но в нём, как и в любых других языках, есть свои подводные камни. И во многих из них сам Go не виноват. Одни — это естественное следствие прихода программистов из других языков, другие возникают из-за ложных представлений и нехватки подробностей. Если вы найдёте время и почитаете официальные спецификации, вики, почтовые рассылки, публикации в блогах и исходный код, то многие из подводных камней станут для вас очевидны. Но далеко не каждый так начинает, и это нормально. Если вы новичок в Go, статья поможет сэкономить немало часов, которые вы бы потратили на отладку кода. Мы будем рассматривать версии Go 1.5 и ниже.
Уровень: абсолютный новичок
Уровень: более опытный новичок
Уровень: продвинутый новичок
1. Открывающую фигурную скобку нельзя размещать в отдельной строке
В большинстве других языков, использующих фигурные скобки, вам нужно выбирать, где их размещать. Go выбивается из правила. За это вы можете благодарить автоматическую вставку точки с запятой (точка с запятой предполагается в конце каждой строки, без анализа следующей). Да, в Go есть точка с запятой!
package main import "fmt" func main() < // ошибка, нельзя выносить открывающую фигурную скобку в отдельную строку fmt.Println("hello there!") >
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before
package main import "fmt" func main()
2. Неиспользуемые переменные
Если у вас есть неиспользуемые переменные, то код не скомпилируется. Исключение: переменные, которые объявляются внутри функций. Это правило не касается глобальных переменных. Также можно иметь неиспользуемые аргументы функций.
Если вы присвоили неиспользуемой переменной новое значение, то ваш код всё равно не будет компилироваться. Придётся её где-то использовать, чтобы угодить компилятору.
package main var gvar int // not an error func main() < var one int // ошибка, неиспользуемая переменная two := 2 // ошибка, неиспользуемая переменная var three int // ошибка, даже несмотря на присваивание значения 3 в следующей строке three = 3 func(unused string) < fmt.Println("Unused arg. No compile error") >("what?") >
/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used
package main import "fmt" func main()
Другое решение: комментировать или удалять неиспользуемые переменные.
3. Неиспользуемые импорты
Если вы импортируете пакет и потом не используете какие-либо из его функций, интерфейсов, структур или переменных, то код не скомпилируется. Если нужно импортировать пакет, идентификатор «_» в качестве его имени поможет избежать ошибок компилирования. Идентификатор «_» чаще всего применяется для использования сайд-эффектов импортированных библиотек.
package main import ( "fmt" "log" "time" ) func main()
/tmp/sandbox627475386/main.go:4: imported and not used: "fmt" /tmp/sandbox627475386/main.go:5: imported and not used: "log" /tmp/sandbox627475386/main.go:6: imported and not used: "time"
package main import ( _ "fmt" "log" "time" ) var _ = log.Println func main()
Другое решение: удалить или закомментировать неиспользуемые импорты. В этом поможет инструмент goimports.
4. Короткие объявления переменных можно использовать только внутри функций
package main myvar := 1 // ошибка func main()
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
package main var myvar = 1 func main()
5. Переобъявления переменных с помощью коротких объявлений
В одной области видимости выражения нельзя переобъявлять переменные, но это можно делать в объявлении нескольких переменных (multi-variable declarations), среди которых хотя бы одна — новая. Переобъявляемые переменные должны располагаться в том же блоке, иначе получится скрытая переменная (shadowed variable).
Неправильно:
package main func main() < one := 0 one := 1 // ошибка >
/tmp/sandbox706333626/main.go:5: no new variables on left side of :=
package main func main()
6. Нельзя использовать короткие объявления переменных для настройки значений полей
package main import ( "fmt" ) type info struct < result int >func work() (int,error) < return 13,nil >func main() < var data info data.result, err := work() // ошибка fmt.Printf("info: %+v\n",data) >
prog.go:18: non-name data.result on left side of :=
Хотя разработчикам Go уже предлагали это исправить, не стоит надеяться на перемены: Робу Пайку нравится всё «как есть». Вам помогут временные переменные. Или предварительно объявляйте все свои переменные и используйте стандартный оператор присваивания.
package main import ( "fmt" ) type info struct < result int >func work() (int,error) < return 13,nil >func main() < var data info var err error data.result, err = work() // ok if err != nil < fmt.Println(err) return >fmt.Printf("info: %+v\n",data) // выводит: info: >
7. Случайное сокрытие переменных
Синтаксис короткого объявления переменных так удобен (особенно для тех, кто пришёл в Go из динамических языков), что его легко принять за регулярную операцию присваивания. Если вы сделаете эту ошибку в новом блоке кода, то компилятор не выдаст ошибку, но приложение будет работать некорректно.
package main import "fmt" func main() < x := 1 fmt.Println(x) // выводит 1 < fmt.Println(x) // выводит 1 x := 2 fmt.Println(x) // выводит 2 >fmt.Println(x) // выводит 1 (плохо, если нужно было 2) >
Это очень распространённая ошибка даже среди опытных Go-разработчиков. Её легко совершить и трудно заметить. Для выявления подобных ситуаций можно использовать команду vet. По умолчанию она не выполняет проверку переменных на скрытость. Поэтому используйте флаг -shadow: go tool vet -shadow your_file.go
8. Нельзя использовать nil для инициализации переменной без явного указания типа
Идентификатор nil можно использовать как «нулевое значение» (zero value) для интерфейсов, функций, указателей, хеш-таблиц (map), слайсов (slices) и каналов. Если не задать тип переменной, то компилятор не сможет завершить работу, потому что не сумеет угадать тип.
package main func main() < var x = nil // ошибка _ = x >
/tmp/sandbox188239583/main.go:4: use of untyped nil
package main func main() < var x interface<>= nil _ = x >
9. Использование nil-слайсов (slice) и хеш-таблиц (map)
Можно добавлять элементы в nil -слайс, но если то же самое сделать с хеш-таблицей, то это приведёт к runtime panic.
package main func main()
package main func main() < var m map[string]int m["one"] = 1 // ошибка >
10. Ёмкость хеш-таблиц
Можно устанавливать ёмкость при создании хеш-таблиц, но нельзя применять к ним функцию cap() .
package main func main() < m := make(map[string]int,99) cap(m) // ошибка >
/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap
11. Строки не могут быть nil
Это подводный камень для начинающих, которые присваивают строковым переменным nil -идентификаторы.
package main func main() < var x string = nil // ошибка if x == nil < // ошибка x = "default" >>
/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)
func main() < var x string // возвращает значение по умолчанию "" (нулевое значение) if x == "" < x = "default" >>
12. Передача массивов в функции
Если вы разрабатываете на С/С++, то массивы для вас — указатели. Когда вы передаёте массивы функциям, функции ссылаются на ту же область памяти и поэтому могут обновлять исходные данные. В Go массивы являются значениями, так что, когда мы передаём их функциям, те получают копию исходного массива. Это может стать проблемой, если вы пытаетесь обновлять данные в массиве.
package main import "fmt" func main() < x := [3]intfunc(arr [3]int) < arr[0] = 7 fmt.Println(arr) // выводит [7 2 3] >(x) fmt.Println(x) // выводит [1 2 3] (плохо, если вам нужно было [7 2 3]) >
Если нужно обновить исходные данные в массиве, используйте типы указателей массива.
package main import "fmt" func main() < x := [3]intfunc(arr *[3]int) < (*arr)[0] = 7 fmt.Println(arr) // выводит &[7 2 3] >(&x) fmt.Println(x) // выводит [7 2 3] >
Другое решение: слайсы. Хотя ваша функция получает копию переменной слайса, та всё ещё является ссылкой на исходные данные.
package main import "fmt" func main() < x := []intfunc(arr []int) < arr[0] = 7 fmt.Println(arr) // выводит [7 2 3] >(x) fmt.Println(x) // выводит [7 2 3] >
13. Неожиданные значения в выражениях range в слайсах и массивах
Это может произойти, если вы привыкли к выражениям for-in или foreach в других языках. Но в Go выражение range отличается тем, что оно генерирует два значения: первое — это индекс элемента (item index), а второе — данные элемента (item data).
package main import "fmt" func main() < x := []stringfor v := range x < fmt.Println(v) // выводит 0, 1, 2 >>
package main import "fmt" func main() < x := []stringfor _, v := range x < fmt.Println(v) // выводит a, b, c >>
14. Одномерность слайсов и массивов
Кажется, что Go поддерживает многомерные массивы и слайсы? Нет, это не так. Хотя можно создавать массивы из массивов и слайсы из слайсов. С точки зрения производительности и сложности — далеко не идеальное решение для приложений, которые выполняют числовые вычисления и основаны на динамических многомерных массивах.
Можно создавать динамические многомерные массивы с помощью обычных одномерных массивов, слайсов из «независимых» слайсов, а также слайсов из слайсов «с совместно используемыми данными».
Если вы используете обычные одномерные массивы, то при их росте вы отвечаете за индексирование, проверку границ и перераспределение памяти.
Процесс создания динамического многомерного массива с помощью слайсов из «независимых» слайсов состоит из двух шагов. Сначала нужно создать внешний слайс, а затем разместить в памяти все внутренние слайсы. Внутренние слайсы не зависят друг от друга. Их можно увеличивать и уменьшать, не затрагивая другие.
package main func main() < x := 2 y := 4 table := make([][]int,x) for i:= range table < table[i] = make([]int,y) >>
Создание динамического многомерного массива с помощью слайсов из слайсов «с совместно используемыми данными» состоит из трёх шагов. Сначала нужно создать слайс, выполняющий роль «контейнера» данных, он содержит исходные данные (raw data). Затем — внешний слайс. В конце мы инициализируем каждый из внутренних слайсов, перенарезая слайс с исходными данными.
package main import "fmt" func main() < h, w := 2, 4 raw := make([]int,h*w) for i := range raw < raw[i] = i >fmt.Println(raw,&raw[4]) // выводит: [0 1 2 3 4 5 6 7] table := make([][]int,h) for i:= range table < table[i] = raw[i*w:i*w + w] >fmt.Println(table,&table[1][0]) // выводит: [[0 1 2 3] [4 5 6 7]] >
Предлагается разработать спецификацию на многомерные массивы и слайсы, но сейчас, судя по всему, у этой задачи низкий приоритет.
15. Обращение к несуществующим ключам в map
Эту ошибку совершают разработчики, которые при обращении к несуществующему ключу ожидают получить nil -значение (как это происходит в некоторых языках). Возвращаемое значение будет nil , если «нулевое значение» для соответствующего типа данных — nil . Но для других типов возвращаемое значение окажется другим. Определять, существует ли запись в хеш-таблице (map record), можно с помощью проверки на правильное «нулевое значение». Но это не всегда надёжно (например, что вы будете делать, если у вас есть таблица булевых значений, где «нулевое значение» — false). Самый надёжный способ узнать, существует ли запись, — проверить второе значение, возвращаемое операцией доступа к таблице.
package main import "fmt" func main() < x := map[string]stringif v := x["two"]; v == "" < // некорректно fmt.Println("no entry") >>
package main import "fmt" func main() < x := map[string]stringif _,ok := x["two"]; !ok < fmt.Println("no entry") >>
16. Неизменяемость строк
Если вы попытаетесь обновить отдельные символы строковой переменной с помощь оператора индекса, то это не сработает. Строки — это байт-слайсы (byte slices), доступные только для чтения. Если вам все-таки нужно обновить строку, то стоит использовать байт-слайс и преобразовывать его в строку по необходимости.
package main import "fmt" func main()
/tmp/sandbox305565531/main.go:7: cannot assign to x[0]
package main import "fmt" func main() < x := "text" xbytes := []byte(x) xbytes[0] = 'T' fmt.Println(string(xbytes)) // выводит Text >
Стоит заметить, что это неправильный способ обновления символов в текстовой строке, потому что символ может состоять из нескольких байтов. В этом случае лучше конвертировать строку в слайс из «рун» (rune). Но даже внутри слайсов из «рун» одиночный символ может быть разбит на несколько рун, например если есть символ апострофа (grave accent). Такая непростая и запутанная природа «символов» является причиной того, что в Go строковые значения представляют собой последовательностей байтов.
17. Преобразование строк в байт-слайсы (Byte Slices), и наоборот
Когда вы преобразуете строку в байт-слайс (и наоборот), вы получаете полную копию исходных данных. Это не операция приведения (cast operation), как в других языках, и не перенарезка (reslicing), когда переменная нового слайса указывает на один и тот же массив, занятый исходным байт-слайсом.
В Go есть несколько оптимизаций для преобразований из []byte в string и из string в []byte , позволяющих избегать дополнительных выделений памяти (ещё больше оптимизаций в списке todo).
Первая оптимизация позволяет избежать дополнительного выделения памяти, когда ключи []byte используются для поиска записей в коллекциях map[string]: m[string(key)] .
Вторая оптимизация позволяет избегать дополнительного выделения в выражениях for range , когда строки преобразуются в []byte: for i,v := range []byte(str) <. >.
18. Строки и оператор индекса
Оператор индекса, применяемый к строке, возвращает байтовое значение (byte value), а не символ (как в других языках).
package main import "fmt" func main() < x := "text" fmt.Println(x[0]) // выводит 116 fmt.Printf("%T",x[0]) // выводит uint8 >
Если нужно обратиться к конкретным «символам» (кодовым точкам/рунам Unicode), то используйте выражение for range . Также вам будут полезны официальный пакет unicode/utf8 и экспериментальный utf8string (golang.org/x/exp/utf8string). utf8string включает в себя удобный метод At() . Можно также преобразовать строку в слайс рун (slice of runes).
19. Строки — не всегда текст в кодировке UTF-8
Строковые значения необязательно должны быть представлены в виде текста в кодировке UTF-8. Здесь возможен произвольный набор байтов. Единственный случай, когда строки должны быть в кодировке UTF-8, — когда они используются как строковые литералы. Но даже они могут включать в себя данные с экранированными последовательностями.
Чтобы узнать кодировку строки, используйте функцию ValidString() из пакета unicode/utf8.
package main import ( "fmt" "unicode/utf8" ) func main() < data1 := "ABC" fmt.Println(utf8.ValidString(data1)) // выводит: true data2 := "A\xfeC" fmt.Println(utf8.ValidString(data2)) // выводит: false >
20. Длина строк
Допустим, вы разрабатываете на Python и у вас есть такой код:
data = u'♥' print(len(data)) # выводит: 1
Если преобразовать его в аналогичный код на Go, то результат может вас удивить.
package main import "fmt" func main() < data := "♥" fmt.Println(len(data)) // выводит: 3 >
Встроенная функция len() возвращает не символ, а количество байт, как это происходит с Unicode-строками в Python.
Чтобы получить такой же результат в Go, используйте функцию RuneCountInString() из пакета unicode/utf8.
package main import ( "fmt" "unicode/utf8" ) func main() < data := "♥" fmt.Println(utf8.RuneCountInString(data)) // выводит: 1
Технически функция RuneCountInString() не возвращает количество символов, потому что один символ может занимать несколько рун.
package main import ( "fmt" "unicode/utf8" ) func main() < data := "é" fmt.Println(len(data)) // выводит: 3 fmt.Println(utf8.RuneCountInString(data)) // выводит: 2 >
21. Отсутствующая запятая в многострочных литералах slice/array/map
package main func main() < x := []int< 1, 2 // error >_ = x >
/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected >
package main func main() < x := []int< 1, 2, >x = x y := []int // ошибки нет y = y >
Вы не получите ошибку компилирования, если оставите замыкающую запятую при объявлении в одну строчку.
22. log.Fatal и log.Panic не только журналируют
Библиотеки для логирования часто обеспечивают различные уровни для сообщений. В отличие от других языков, пакет логирования в Go делает больше. Если вызвать его функции Fatal*() и Panic*() , то приложение будет закрыто.
package main import "log" func main() < log.Fatalln("Fatal Level: log entry") // здесь выполняется выход из приложения log.Println("Normal Level: log entry") >
23. Несинхронизированные операции встроенных структур данных
Некоторые возможности Go нативно поддерживают многозадачность (concurrency), но в их число не входят потокобезопасные коллекции (concurrency safe). Вы сами отвечаете за атомарность обновления коллекций. Для реализации атомарных операций рекомендуется использовать горутины и каналы, но можно задействовать и пакет sync, если это целесообразно для вашего приложения.
24. Итерационные значения для строк в выражениях range
Значение индекса (первое значение, возвращаемое операцией range ) — это индекс первого байта текущего «символа» (кодовая точка/руна Unicode), возвращённый во втором значении. Это не индекс текущего «символа», как в других языках. Обратите внимание, что настоящий символ может быть представлен несколькими рунами. Если вам нужно работать именно с символами, то стоит использовать пакет norm (golang.org/x/text/unicode/norm).
Выражения for range со строковыми переменными пытаются интерпретировать данные как текст в кодировке UTF-8. Если они не распознают какую-либо последовательность байтов, то возвращают руны 0xfffd (символы замены Unicode), а не реальные данные. Если в вашей строке хранятся произвольные данные (не UTF-8), то для сохранения преобразуйте их в байт-слайсы.
package main import "fmt" func main() < data := "A\xfe\x02\xff\x04" for _,v := range data < fmt.Printf("%#x ",v) >// выводит: 0x41 0xfffd 0x2 0xfffd 0x4 (нехорошо) fmt.Println() for _,v := range []byte(data) < fmt.Printf("%#x ",v) >// выводит: 0x41 0xfe 0x2 0xff 0x4 (хорошо) >
25. Итерирование хеш-таблиц (map) с помощью выражения for range
На этот подводный камень натыкаются те, кто ожидают, что элементы будут располагаться в определённом порядке (например, отсортированные по значению ключа). Каждая итерация хеш-таблицы приводит к разным результатам. Среда исполнения (runtime) Go пытается сделать всё возможное, рандомизируя порядок итерирования, но ей это не всегда удаётся, поэтому вы можете получить несколько одинаковых итераций (например, пять) подряд.
package main import "fmt" func main() < m := map[string]intfor k,v := range m < fmt.Println(k,v) >>
А если вы используете Go Playground (https://play.golang.org/), то всегда будете получать одинаковые результаты, потому что код не перекомпилируется, пока вы его не измените.
26. Сбойное поведение в выражениях switch
Блоки case в выражениях switch по умолчанию прерываются (break). В других языках поведение по умолчанию другое: переход (fall through) к следующему блоку case .
package main import "fmt" func main() < isSpace := func(ch byte) bool < switch(ch) < case ' ': // ошибка case '\t': return true >return false > fmt.Println(isSpace('\t')) // выводит true (хорошо) fmt.Println(isSpace(' ')) // выводит false (плохо) >
Можно заставить блоки case переходить принудительно с помощью выражения fallthrough в конце каждого блока. Можно также переписать ваше выражение switch , чтобы в блоках использовались списки выражений.
package main import "fmt" func main() < isSpace := func(ch byte) bool < switch(ch) < case ' ', '\t': return true >return false > fmt.Println(isSpace('\t')) // выводит true (хорошо) fmt.Println(isSpace(' ')) // выводит true (хорошо) >
27. Инкременты и декременты
Во многих языках есть операторы инкрементирования и декрементирования. Но в Go не поддерживаются их префиксные версии. Также нельзя в одном выражении использовать оба этих выражения.
package main import "fmt" func main() < data := []inti := 0 ++i // error fmt.Println(data[i++]) // ошибка >
/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++ /tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :
package main import "fmt" func main() < data := []inti := 0 i++ fmt.Println(data[i]) >
28. Побитовый NOT-оператор
Во многих языках символ ~ используется в качестве унарной NOT-операции (aka побитовое дополнение, bitwise complement), однако в Go для этого применяется XOR-оператор (^).
package main import "fmt" func main() < fmt.Println(~2) // ошибка >
/tmp/sandbox965529189/main.go:6: the bitwise complement operator is ^
package main import "fmt" func main()
Кого-то может запутать, что ^ в Go — это XOR-оператор. Если хотите, выражайте унарную NOT-операцию (например, NOT 0x02 ) с помощью бинарной XOR-операции (например, 0x02 XOR 0xff ). Это объясняет, почему ^ используется для выражения унарной NOT-операции.
Также в Go есть специальный побитовый оператор AND NOT (&^), который легко принять за оператор NOT. AND NOT выглядит как специальная функция/хак ради поддержки A AND (NOT B) без обязательного использования фигурных скобок.
package main import "fmt" func main()
29. Различия приоритетов операторов
Помимо «довольно понятных» (bit clear) операторов (&^), в Go есть набор стандартных операторов, используемых многими другими языками. Но их приоритеты в данном случае не всегда такие же.
package main import "fmt" func main() < fmt.Printf("0x2 & 0x2 + 0x4 ->%#x\n",0x2 & 0x2 + 0x4) //prints: 0x2 & 0x2 + 0x4 -> 0x6 //Go: (0x2 & 0x2) + 0x4 //C++: 0x2 & (0x2 + 0x4) -> 0x2 fmt.Printf("0x2 + 0x2 %#x\n",0x2 + 0x2 << 0x1) //prints: 0x2 + 0x2 0x6 //Go: 0x2 + (0x2 << 0x1) //C++: (0x2 + 0x2) 0x8 fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2) //prints: 0xf | 0x2 ^ 0x2 -> 0xd //Go: (0xf | 0x2) ^ 0x2 //C++: 0xf | (0x2 ^ 0x2) -> 0xf >
30. Неэкспортированные поля структур не кодируются
Поля структур (struct fields), начинающиеся со строчных букв, не будут кодироваться (JSON, XML, GON и т. д.), так что при декодировании структуры вы получите в этих неэкспортированных полях нулевые значения.
package main import ( "fmt" "encoding/json" ) type MyData struct < One int two string >func main() < in := MyDatafmt.Printf("%#v\n",in) // выводит main.MyData encoded,_ := json.Marshal(in) fmt.Println(string(encoded)) // выводит var out MyData json.Unmarshal(encoded,&out) fmt.Printf("%#v\n",out) // выводит main.MyData >
31. Выход из приложений с помощью активных горутин
Приложение не будет ждать завершения ваших горутин. Новички часто об этом забывают. Все когда-то начинают — в таких ошибках нет ничего стыдного.
package main import ( "fmt" "time" ) func main() < workerCount := 2 for i := 0; i < workerCount; i++ < go doit(i) >time.Sleep(1 * time.Second) fmt.Println("all done!") > func doit(workerId int)
[0] is running [1] is running all done!
Одно из самых популярных решений — переменная WaitGroup . Это позволит главной горутине ожидать завершения работы всех рабочих горутин. Если ваше приложение использует долго выполняемые рабочие горутины с циклами обработки сообщений, то вам понадобится как-то сигнализировать им о том, что пора выходить. Можно отправлять каждой такой горутине сообщение kill . Или закрывать каналы, из которых рабочие горутины получают данные: это простой способ сигнализировать оптом.
package main import ( "fmt" "sync" ) func main() < var wg sync.WaitGroup done := make(chan struct<>) workerCount := 2 for i := 0; i < workerCount; i++ < wg.Add(1) go doit(i,done,wg) >close(done) wg.Wait() fmt.Println("all done!") > func doit(workerId int,done ,wg sync.WaitGroup)
Если запустить это приложение, вы увидите:
[0] is running [0] is done [1] is running [1] is done
Похоже, все горутины закончили работать до выхода главной горутины. Замечательно! Однако вы увидите и это:
fatal error: all goroutines are asleep - deadlock!
Нехорошо! Что происходит? Откуда взялась взаимоблокировка? Ведь все вышли и выполнили wg.Done() . Приложение должно работать.
Блокировка возникает, потому что каждый рабочий получает копию исходной переменной WaitGroup . И когда все они выполняют wg.Done() , это никак не влияет на переменную WaitGroup в главной горутине.
package main import ( "fmt" "sync" ) func main() < var wg sync.WaitGroup done := make(chan struct<>) wq := make(chan interface<>) workerCount := 2 for i := 0; i < workerCount; i++ < wg.Add(1) go doit(i,wq,done,&wg) >for i := 0; i < workerCount; i++ < wq close(done) wg.Wait() fmt.Println("all done!") > func doit(workerId int, wq ,done ,wg *sync.WaitGroup) < fmt.Printf("[%v] is running\n",workerId) defer wg.Done() for < select < case m := %v\n",workerId,m) case > >
Теперь всё работает правильно.
32. При отправке в небуферизованный канал данные возвращаются по мере готовности получателя
Отправитель не будет заблокирован, пока получатель обрабатывает ваше сообщение. В зависимости от машины, на которой выполняется код, получающая горутина может и не иметь достаточно времени на обработку сообщения, прежде чем продолжится выполнение отправителя.
package main import "fmt" func main() < ch := make(chan string) go func() < for m := range ch < fmt.Println("processed:",m) >>() ch
33. Отправка в закрытый канал приводит к panic
Получение из закрытого канала безопасно. Возвращаемое значение ok в получаемом выражении (receive statement) станет false , что говорит о том, что никакие данные не были получены. Если вы получаете из буферизованного канала, то получите сначала буферизованные данные, а когда они закончатся, выражение ok станет false .
Отправка данных в закрытый канал приводит к panic. Это задокументированное поведение, но оно не всегда интуитивно ожидаемо разработчиками, которые могут считать, что поведение при отправке будет аналогично поведению при приёме.
package main import ( "fmt" "time" ) func main() < ch := make(chan int) for i := 0; i < 3; i++ < go func(idx int) < ch (i) > // get the first result fmt.Println( <-ch) close(ch) //нехорошо (у вас всё ещё есть другие отправители) // do other work time.Sleep(2 * time.Second) >
Решение зависит от вашего приложения. Это может быть небольшое изменение кода — или архитектуры, если потребуется. В любом случае удостоверьтесь, что приложение не пытается отправлять данные в закрытый канал.
Пример с багом можно исправить, сигнализируя через специальный канал отмены (special cancellation channel) остальным рабочим горутинам, что их результаты больше не нужны.
package main import ( "fmt" "time" ) func main() < ch := make(chan int) done := make(chan struct<>) for i := 0; i < 3; i++ < go func(idx int) < select < case ch >(i) > // get first result fmt.Println("result:", <-ch) close(done) // do other work time.Sleep(3 * time.Second) >
34. Использование «nil»-каналов
В канале nil операции отправки и приёма блокируются навсегда. Это хорошо задокументированное поведение, но оно может стать сюрпризом для новичков.
package main import ( "fmt" "time" ) func main() < var ch chan int for i := 0; i < 3; i++ < go func(idx int) < ch (i) > // get first result fmt.Println("result:", <-ch) // do other work time.Sleep(2 * time.Second) >
При выполнении этого кода вы увидите ошибку runtime наподобие fatal error: all goroutines are asleep - deadlock!
Это поведение можно использовать для динамического включения и отключения блоков case в выражении select .
package main import "fmt" import "time" func main() < inch := make(chan int) outch := make(chan int) go func() < var in > >() go func() < for r := range outch < fmt.Println("result:",r) >>() time.Sleep(0) inch
35. Методы, принимающие параметры по значению, не меняют исходных значений
Параметры методов — это как обычные аргументы функций. Если они объявляются значением, то функция/метод получает копию вашего аргумента (receiver argument). Изменения в принятом значении не повлияют на исходное значение, если значение — переменная хеш-таблицы (map) или слайса и вы обновляете элементы коллекции или если обновляемые поля в значении — это указатели.
package main import "fmt" type data struct < num int key *string items map[string]bool >func (this *data) pmethod() < this.num = 7 >func (this data) vmethod() < this.num = 8 *this.key = "v.key" this.items["vmethod"] = true >func main() < key := "key.1" d := datafmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items) // prints num=1 key=key.1 items=map[] d.pmethod() fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items) // prints num=7 key=key.1 items=map[] d.vmethod() fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items) // prints num=7 key=v.key items=map[vmethod:true] >
36. Закрытие тела HTTP-ответа
Делая запрос с помощью стандартной HTTP-библиотеки, вы получаете переменную HTTP-ответа. Даже если вы не читаете тело ответа, всё равно нужно его закрыть. Обратите внимание: это относится и к пустым ответам. О них очень легко забыть, особенно новичкам.
Некоторые новички пытаются закрывать тело ответа, но в неправильном месте.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() < resp, err := http.Get("https://api.ipify.org?format=json") defer resp.Body.Close()// неправильно if err != nil < fmt.Println(err) return >body, err := ioutil.ReadAll(resp.Body) if err != nil < fmt.Println(err) return >fmt.Println(string(body)) >
Этот код будет работать с успешными HTTP-запросами, но в случае сбоя переменная resp может быть nil , что приведёт к runtime panic.
Самый распространённый способ закрыть тело ответа — с помощью вызова defer после проверки ошибочности HTTP-ответа.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() < resp, err := http.Get("https://api.ipify.org?format=json") if err != nil < fmt.Println(err) return >defer resp.Body.Close()// допустимо, в большинстве случаев :-) body, err := ioutil.ReadAll(resp.Body) if err != nil < fmt.Println(err) return >fmt.Println(string(body)) >
В большинстве случаев, когда возникают сбои HTTP-запросов, переменная resp будет nil , а переменная err — non-nil . Но при сбое переадресации обе переменные будут non-nil . Это означает возникновение утечки.
Её можно предотвратить, добавив вызов для закрытия тел ответов non-nil в блоке обработки ошибок HTTP-запросов. Другое решение: использовать один вызов defer для закрытия тел ответов для всех сбойных и успешных запросов.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() < resp, err := http.Get("https://api.ipify.org?format=json") if resp != nil < defer resp.Body.Close() >if err != nil < fmt.Println(err) return >body, err := ioutil.ReadAll(resp.Body) if err != nil < fmt.Println(err) return >fmt.Println(string(body)) >
Исходная реализация resp.Body.Close() также считывает и отклоняет данные оставшихся тел ответов. Благодаря этому HTTP-соединение может быть повторно использовано для другого запроса, если включено поведение keep alive . Поведение самого последнего HTTP-клиента отличается. Теперь вы ответственны за чтение и отклонение оставшихся данных ответов. Если этого не сделать, то HTTP-соединение вместо повторного использования может быть закрыто. Надеюсь, этот маленький подводный камень будет задокументирован в Go 1.5.
Если для вашего приложения важно повторно использовать HTTP-соединения, то в конце логики обработки ответа может понадобиться добавить что-то вроде этого:
_, err = io.Copy(ioutil.Discard, resp.Body)
Это будет необходимо, если вы не считываете всё тело ответа немедленно, например при обработке ответов JSON API с помощью подобного кода:
json.NewDecoder(resp.Body).Decode(&data)
37. Закрытие HTTP-соединений
Некоторые HTTP-серверы какое-то время держат сетевые соединения открытыми (согласно спецификации HTTP 1.1 и серверной конфигурации keep alive ). По умолчанию стандартная HTTP-библиотека закрывает соединения, только когда об этом просит целевой HTTP-сервер. Тогда при определённых условиях в вашем приложении могут закончиться сокеты / файловые дескрипторы.
Можно попросить библиотеку закрывать соединение после завершения вашего запроса, задав значение true в поле Close переменной запроса.
Другое решение: добавить заголовок запроса Connection и задать ему значение close . Целевой HTTP-сервер тоже должен ответить заголовком Connection: close . Когда библиотека его увидит, она закроет соединение.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() < req, err := http.NewRequest("GET","http://golang.org",nil) if err != nil < fmt.Println(err) return >req.Close = true //or do this: //req.Header.Add("Connection", "close") resp, err := http.DefaultClient.Do(req) if resp != nil < defer resp.Body.Close() >if err != nil < fmt.Println(err) return >body, err := ioutil.ReadAll(resp.Body) if err != nil < fmt.Println(err) return >fmt.Println(len(string(body))) >
Можно ещё глобально отключить повторное использование HTTP-соединений. Для этого создайте кастомную конфигурацию HTTP-транспорта.
package main import ( "fmt" "net/http" "io/ioutil" ) func main() < tr := &http.Transportclient := &http.Client resp, err := client.Get("http://golang.org") if resp != nil < defer resp.Body.Close() >if err != nil < fmt.Println(err) return >fmt.Println(resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil < fmt.Println(err) return >fmt.Println(len(string(body))) >
Если вы отправляете на один сервер много запросов, то этого достаточно для сохранения соединения открытым. Но если приложение за короткое время шлёт один-два запроса на много разных серверов, то лучше закрывать соединения сразу после получения ответов. Также можно увеличить лимит на количество открытых файлов. Что лучше — зависит от вашего приложения.
38. Десериализация (unmarshalling) JSON-чисел в интерфейсные значения
Когда вы декодируете/десериализуете JSON-данные в интерфейс, Go по умолчанию обращается с числовыми значениями в JSON как с числами float64 . Значит, вот такой код вызовет panic:
package main import ( "encoding/json" "fmt" ) func main() < var data = []byte(``) var result map[string]interface<> if err := json.Unmarshal(data, &result); err != nil < fmt.Println("error:", err) return >var status = result["status"].(int) // ошибка fmt.Println("status value:",status) >
panic: interface conversion: interface is float64, not int
Если JSON-значение, которое вы пытаетесь декодировать, целочисленное, есть несколько вариантов.
- Использовать значение с плавающей запятой как есть 🙂
- Преобразовать значение с плавающей запятой в целочисленный тип, который вам нужен.
package main import ( "encoding/json" "fmt" ) func main() < var data = []byte(``) var result map[string]interface<> if err := json.Unmarshal(data, &result); err != nil < fmt.Println("error:", err) return >var status = uint64(result["status"].(float64)) // хорошо fmt.Println("status value:",status) >
package main import ( "encoding/json" "bytes" "fmt" ) func main() < var data = []byte(``) var result map[string]interface<> var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&result); err != nil < fmt.Println("error:", err) return >var status,_ = result["status"].(json.Number).Int64() // хорошо fmt.Println("status value:",status) >
Можно использовать строковое представление вашего значения Number , чтобы десериализовать его в другой числовой тип:
package main import ( "encoding/json" "bytes" "fmt" ) func main() < var data = []byte(``) var result map[string]interface<> var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&result); err != nil < fmt.Println("error:", err) return >var status uint64 if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); err != nil < fmt.Println("error:", err) return >fmt.Println("status value:",status) >
package main import ( "encoding/json" "bytes" "fmt" ) func main() < var data = []byte(``) var result struct < Status uint64 `json:"status"` >if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil < fmt.Println("error:", err) return >fmt.Printf("result => %+v",result) //prints: result => >
Это полезно, если вы должны выполнить декодирование условного JSON-поля (conditional field) в условиях возможности изменения структуры или типа поля.
package main import ( "encoding/json" "bytes" "fmt" ) func main() < records := [][]byte< []byte(``), []byte(``), > for idx, record := range records < var result struct < StatusCode uint64 StatusName string Status json.RawMessage `json:"status"` Tag string `json:"tag"` >if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil < fmt.Println("error:", err) return >var sstatus string if err := json.Unmarshal(result.Status, &sstatus); err == nil < result.StatusName = sstatus >var nstatus uint64 if err := json.Unmarshal(result.Status, &nstatus); err == nil < result.StatusCode = nstatus >fmt.Printf("[%v] result => %+v\n",idx,result) > >
39. Сравнение struct, array, slice и map
Можно использовать оператор эквивалентности == для сравнения переменных структур, если каждое поле структуры можно сравнить с помощью этого оператора.
package main import "fmt" type data struct < num int fp float32 complex complex64 str string char rune yes bool events ref *byte raw [10]byte > func main() < v1 := data<>v2 := data<> fmt.Println("v1 == v2:",v1 == v2) // выводит: v1 == v2: true >
Если хоть одно из полей несравниваемо, то применение оператора эквивалентности приведёт к ошибке компилирования. Обратите внимание, что сравнивать массивы можно только тогда, когда сравниваемы их данные.
package main import "fmt" type data struct < num int // ok checks [10]func() bool // несравниваемо doit func() bool // несравниваемо m map[string] string // несравниваемо bytes []byte // несравниваемо >func main() < v1 := data<>v2 := data<> fmt.Println("v1 == v2:",v1 == v2) >
Go предоставляет несколько вспомогательных функций для сравнения переменных, которые нельзя сравнивать с помощью операторов сравнения.
Самое популярное решение: использовать функцию DeepEqual() из пакета reflect.
package main import ( "fmt" "reflect" ) type data struct < num int // ok checks [10]func() bool // несравниваемо doit func() bool // несравниваемо m map[string] string // несравниваемо bytes []byte // несравниваемо >func main() < v1 := data<>v2 := data<> fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) // prints: v1 == v2: true m1 := map[string]string m2 := map[string]string fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) // prints: m1 == m2: true s1 := []int s2 := []int fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) // prints: s1 == s2: true >
Помимо невысокой скорости (что может быть критично для вашего приложения), DeepEqual() имеет свои собственные подводные камни.
package main import ( "fmt" "reflect" ) func main() < var b1 []byte = nil b2 := []byte<>fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) // prints: b1 == b2: false >
DeepEqual() не считает пустой слайс эквивалентным nil -слайсу. Это поведение отличается от того, что вы получите при использовании функции bytes.Equal() : она считает эквивалентными nil и пустые слайсы.
package main import ( "fmt" "bytes" ) func main() < var b1 []byte = nil b2 := []byte<>fmt.Println("b1 == b2:",bytes.Equal(b1, b2)) // prints: b1 == b2: true >
DeepEqual() не всегда идеальна при сравнении слайсов.
package main import ( "fmt" "reflect" "encoding/json" ) func main() < var str string = "one" var in interface<>= "one" fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in)) //prints: str == in: true true v1 := []string v2 := []interface<> fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2)) //prints: v1 == v2: false (not ok) data := map[string]interface<>< "code": 200, "value": []string, > encoded, _ := json.Marshal(data) var decoded map[string]interface<> json.Unmarshal(encoded, &decoded) fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded)) //prints: data == decoded: false (not ok) >
Если ваши байт-слайсы (или строки) содержат текстовые данные, то, когда понадобится сравнить значения без учёта регистра, вы можете использовать ToUpper() или ToLower() из пакетов bytes и strings (прежде чем прибегнуть к == , bytes.Equal() или bytes.Compare() ). Это сработает для англоязычных текстов, но не для многих других языков. Так что лучше выбрать strings.EqualFold() и bytes.EqualFold() .
Если ваши байт-слайсы содержат секретные данные (криптографические хеши, токены и т. д.), которые нужно сравнивать с предоставленной пользователями информацией, обойдитесь без reflect.DeepEqual() , bytes.Equal() или bytes.Compare() . Эти функции сделают приложение уязвимым к атакам по времени. Чтобы избежать утечки информации о времени, используйте функции из пакета crypto/subtle (например, subtle.ConstantTimeCompare() ).
40. Восстановление после panic
Функцию recover() можно использовать для поимки/перехвата panic. Это получится, только если вызвать её в блоке defer.
package main import "fmt" func main() < recover() // ничего не делает panic("not good") recover() // не будет выполнено :) fmt.Println("ok") >
package main import "fmt" func main() < defer func() < fmt.Println("recovered:",recover()) >() panic("not good") >
Вызов recover() сработает, только если будет выполнен в блоке defer.
package main import "fmt" func doRecover() < fmt.Println("recovered =>",recover()) // prints: recovered => > func main() < defer func() < doRecover() // восстановление panic не произошло >() panic("not good") >
41. Обновление и привязка значений полей в slice, array и map в выражениях for range
Сгенерированные в выражениях range значения данных — это копии реальных элементов коллекций, а не ссылки на исходные элементы. Стало быть, обновление значений не изменит исходные данные. Кроме того, если взять адрес значения, то вы не получите указатель на исходные данные.
package main import "fmt" func main() < data := []intfor _,v := range data < v *= 10 // оригинал не изменился >fmt.Println("data:",data) // выводит: [1 2 3] >
Если вам нужно обновить исходное значение записи в коллекции, то для доступа к данным воспользуйтесь индексным оператором.
package main import "fmt" func main() < data := []intfor i,_ := range data < data[i] *= 10 >fmt.Println("data:",data) // выводит: [10 20 30] >
Если коллекция содержит значения указателей, то правила немного меняются. Вам всё ещё нужно использовать индексный оператор, чтобы исходная запись указывала на другое значение. Но вы можете обновлять данные, хранящиеся в целевом месте, с помощью второго значения в выражении for range .
package main import "fmt" func main() < data := []*struct,,> for _,v := range data < v.num *= 10 >fmt.Println(data[0],data[1],data[2]) // prints & & & >
42. «Скрытые данные» в слайсах
При перенарезке получившийся слайс будет ссылаться на массив исходного слайса. Не забывайте об этом. Иначе может возникнуть непредсказуемое потребление памяти, когда приложение разместит в ней крупные временные слайсы и создаст из них новые, чтобы ссылаться на небольшие куски исходных данных.
package main import "fmt" func get() []byte < raw := make([]byte,10000) fmt.Println(len(raw),cap(raw),&raw[0]) // выводит: 10000 10000 return raw[:3] > func main() < data := get() fmt.Println(len(data),cap(data),&data[0]) // выводит: 3 10000 >
Чтобы избежать этой ошибки, удостоверьтесь, что копируете нужные данные из временного слайса (вместо перенарезки).
package main import "fmt" func get() []byte < raw := make([]byte,10000) fmt.Println(len(raw),cap(raw),&raw[0]) // выводит: 10000 10000 res := make([]byte,3) copy(res,raw[:3]) return res > func main() < data := get() fmt.Println(len(data),cap(data),&data[0]) // выводит: 3 3 >
43. «Повреждение» данных в слайсах
Допустим, вам нужно переписать путь (хранящийся в слайсе). Чтобы ссылаться на каждую папку, вы его перенарезаете, изменяя имя первой папки, а затем комбинируете имена в новый путь.
package main import ( "fmt" "bytes" ) func main() < path := []byte("AAAA/BBBBBBBBB") sepIndex := bytes.IndexByte(path,'/') dir1 := path[:sepIndex] dir2 := path[sepIndex+1:] fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAA fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB dir1 = append(dir1,"suffix". ) path = bytes.Join([][]byte,[]byte) fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAAsuffix fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => uffixBBBB (not ok) fmt.Println("new path =>",string(path)) >
Так не сработает. Вместо AAAAsuffix/BBBBBBBBB вы получите AAAAsuffix/uffixBBBB. Причина в том, что слайсы обеих папок ссылаются на один и тот же массив данных из исходного слайса пути. То есть исходный путь тоже изменился. Это может быть проблемой для вашего приложения.
Ее можно решить, разместив в памяти новые слайсы и скопировав туда нужные данные. Другой выход: использовать полное выражение слайса (full slice expression).
package main import ( "fmt" "bytes" ) func main() < path := []byte("AAAA/BBBBBBBBB") sepIndex := bytes.IndexByte(path,'/') dir1 := path[:sepIndex:sepIndex] // полное выражение слайса dir2 := path[sepIndex+1:] fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAA fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB dir1 = append(dir1,"suffix". ) path = bytes.Join([][]byte,[]byte) fmt.Println("dir1 =>",string(dir1)) // выводит: dir1 => AAAAsuffix fmt.Println("dir2 =>",string(dir2)) // выводит: dir2 => BBBBBBBBB (ok now) fmt.Println("new path =>",string(path)) >
Дополнительный параметр в полном выражении управляет ёмкостью нового слайса. Добавление этого параметра к новому слайсу запустит размещение в памяти нового буфера вместо перезаписи данных во второй слайс.
44. «Устаревшие» слайсы
На одни и те же данные могут ссылаться несколько слайсов. Например, когда вы создаёте новый слайс на основе имеющегося. Если такое поведение важно для вашего приложения, позаботьтесь об «устаревших» слайсах.
В какой-то момент добавление данных в один из слайсов приведёт к размещению в памяти нового массива, потому что в старом не хватит места для новых данных. Теперь на старый массив (со старыми данными) ссылаются несколько слайсов.
import "fmt" func main() < s1 := []intfmt.Println(len(s1),cap(s1),s1) // выводит 3 3 [1 2 3] s2 := s1[1:] fmt.Println(len(s2),cap(s2),s2) // выводит 2 2 [2 3] for i := range s2 < s2[i] += 20 >// всё ещё ссылается на тот же массив fmt.Println(s1) // выводит [1 22 23] fmt.Println(s2) // выводит [22 23] s2 = append(s2,4) for i := range s2 < s2[i] += 10 >//s1 is now "stale" fmt.Println(s1) // выводит [1 22 23] fmt.Println(s2) // выводит [32 33 14] >
45. Методы и объявления типов
Когда вы определяете новый тип на основе существующего (не интерфейсного), тем самым вы создаёте объявление типа и не наследуете методы, объявленные в существующем типе.
package main import "sync" type myMutex sync.Mutex func main() < var mtx myMutex mtx.Lock() // ошибка mtx.Unlock() // ошибка >
/tmp/sandbox106401185/main.go:9: mtx.Lock undefined (type myMutex has no field or method Lock) /tmp/sandbox106401185/main.go:10: mtx.Unlock undefined (type myMutex has no field or method Unlock)
Если вам нужны методы из исходного типа, вы можете задать новый тип структуры, встроив исходный в качестве анонимного поля.
package main import "sync" type myLocker struct < sync.Mutex >func main() < var lock myLocker lock.Lock() // ok lock.Unlock() // ok >
Объявления интерфейсных типов также сохраняют свои наборы методов.
package main import "sync" type myLocker sync.Locker func main() < var lock myLocker = new(sync.Mutex) lock.Lock() // ok lock.Unlock() // ok >
46. Как выбраться из кодовых блоков for switch и for select
- уровень: более опытный
package main import "fmt" func main() < loop: for < switch < case true: fmt.Println("breaking out. ") break loop >> fmt.Println("out!") >
То же самое и с выражением goto …
47. Итерационные переменные и замыкания в выражениях for
Самая распространённая проблема в Go. Итерационные переменные в выражении for снова используются для каждой итерации. Это значит, что каждое замыкание (aka функциональный литерал), созданное в вашем цикле for , будет ссылаться на ту же переменную (и они получат значение переменной в тот момент, когда начнётся исполнение их горутин).
package main import ( "fmt" "time" ) func main() < data := []stringfor _,v := range data < go func() < fmt.Println(v) >() > time.Sleep(3 * time.Second) // горутины выводят: three, three, three >
Простейшее решение (не требующее менять горутины): сохранить текущее значение итерационной переменной в локальной переменной внутри блока цикла for .
package main import ( "fmt" "time" ) func main() < data := []stringfor _,v := range data < vcopy := v // go func() < fmt.Println(vcopy) >() > time.Sleep(3 * time.Second) // горутины выводят: one, two, three >
Другое решение: передать текущую итерационную переменную анонимной горутине в виде параметра.
package main import ( "fmt" "time" ) func main() < data := []stringfor _,v := range data < go func(in string) < fmt.Println(in) >(v) > time.Sleep(3 * time.Second) // горутины выводят: one, two, three >
Здесь чуть более сложная версия ловушки.
package main import ( "fmt" "time" ) type field struct < name string >func (p *field) print() < fmt.Println(p.name) >func main() < data := []field,,> for _,v := range data < go v.print() >time.Sleep(3 * time.Second) // горутины выводят: three, three, three >
package main import ( "fmt" "time" ) type field struct < name string >func (p *field) print() < fmt.Println(p.name) >func main() < data := []field,,> for _,v := range data < v := v go v.print() >time.Sleep(3 * time.Second) // горутины выводят: one, two, three >
Как вы думаете, что вы увидите (и почему), запустив этот код?
package main import ( "fmt" "time" ) type field struct < name string >func (p *field) print() < fmt.Println(p.name) >func main() < data := []*field,,> for _,v := range data < go v.print() >time.Sleep(3 * time.Second) >
48. Вычисление аргумента блока defer (Deferred Function Call Argument Evaluation)
Аргументы для вызовов отложенных функций вычисляются тогда же, когда и выражение defer (а не когда на самом деле выполняется функция).
package main import "fmt" func main() < var i int = 1 defer fmt.Println("result =>",func() int < return i * 2 >()) i++ //выводит: result => 2 (not ok if you expected 4) >
49. Вызов блока defer
Отложенные вызовы исполняются в конце содержащей их функции, а не в конце содержащего их кодового блока. Новички часто ошибаются, путая правила исполнения отложенного кода с правилами определения области видимости переменной. Это может стать проблемой, если вы долго выполняете функцию с циклом for , которая во время каждой итерации пытается отложить ( defer ) вызовы очистки ресурсов.
package main import ( "fmt" "os" "path/filepath" ) func main() < if len(os.Args) != 2 < os.Exit(-1) >start, err := os.Stat(os.Args[1]) if err != nil || !start.IsDir() < os.Exit(-1) >var targets []string filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error < if err != nil < return err >if !fi.Mode().IsRegular() < return nil >targets = append(targets,fpath) return nil >) for _,target := range targets < f, err := os.Open(target) if err != nil < fmt.Println("bad target:",target,"error:",err) //выводит ошибку: too many open files break >defer f.Close() // не будет закрыто в конце этого блока // сделай что-нибудь с файлом. > >
Один из способов решения проблемы — обернуть кодовый блок в функцию.
package main import ( "fmt" "os" "path/filepath" ) func main() < if len(os.Args) != 2 < os.Exit(-1) >start, err := os.Stat(os.Args[1]) if err != nil || !start.IsDir() < os.Exit(-1) >var targets []string filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error < if err != nil < return err >if !fi.Mode().IsRegular() < return nil >targets = append(targets,fpath) return nil >) for _,target := range targets < func() < f, err := os.Open(target) if err != nil < fmt.Println("bad target:",target,"error:",err) return >defer f.Close() // ok // сделай что-нибудь с файлом. >() > >
Другое решение: избавиться от выражения defer 🙂
50. Ошибки при приведении типов
Сбойные утверждения типов возвращают «нулевое значение» для целевых типов, использованных в операторе утверждения. Наложившись на сокрытие переменных, это может привести к непредсказуемому поведению.
package main import "fmt" func main() < var data interface<>= "great" if data, ok := data.(int); ok < fmt.Println("[is an int] value =>",data) > else < fmt.Println("[not an int] value =>",data) //выводит: [not an int] value => 0 (not "great") > >
package main import "fmt" func main() < var data interface<>= "great" if res, ok := data.(int); ok < fmt.Println("[is an int] value =>",res) > else < fmt.Println("[not an int] value =>",data) // выводит: [not an int] value => great (as expected) > >
51. Блокированные горутины и утечки ресурсов
В выступлении «Go Concurrency Patterns» на конференции Google I/O в 2012-м Роб Пайк рассказал о нескольких фундаментальных concurrency-шаблонах. Один из них — извлечение первого результата.
func First(query string, replicas . Search) Result < c := make(chan Result) searchReplica := func(i int) < c for i := range replicas < go searchReplica(i) >return
Для каждой копии (replica) поиска функция запускает отдельную горутину. Каждая из горутин отправляет свои поисковые результаты в канал результатов. Возвращается первое значение из канала.
А что с результатами от других горутин? И что насчёт них самих?
В функции First() канал результатов не буферизован. Это значит, что возвращается только первая горутина. Все остальные застревают в попытке отправить свои результаты. Получается, что если у вас более одной копии (replica), то при каждом вызове происходит утечка ресурсов.
Чтобы этого избежать, все горутины должны завершиться (exit). Одно из возможных решений: использовать достаточно большой буферизованный канал результатов, способный вместить все результаты.
func First(query string, replicas . Search) Result < c := make(chan Result,len(replicas)) searchReplica := func(i int) < c for i := range replicas < go searchReplica(i) >return
Другое решение: использовать выражение select со сценарием (case) default и буферизованный канал на одно значение. Сценарий default позволяет быть уверенным, что горутина не застряла, даже если канал результатов не может принимать сообщения.
func First(query string, replicas . Search) Result < c := make(chan Result,1) searchReplica := func(i int) < select < case c > for i := range replicas < go searchReplica(i) >return
Также можно использовать специальный канал отмены (special cancellation channel) для прерывания рабочих горутин.
func First(query string, replicas . Search) Result < c := make(chan Result) done := make(chan struct<>) defer close(done) searchReplica := func(i int) < select < case c > for i := range replicas < go searchReplica(i) >return
Почему в презентации есть такие баги? Роб Пайк просто не хотел усложнять слайды (slides) своей презентации. Такое объяснение имеет смысл, но это может быть проблемой для новичков, которые используют код, не думая о вероятных проблемах.
52. Применение методов, принимающих значение по ссылке (pointer receiver), к экземплярам значений
Пока значение адресуемо (addressable), к нему можно применять метод, принимающий значение по ссылке. Иными словами, в некоторых случаях вам не нужно иметь версию метода, принимающего параметр по значению.
Но не каждая переменная адресуема. Элементы хеш-таблицы (map) неадресуемы. Переменные, на которые ссылаются через интерфейсы, тоже неадресуемы.
package main import "fmt" type data struct < name string >func (p *data) print() < fmt.Println("name:",p.name) >type printer interface < print() >func main() < d1 := datad1.print() //ok var in printer = data // ошибка in.print() m := map[string]data > m["x"].print() //ошибка >
/tmp/sandbox017696142/main.go:21: cannot use data literal (type data) as type printer in assignment: data does not implement printer (print method has pointer receiver) /tmp/sandbox017696142/main.go:25: cannot call pointer method on m["x"] /tmp/sandbox017696142/main.go:25: cannot take the address of m["x"]
53. Обновление полей значений в хеш-таблице
Если у вас есть таблица, состоящая из структур, то вы не можете обновлять отдельные структурные поля.
package main type data struct < name string >func main() < m := map[string]data > m["x"].name = "two" // error >
/tmp/sandbox380452744/main.go:9: cannot assign to m["x"].name
Это не работает, потому что элементы таблицы не адресуемы.
Новичков может дополнительно путать то, что элементы слайсов — адресуемы.
package main import "fmt" type data struct < name string >func main() < s := []data > s[0].name = "two" // ok fmt.Println(s) // prints: [] >
Обратите внимание, что когда-то в одном из компиляторов (gccgo) можно было обновлять поля элементов таблицы. Но это быстро пофиксили 🙂 Также считалось, что такая возможность появится в Go 1.3. Но в то время это было не так важно, так что фича всё ещё висит в списке todo.
Первое обходное решение: использовать временную переменную.
package main import "fmt" type data struct < name string >func main() < m := map[string]data > r := m["x"] r.name = "two" m["x"] = r fmt.Printf("%v",m) //выводит: map[x:] >
Второе обходное решение: использовать хеш-таблицу с указателями.
package main import "fmt" type data struct < name string >func main() < m := map[string]*data > m["x"].name = "two" //ok fmt.Println(m["x"]) //выводит: & >
Кстати, что будет, если выполнить этот код?
package main type data struct < name string >func main() < m := map[string]*data > m["z"].name = "what?" //. >
54. nil-интерфейсы и nil-интерфейсные значения
Это вторая по распространённости ловушка Go. Интерфейсы — не указатели, даже если они так выглядят. Интерфейсные переменные будут nil только тогда, когда их типы и поля значений будут nil .
Интерфейсный тип и поля значений заполняются на основе типа и значения переменной, использованной для создания соответствующей интерфейсной переменной. Если вы попытаетесь проверить, эквивалентна ли переменная nil , то это может привести к непредсказуемому поведению.
package main import "fmt" func main() < var data *byte var in interface<>fmt.Println(data,data == nil) // выводит: true fmt.Println(in,in == nil) // выводит: true in = data fmt.Println(in,in == nil) // выводит: false //'data' является 'nil', но 'in' — не 'nil' >
Остерегайтесь этой ловушки, когда у вас есть функция, возвращающая интерфейсы.
package main import "fmt" func main() < doit := func(arg int) interface<> < var result *struct<>= nil if(arg > 0) < result = &struct<><> > return result > if res := doit(-1); res != nil < fmt.Println("good result:",res) // выводит: good result: // 'res' не является 'nil', но его значение — 'nil' > >
package main import "fmt" func main() < doit := func(arg int) interface<> < var result *struct<>= nil if(arg > 0) < result = &struct<><> > else < return nil // возвращает явный 'nil' >return result > if res := doit(-1); res != nil < fmt.Println("good result:",res) >else < fmt.Println("bad result (res is nil)") // здесь — как и ожидалось >>
55. Переменные стека и кучи
Не всегда известно, находится ли переменная в стеке или в куче. Если в С++ создать переменную с помощью оператора new , то она всегда будет в куче. В Go место размещения переменной выбирает компилятор, даже если используются функции new() или make() . Компилятор делает выбор на основании размера и результата «анализа локальности» (escape analysis). Это также означает, что можно возвращать ссылки на локальные переменные, что недопустимо в других языках, например в С и С++.
Если вы хотите знать, где находятся переменные, то передайте -gcflags -m в go build или go run (например, go run -gcflags -m app.go ).
56. GOMAXPROCS, согласованность (concurrency) и параллелизм
Go 1.4 и ниже используют только один тред контекста исполнения / ОС. Это значит, что в каждый момент времени может исполняться лишь одна горутина. Начиная с Go 1.5 количество контекстов исполнения стало равно количеству логических процессорных ядер, возвращаемому runtime.NumCPU() . Оно может не совпадать с общим количеством логических ядер в системе, в зависимости от настроек привязки CPU для процесса. Количество можно настроить, изменив переменную среды GOMAXPROCS или вызвав функцию runtime.GOMAXPROCS() .
Существует распространённое заблуждение, что GOMAXPROCS представляет собой количество процессоров, которые Go будет использовать для запуска горутин. Документация к функции runtime.GOMAXPROCS() только добавляет неразберихи. Но в описании к переменной GOMAXPROCS (https://golang.org/pkg/runtime/) говорится именно о тредах ОС.
Значение GOMAXPROCS может превышать количество ваших процессоров, верхний предел — 256.
package main import ( "fmt" "runtime" ) func main() < fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: X (1 on play.golang.org) fmt.Println(runtime.NumCPU()) // выводит: X (1 on play.golang.org) runtime.GOMAXPROCS(20) fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: 20 runtime.GOMAXPROCS(300) fmt.Println(runtime.GOMAXPROCS(-1)) // выводит: 256 >
57. Изменение порядка операций чтения и записи
Go может менять порядок некоторых операций, но общее поведение внутри горутины, где это происходит, не меняется. Однако сказанное не относится к порядку исполнения самих горутин.
package main import ( "runtime" "time" ) var _ = runtime.GOMAXPROCS(3) var a, b int func u1() < a = 1 b = 2 >func u2() < a = 3 b = 4 >func p() < println(a) println(b) >func main()
Если запустить этот код несколько раз, то можно увидеть такие комбинации переменных a и b :
1 2 3 4 0 2 0 0 1 4
Самая интересная комбинация — 02 — говорит о том, что b была обновлена раньше a .
Если нужно сохранить порядок операций чтения и записи среди нескольких горутин, то используйте каналы или соответствующие конструкции из пакета sync.
58. Диспетчеризация по приоритетам (Preemptive Scheduling)
Могут появляться разбойничьи (rogue) горутины, не дающие другим горутинам выполняться. Такое случается, если у вас есть цикл for , не позволяющий запустить диспетчер.
package main import "fmt" func main() < done := false go func()< done = true >() for !done < >fmt.Println("done!") >
Цикл for не должен быть пустым. Проблема не исчезнет, пока в цикле содержится код, не запускающий исполнение диспетчера.
Он запускается после сборки мусора, выражений go , операций блокирования каналов, блокирующих системных вызовов и операций блокирования. Также он может работать, когда вызвана невстроенная (non-inlined) функция.
package main import "fmt" func main() < done := false go func()< done = true >() for !done < fmt.Println("not done!") // не встроена >fmt.Println("done!") >
Чтобы узнать, встроена ли вызываемая вами в цикле функция, передайте -gcflags –m в go build или go run (например, go build -gcflags -m ).
Другое решение: явно вызвать диспетчер. Это можно сделать с помощью функции Gosched() из пакета runtime.
package main import ( "fmt" "runtime" ) func main() < done := false go func()< done = true >() for !done < runtime.Gosched() >fmt.Println("done!") >
Если вы дочитали до конца и у вас есть комментарии или идеи, добро пожаловать в дискуссию на Reddit (и в комментарии здесь, на Хабре. — Примеч. пер.).
- Блог компании VK
- Программирование
- Совершенный код
- Go