Видимость из другого потока
От: solvitz Украина  
Дата: 17.12.13 15:17
Оценка:
Имеется приложение на языке Си, которое собирается в MinGW. Приложение сетевое и многопоточное.
Для своей работы использует часть другого проекта, котороя вынесена в отдельный файл.
Структура приложения такая: main.c и main.h (основное приложение), net.c и net.h (сторонний компонент).
В main.c располагается создание потоков. Там их создается несколько десятков.
В net. c располагается работа с сетью. Расположено большое количество функций.
Но из main.c вызывается всего одна, котороя расположена в net.c. Она объявляется в main.h так:
extern DWORD send_data(char* ip, unsigned short port);

То есть, у меня в main.c из каждого потока вызывается ф-ия: send_data("127.0.0.1", 4000);
Каждый отдельный поток работает с отдельным IP. Строка для сборки проекта выглядит так:
gcc main.c net.c -lws2_32 -lkernel32 -lmsvcrt -nostdlib -Wl,--entry=_start -std=gnu99 -o main.exe

Суть проблемы заключается вот в чем. При работе одного потока все работает прекрасно, но когда
начинает работать несколько потоков происходит ошибка. Допустим работает 2 потока, один из них
так и не отсылает данные, а во втором WSAGetLastError() показывает, что не может сделать connect
из-за отсутсвующего сокета. Вот этот фрагмент в файле net.c:


#include "net.h"

int sock;

VOID func()
{
    return;
}

DWORD send_data(char* ip, unsigned short port)
{
    struct sockaddr_in target_ip;
    
    target_ip.sin_family = AF_INET;
    target_ip.sin_addr.s_addr = inet_addr(ip);
    target_ip.sin_port = htons(port);

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sock == INVALID_SOCKET)
    {
        return 0;
    }
    if (connect(sock, (struct sockaddr *)&target_ip, sizeof(target_ip)) != 0)
    {
        closesocket(sock);
                return 0;
    }
    func();
    
    return 1;
        
}


Скажите пожалуйста как такое происходит, ведь ф-ия send_data вызывается из другого потока?
Я так понимаю, что где-то внутри net.c присходит что-то с дескриптором сокета, но как это может повлиять
на сокет созданный в другом потоке?
Re: Видимость из другого потока
От: watchmaker  
Дата: 17.12.13 15:38
Оценка: 10 (2)
Здравствуйте, solvitz, Вы писали:

S>int sock;
  ...
S>    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

У тебя переменная, где хранится дескриптор сокета, глобальная и одна на всю программу.
Когда приходит очередной поток, то он перезаписывает её и таким образом все предыдущие потоки начинают работать уже с новым сокетом, а не со своим.
Правильное и хорошее решение — сделать эту переменную локальной. Но можно и по простому в tls её положить в качестве быстрого временного патча.
Re[2]: Видимость из другого потока
От: solvitz Украина  
Дата: 17.12.13 15:41
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Здравствуйте, solvitz, Вы писали:


W>
S>>int sock;
W>  ...
S>>    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
W>

W>У тебя переменная, где хранится дескриптор сокета, глобальная и одна на всю программу.
W>Когда приходит очередной поток, то он перезаписывает её и таким образом все предыдущие потоки начинают работать уже с новым сокетом, а не со своим.
W>Правильное и хорошее решение — сделать эту переменную локальной. Но можно и по простому в tls её положить в качестве быстрого временного патча.

Я примерно так и думал, дело в том, что там этих переменных много и много мест откуда они вызываются.
Нет ли какого-либо другого способа локализовать эти переменные в пределах потока? Этот способ не сильно простой.
Re[3]: Видимость из другого потока
От: watchmaker  
Дата: 17.12.13 15:53
Оценка: -1
Здравствуйте, solvitz, Вы писали:

S>Я примерно так и думал, дело в том, что там этих переменных много и много мест откуда они вызываются.

S>Нет ли какого-либо другого способа локализовать эти переменные в пределах потока?
Например, использовать многозадачность на основе процессов, а не потоков.
Re[3]: Видимость из другого потока
От: B0FEE664  
Дата: 17.12.13 15:54
Оценка:
Здравствуйте, solvitz, Вы писали:

S>Я примерно так и думал, дело в том, что там этих переменных много и много мест откуда они вызываются.

Глобальные и статические переменные для многопоточного приложения губительны.

S>Нет ли какого-либо другого способа локализовать эти переменные в пределах потока? Этот способ не сильно простой.

Ну а как вы себе это представляете? Допустим у вас два потока использующие различные данные, следовательно эти данные должны лежать в различных ячейках памяти.
Есть старый чудовищный способ решения — запустить несколько однопоточных приложений с различными параметрами, но вам, видимо, такое решение не подойдёт.
И каждый день — без права на ошибку...
Re[3]: Видимость из другого потока
От: visual_wind  
Дата: 17.12.13 16:53
Оценка:
Здравствуйте, solvitz, Вы писали:

S>Я примерно так и думал, дело в том, что там этих переменных много и много мест откуда они вызываются.

S>Нет ли какого-либо другого способа локализовать эти переменные в пределах потока? Этот способ не сильно простой.

Есть еще один, наверное, больше теоретический способ. Вы можете скомпилировать net.c и net.h в отдельную dll (назовем ее net.dll), откуда экспортировать интересующие вас функции. Эту dll следует положить в известное место (например, в директорию с исполнямым файлом) и далее действовать по такому сценарию:

1. При создании нового потока файл dll физически копируется, но уже с новым именем (пусть net2.dll).
2. Новый поток делает LoadLibrary уже для net2.dll. По идее, это будет уже другая dll со своими данными.
3. Новый поток юзает функции из этой net2.dll и по окончании своей работы не только выгружает ее, но и удаляет ее файл с диска.
4. Для следующего потока повторяются те же действия, только теперь создается на диске net3.dll и делается LoadLibrary уже для нее.

Это простейший сценарий, который можно оптимизировать, чтобы минимизировать лишние действия с созданием-удалением файлов dll на диске. Скорость работы я также затрудняюсь прогнозировать. И конечно, все очень сильно зависит от максимального количества потоков и частоты их создания.

В любом случае, мне самому этот вариант не нравится. Лично я серьезно бы подумал, стоит ли так делать
Re[4]: Видимость из другого потока
От: niXman Ниоткуда https://github.com/niXman
Дата: 17.12.13 17:32
Оценка:
почему бы просто не заюзать asio?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[4]: Видимость из другого потока
От: niXman Ниоткуда https://github.com/niXman
Дата: 17.12.13 17:32
Оценка:
и, я так и не понял, зачем тут вообще несколько потоков?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[2]: Видимость из другого потока
От: solvitz Украина  
Дата: 17.12.13 19:45
Оценка:
W>
S>>int sock;
W>  ...
S>>    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
W>

W>У тебя переменная, где хранится дескриптор сокета, глобальная и одна на всю программу.
W>Когда приходит очередной поток, то он перезаписывает её и таким образом все предыдущие потоки начинают работать уже с новым сокетом, а не со своим.
W>Правильное и хорошее решение — сделать эту переменную локальной. Но можно и по простому в tls её положить в качестве быстрого временного патча.

Спасибо большое! TLS это как раз то, что нужно было. Все переводить в локальные переменные было бы очень долго.
Re[5]: Видимость из другого потока
От: visual_wind  
Дата: 18.12.13 07:21
Оценка:
Здравствуйте, niXman, Вы писали:

X>почему бы просто не заюзать asio?


Сдается мне, asio здесь не поможет. К тому же, как я понимаю, проект на чистом Си.

Проблема топикстартера в том, что у него есть непотокобезопасная библиотека, которую он хочет использовать из разных потоков. Библиотека доступна в исходных кодах, но он модифицировать ее опасался. Я предложил подход, основанный на том, что длл с разными именами, но с одинаковым содержимым — это разные длл со своим набором данных. Если скомпилировать библиотеку топикстартера в длл, и перед использованием копировать ее файл на диске, давая ему новое имя, то каждый поток может загружать длл из своего личного предварительно скопированного файла. То есть, каждый поток будет загружать "другую" длл, которая будет работать идентично исходной. Главное — не забывать удалить скопированный файл после выгрузки длл.

Очевидно, что это решение кривое, и если топикстартер таки согласился модифицировать код библиотеки, поместив проблемные переменные в tls, это только к лучшему.
Re[6]: Видимость из другого потока
От: niXman Ниоткуда https://github.com/niXman
Дата: 18.12.13 13:05
Оценка:
сдается мне, что тут вообще потоков не нужно, достаточно использовать неблокирующий IO
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.