На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> TLS ("thread-local storage", не "transport layer security") , libssh2
    Всем привет!

    В общем тема дня - сабж. Не знаю как вам, а для меня - мутная тема. Но жысть диктует свои правила ...

    А ноги растут из все той же си-шной либы libssh2. На сколько мне хватило моего скромного IQ - она знать не знает о многопоточности. Залез в исходники. По-грипал по слову "mutex" - по нулям. А вот по слову "thread" нашел два более-менее совпадения в коде:

    src/agent.c

    ExpandedWrap disabled
          snprintf(mapname, sizeof(mapname),
                   "PageantRequest%08x%c", (unsigned)GetCurrentThreadId(), '\0'); // ◄──────────────────────── GetCurrentThreadId()
          filemap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
                                       0, PAGEANT_MAX_MSGLEN, mapname);

    Ну а потом еще в комментах исходников обнаружил следующее:

    include/libssh2.h

    ExpandedWrap disabled
      /*
       * libssh2_init()
       *
       * Initialize the libssh2 functions.  This typically initialize the
       * crypto library.  It uses a global state, and is not thread safe ◄──────────────────────── "not thread safe "
       * you must make sure this function is not called concurrently.
       *
       * Flags can be:
       * 0:                              Normal initialize
       * LIBSSH2_INIT_NO_CRYPTO:         Do not initialize the crypto library (ie.
       *                                 OPENSSL_add_cipher_algoritms() for OpenSSL
       *
       * Returns 0 if succeeded, or a negative value for error.
       */


    И тут я вспомнил, что недавно, перед сном я читал какую-то бадягу, уже засыпая ... и там был термин, как его ... во, он - thread_local, из С++. Читаем:

    Цитата
    The thread_local keyword is only allowed for objects declared at namespace scope, objects declared at block scope, and static data members. It indicates that the object has thread storage duration. It can be combined with static or extern to specify internal or external linkage (except for static data members which always have external linkage), respectively, but that additional static doesn't affect the storage duration.

    Короч, оч много слов интуристов! Но, как я понял, описатель thread_local сразу же подразумевает static (не? пруф?)

    Едем дальше ... у нас же на анатомическом столе си-либа (нам плюсами и не пахнет). Включаем глубинное гугление... Находим занятное чтиво:

    Цитата
    Aside from that, various compiler implementations provide specific ways to declare thread-local variables:

    Solaris Studio C/C++, IBM XL C/C++,[3] GNU C,[4] Clang[5] and Intel C++ Compiler (Linux systems)[6] use the syntax:
    __thread int number;
    Visual C++,[7] Intel C/C++ (Windows systems),[8] C++Builder, and Digital Mars C++ use the syntax:
    __declspec(thread) int number;
    C++Builder also supports the syntax:
    int __thread number;
    On Windows versions before Vista and Server 2008, __declspec(thread) works in DLLs only when those DLLs are bound to the executable, and will not work for those loaded with LoadLibrary() (a protection fault or data corruption may occur).

    Ну и добрались до самих органов

    В либе libssh2:

    • И намека нет на многопоточность
    • То тут, то сям используются переменные и функции с описателем static

    Собственно, вопрос: а если заменить все описатели static на аналоги c thread_local, то можно ли данную либу использовать в многопоточной среде (в более чем одном потоке)?

    Или что-то есть еще важное, что я тупо упускаю?
      Цитата JoeUser @
      Короч, оч много слов интуристов! Но, как я понял, описатель thread_local сразу же подразумевает static (не? пруф?)
      Не. Пруф прям в цитате: они могут иметь linkage как external, так и internal. Это просто объекты, локальные в потоке. Т.е. каждая нитка, обращаясь к ним по имени, будет ссылаться на свои собственные объекты.
      Цитата JoeUser @
      Собственно, вопрос: а если заменить все описатели static на аналоги c thread_local, то можно ли данную либу использовать в многопоточной среде (в более чем одном потоке)?
      Собсвтенно поэтому и нет. thread_local нужно делать любые глобальные объекты, которые хранят состояния, которые могут быть разрушены гонкой потоков.
        Цитата Qraizer @
        Не

        Вот народ приводит цитаты из стандарта:

        Цитата
        When thread_local is applied to a variable of block scope the storage-class-specifier static is implied if it does not appear explicitly

        Цитата
        All variables declared with the thread_local keyword have thread storage duration. The storage for these entities shall last for the duration of the thread in which they are created. There is a distinct object or reference per thread, and use of the declared name refers to the entity associated with the current thread
          JoeUser, как это что-либо меняет? Накидал вот наконец-то пример:
          ExpandedWrap disabled
            // ------------ thrd.h
            extern thread_local int trdLocalVar;
             
            void g();
             
            // ------------ src1.cpp
            #include <iostream>
            #include "thrd.h"
             
            void g()
            {
              std::cout << "The trdLocalVar is " << trdLocalVar << " now" << std::endl;
            }
             
            // ------------ src2.cpp
            #include <thread>
            #include <mutex>
            #include <iostream>
            #include "thrd.h"
             
            thread_local int trdLocalVar = -1;
             
            std::mutex mtx;
             
            void thr(int n)
            {
              std::lock_guard ct(mtx);
             
              trdLocalVar = n;
              g();
            }
             
            int main()
            {
              std::thread trd1(thr, 1);
              std::thread trd2(thr, 2);
              std::thread trd3(thr, 3);
             
              trd1.join();
              trd2.join();
              trd3.join();
            }
          Всё компилится и линкуется. Результат ожидаемый:
          ExpandedWrap disabled
            The trdLocalVar is 1 now
            The trdLocalVar is 2 now
            The trdLocalVar is 3 now
            Цитата Qraizer @
            JoeUser, как это что-либо меняет?

            Ну просто мне запомнилось, что threal_local "подразумевает статический спецификатор класса хранения". Но я забыл где об этом читал. Решил пререспросить "а если заменить все описатели static на аналоги c thread_local ...". Ты ответил нет. Но все же получается "дa" ... Для однопоточной среды static будет хранить одну копию данных. В многопоточной среде, если использовать thread_local - для каждого потока будет своя копия, аналогичная static - для однопоточной среды.
              И ещё раз нет. static делает имя локальным в единице трансляции, и это не то же самое, что локальное имя в потоке. static обеспечивает локальность имени на основе статического деления программы на единицы трансляции, thread_local – на основе динамического разделения программы на потоки. Поток может вызвать функции из других единиц трансляции, и есть очень большая разница между глобальными объектами с внутренним связыванием и локальными в потоке объектами с внешним связыванием.

              Добавлено
              Вот более детальный пример:
              ExpandedWrap disabled
                // ------------ thrd.h
                #ifdef THREAD_LOCAL
                extern thread_local int trdLocalVar;
                #else
                static int trdLocalVar = -1;
                #endif
                 
                void g();
              ExpandedWrap disabled
                // ------------ src1.cpp
                #include <iostream>
                #include <iomanip>
                #include <mutex>
                #include <thread>
                #include "thrd.h"
                 
                std::mutex out;
                 
                void g()
                {
                  std::lock_guard ct(out);
                 
                  std::cout << std::hex << std::setw(8) << std::this_thread::get_id() << ": the trdLocalVar is "
                            << std::dec << trdLocalVar << " now" << std::endl;
                }
              ExpandedWrap disabled
                // ------------ src2.cpp
                #include <thread>
                #include <mutex>
                #include <array>
                #include <iostream>
                #include "thrd.h"
                 
                #ifdef THREAD_LOCAL
                thread_local int trdLocalVar = -1;
                #endif
                 
                std::mutex mtx;
                std::array<std::mutex, 3> events;
                 
                void thr(int n)
                {
                  trdLocalVar = n;
                  g();
                 
                  std::lock_guard ev(events[n-1]);
                  std::lock_guard ct(mtx);
                 
                  g();
                }
                 
                int main()
                {
                  using namespace std::literals;
                 
                  for (auto &i : events) i.lock();
                 
                  std::thread trd1(thr, 1);
                  std::thread trd2(thr, 2);
                  std::thread trd3(thr, 3);
                 
                  g();
                  std::this_thread::sleep_for(2s);
                 
                  events[1].unlock();
                  trd2.join();
                  g();
                 
                  events[0].unlock();
                  trd1.join();
                  g();
                 
                  events[2].unlock();
                  trd3.join();
                  g();
                }
              Результат при наличии THREAD_LOCAL:
              ExpandedWrap disabled
                     bc4: the trdLocalVar is 1 now
                     f1c: the trdLocalVar is 2 now
                    198c: the trdLocalVar is 3 now
                     654: the trdLocalVar is -1 now
                     f1c: the trdLocalVar is 2 now
                     654: the trdLocalVar is -1 now
                     bc4: the trdLocalVar is 1 now
                     654: the trdLocalVar is -1 now
                    198c: the trdLocalVar is 3 now
                     654: the trdLocalVar is -1 now
              Результат без него легко предсказать: все строчки будут "... is -1 now".

              Добавлено
              P.S. Вариант с thread_local без extern ничего не поменяет. Всё равно в src1 и src2 это будут разные объекты.
                Цитата Qraizer @
                P.S. Вариант с thread_local без extern ничего не поменяет. Всё равно в src1 и src2 это будут разные объекты.

                Тогда не понимаю, о чем ты споришь :)

                Смотри

                static. Для src1 и src2 - это разные объекты, принадлежащие своим единицам транстяции.
                thread_local. Для src1 и src2 - это также разные объекты, принадлежащие своим единицам транстяции.

                Разница лишь во времени существования. static - существуют все время жизни программы. А thread_local - время жизни потока.

                Пусть есть однопоточная либа SRC и ее однопоточное использование:

                ExpandedWrap disabled
                  // src.h ──────────────────────────────────────────────────────────────────────────
                   
                  #ifndef SRC_H
                  #define SRC_H
                  #include <iostream>
                   
                  void set(int i);
                  void prn();
                   
                   
                  #endif // SRC_H
                   
                  // src.cpp ────────────────────────────────────────────────────────────────────────
                   
                  #include "src.h"
                   
                  static int State;
                   
                  void set(int i) {
                    State = i;
                  }
                   
                  void prn() {
                    std::cout << State << std::endl;
                  }
                   
                  // main.cpp ───────────────────────────────────────────────────────────────────────
                   
                  #include <src.h>
                   
                  void func(int i) {
                    set(i);
                    prn();
                  }
                   
                  int main() {
                    func(12);
                    return 0;
                  }

                Ничего необычного. Запустили функцию, которая вызвала изменение "состояния библиотеки" и потом печать. Увидели 12.

                А теперь желаем эту либу использовать в многопоточной среде. В самой "либе" меняем static на thread_local. Ну а основную прогу переписываем под многопоточное исполнение:

                ExpandedWrap disabled
                  // src.h ──────────────────────────────────────────────────────────────────────────
                   
                  #ifndef SRC_H
                  #define SRC_H
                  #include <iostream>
                   
                  void set(int i);
                  void prn();
                   
                   
                  #endif // SRC_H
                   
                  // src.cpp ────────────────────────────────────────────────────────────────────────
                   
                  #include "src.h"
                   
                  thread_local int Value;
                   
                  void set(int i) {
                    State = i;
                  }
                   
                  void prn() {
                    std::cout << State << std::endl;
                  }
                   
                  // main.cpp ───────────────────────────────────────────────────────────────────────
                   
                  #include <src.h>
                  #include <thread>
                  #include <chrono>
                  #include <ratio>
                  #include <mutex>
                   
                  std::mutex Mutex;
                   
                  void func(int i) {
                    set(i);
                    std::this_thread::sleep_for(
                      std::chrono::milliseconds(500 + ((std::rand() % 20) * 300))
                    );
                    std::lock_guard Guard(Mutex);
                    std::cout << "(" << std::this_thread::get_id() << "): ";
                    prn();
                  }
                   
                  int main() {
                    std::srand(343452345);
                    std::thread trd1(func, 10);
                    std::thread trd2(func, 20);
                    std::thread trd3(func, 30);
                    trd1.join();
                    trd2.join();
                    trd3.join();
                    return 0;
                  }

                Как и ожидалось, выводятся разные цифры, те, которыми инициализировали функции потоков (типа у каждого потока свое состояние либы):

                ExpandedWrap disabled
                  (4): 30
                  (2): 10
                  (3): 20

                Ну, получается получается простая замена static на thred_local позволяет нам получить желаемое многопоточное использование?
                  Цитата JoeUser @
                  Ну, получается получается простая замена static на thred_local позволяет нам получить желаемое многопоточное использование?
                  Если считать
                  ExpandedWrap disabled
                        6804: the trdLocalVar is -1 now
                        6e1c: the trdLocalVar is -1 now
                        4060: the trdLocalVar is -1 now
                        3fe4: the trdLocalVar is -1 now
                        6e1c: the trdLocalVar is -1 now
                        3fe4: the trdLocalVar is -1 now
                        6804: the trdLocalVar is -1 now
                        3fe4: the trdLocalVar is -1 now
                        4060: the trdLocalVar is -1 now
                        3fe4: the trdLocalVar is -1 now
                  вместо
                  ExpandedWrap disabled
                        45cc: the trdLocalVar is 1 now
                        1110: the trdLocalVar is 2 now
                        5534: the trdLocalVar is 3 now
                        4b44: the trdLocalVar is -1 now
                        1110: the trdLocalVar is 2 now
                        4b44: the trdLocalVar is -1 now
                        45cc: the trdLocalVar is 1 now
                        4b44: the trdLocalVar is -1 now
                        5534: the trdLocalVar is 3 now
                        4b44: the trdLocalVar is -1 now
                  желаемым многопоточным результатом, то да. Ты не забыл, что в q() из src.cpp видимая ею trdLocalVar разная и зависит от того, какая из трёх запущенных параллельно thr() из main.cpp её вызвала? thread_local поменяла семантику этой ранее static переменной.
                  Сообщение отредактировано: Qraizer -
                    Цитата Qraizer @
                    поменяла семантику этой ранее static переменной.

                    В многпоточном исполнении - там не одна же переменная! Там одна копия переменной на один поток. Каждый поток юзает свою копию? И если "да", то что не так?
                      Цитата Qraizer @
                      static обеспечивает локальность имени на основе статического деления программы на единицы трансляции, thread_local – на основе динамического разделения программы на потоки.
                        Скорее всего все thread_local размещаются в отдельную виртуальную секцию. Виртуальную в том смысле, что линковщик собирает её воедино подобно секциям кода или данных, определяет смещения переменных, но не выделяет под секцию память (хотя наверно всё же выделяет, ведь есть ещё и основной поток со своими экземплярами этих же переменных).
                        Для новых потоков память распределяется динамически и инициализируется при их создании. При обращении к таким переменным обращение
                        thread_local_var
                        заменяется на что-то вроде
                        get_thread_locals()->thread_local_var_as_struct_field
                        По завершении потока память освобождается.
                          amk, рассуждения вполне логичные! Вот если бы как-то, где-то найти реальный пруф ... Эх :-?
                          0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                          0 пользователей:


                          Рейтинг@Mail.ru
                          [ Script execution time: 0,0514 ]   [ 16 queries used ]   [ Generated: 18.04.24, 03:20 GMT ]