Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   С/С++, C#, Delphi, .NET, Asm (https://forum.antichat.xyz/forumdisplay.php?f=24)
-   -   Проблема с многопоточностью, C++ (https://forum.antichat.xyz/showthread.php?t=143607)

mailbrush 25.09.2009 17:31

Проблема с многопоточностью, C++
 
В общем интересуюсь этой проблемой уже давно. Пишу парсер аккаунтов одного форума. Пока тестовый, в 5 потоках надо отпарсить только 5 юзеов. Есть код
Код:

int uid = 1; //текущий айди юзера

void tfunc(void *argv) 
{
//здесь я создаю WinSock
sprintf( request, "GET /forum/profile.php?mode=viewprofile&u=%s HTTP/1.1\r\nHost: tfile.ru\r\nConnection: Close\r\n\r\n", IntToStr(uid).c_str()); //пишу в запрос id юзера
//здесь завершаю работу WinSock
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
for(i=0;i<=4;i++)
{
uid++;
_beginthread(tfunc, 0, NULL); //старт потока
}
}

Проблема в том, что запрос отправляется всегда с айди = 5, т.е. с последним значением uid. Почему?

Hiro Protagonist 25.09.2009 17:40

2mailbrush

а с чего ты взял, что потоки создадутся, запустятся и дойдут до sprintf сразу после _beginthread, там же ещё хренова туча crt мутотени выполнять? Есественно, что цикл выполняется быстрее, чем uid используется в потоке. В данном случае нужно синхронизировать потоки или передавать индивидуальные параметры по типу:

int *q = malloc(5*4);

for(i=0;i<=4;i++)
{
uid++;
q[i] = uid;
_beginthread(tfunc, 0, &q[i]);
}

free(q);

PS НЕ ИСПОЛЬЗУЙ _beginthread БОЛЬШЕ НИКОГДА!!! ОНА УСТАРЕЛА!!! ИСПОЛЬЗУЙ _beginthreadex.

Chrome~ 25.09.2009 18:04

У меня раньше тоже была подобная проблема. Она решается путем блокировки переменной. В общем, я читал на блоге Kaimi статью от DX по работе с антикапчей. Если я не ошибаюсь, в статье такая проблема решается с помощью мьютексов.

http://kaimi.ru/2009/05/anticaptcha-assembler/

(ASM)

slesh 25.09.2009 19:01

а вообще для таких целей есть интерлоковые функции которые юзаются внутри потока. типа такой:
Код:


int uid = 1; //текущий айди юзера

void tfunc(void *argv) 
{
int myID =  InterlockedIncrement(&uid) - 1;
//здесь я создаю WinSock
sprintf( request, "GET /forum/profile.php?mode=viewprofile&u=%i HTTP/1.1\r\nHost: tfile.ru\r\nConnection: Close\r\n\r\n", my_ID); //пишу в запрос id юзера
//здесь завершаю работу WinSock
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
for(i=0;i<=4;i++)
{
_beginthread(tfunc, 0, NULL); //старт потока
}
}

т.е. значение переменной увеличивается на 1 через атомарный доступ к памяти. т.е. происходит блокировка шины памяти, то даже на многопроцессорных системах всё будет пахать офегительно.

К томуже IntToStr(uid).c_str() смысл юзать? или можно с шаблоне задать %i или %u

Hiro Protagonist 25.09.2009 20:20

2slesh

Интерлокед конечно быстрые, да и в большинстве случаев синхронизация концептуальнее более правильна, но имхо в брутерах-парсерах многопоточных лучше инициализировать все заранее + использовать пулы потоков (+ асинхронный I/O). Тогда к моменту исполнения непосредственно функционала не надо будет тратится на создания потоков (тысячи тактов) и/или синхронизацию (от десятков до сотен тактов). Выйгрышь в производительности существеннен, оссобенно при количестве потоков более 2*кол-во_процессоров. Вышесказанное естественно ИМХО.

slesh 25.09.2009 20:40

всё это правильно, но ты забыл про одно - скорость сети - ничтожна по сравнению с пропускной способностью шины памяти и скорости работы проца.
Так что как никрути но всё равно упрешся в скорость сети. И эти все затраты будут незаметны.

темболее что если ты не заметил то интерлок функции состоят примерно из 3-4 машинных команд. и такая синхронизация никаких затрат не вызывает.
банальный тест:
на проце E5200 (2 ядра 2,5 ГГц)
10 миллионов вызовов InterlockedIncrement
занимают ~156 микросекунд. так что за 1 микросекунду эту будет примерно 64 тысячи раз.
При этом если учеть что идеальаня скорость инета - 100 мегобит / с то выходит что за 1 микросекунду максимум ты пошлеш только 12500 байт. Это только посылка.
Сам понимаеш такой инет мало у кого есть. а вот такие процы есть у всех.
так что сейчас как никрути но всё только в сеть упирается. единственное что для брута нужно - выделенная память для буферов - вот это реально критично. потому что выделение памяти - процес довольно медленный. Потому что 10 тысяч выделений памяти по 64 кила. занимает времяни больше 1 секунды.

Да и брутеры не нужнаются в такой скорости по причине того что тебя быстрее забанять на серваке )

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

Hiro Protagonist 25.09.2009 20:49

2slesh

все мы стремимся к идеалу )) поэтому и пишем shl eax, 1 вместо mul, хотя вызовы API сотрут различия )) тем более не стоит забывать, что при увеличении числа потоков прога станет быстрее (в смысле в работе на хосте) за счет того, что все тяжелые операции мы сделаем заранее. ИМХО ^____^

mailbrush 25.09.2009 20:52

Код:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <process.h>
#include <stdio.h>
#include <winsock2.h>
#include <iostream>
#include <string>
#include <vector>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "sSkinManager"
#pragma link "sSkinProvider"
#pragma resource "*.dfm"

TForm1 *Form1;
volatile long uid = 1;
char mystring [1024];
CRITICAL_SECTION cs;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
int i;



void tfunc(void *argv)
{
int myID =  InterlockedIncrement(&uid) - 1;
char request[1024*5], response[1024*5];
int first, second;
char* headers;

sockaddr_in s_a;
WSADATA wd;
WSAStartup( MAKEWORD( 2, 2 ), &wd );

while(myID<15)
{
SOCKET s = socket( AF_INET, SOCK_STREAM, 0 );
if (s == INVALID_SOCKET)
ShowMessage("INVALID");
s_a.sin_addr.s_addr = inet_addr( "89.188.109.206" );
s_a.sin_port = htons( 80 );
s_a.sin_family = AF_INET;
if (connect( s, (struct sockaddr*)&s_a, sizeof( s_a ) ) == SOCKET_ERROR)
ShowMessage("Bad Connect!");
sprintf( request, "GET /forum/profile.php?mode=viewprofile&u=%i HTTP/1.1\r\nHost: tfile.ru\r\nConnection: Close\r\n\r\n", myID);
send( s, request, strlen( request ), 0 );
//EnterCriticalSection( &cs );
for(i=0;i<=4;i++)
recv( s, response, 1024 , 0 );
//LeaveCriticalSection( &cs );
closesocket( s );
}
WSACleanup(); //
}

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
InitializeCriticalSection( &cs );
for(i=1;i<=5;i++)
{
_beginthread(tfunc, 0, NULL);
}
}
//---------------------------------------------------------------------------







void __fastcall TForm1::Button2Click(TObject *Sender)
{
ShowMessage("hello");
}
//---------------------------------------------------------------------------

Вобщем вот весь код... Теперь я хочу, чтобы каждый поток своего юзера парсил, но что я только не плюсовал, чтобы айди увеличивался - ничего не помогает. Все потоки парсят 1-5 юзера. Да и с цикла while() не выходит...

slesh 25.09.2009 20:57

WSAStartup - делается только в начале проги и тока 1 раз
WSACleanup - только в конце проги. Так что таким видом ты можеш заглючить прогу

mailbrush 25.09.2009 21:01

Цитата:

Сообщение от slesh
WSAStartup - делается только в начале проги и тока 1 раз
WSACleanup - только в конце проги. Так что таким видом ты можеш заглючить прогу

Ок, но как решить проблему, которую я описал выше?

Hiro Protagonist 25.09.2009 21:04

2mailbrush

у тебя с логикой программы не в порядке. Ты смотри где интерлокед вызываешь. Глобальная переменная увеличится только пять раз, по числу создаваемых потоков, а в цикле её значение аж с 15 сравнивается.

да и это

PHP код:

for(i=0;i<=4;i++)
recvsresponse1024 ); 

тоже некорректно. память нужно выделять динамически и проверять возвращаемое recv значение.

mailbrush 25.09.2009 21:12

Цитата:

Сообщение от Hiro Protagonist
2mailbrush

у тебя с логикой программы не в порядке. Ты смотри где интерлокед вызываешь. Глобальная переменная увеличится только пять раз, по числу создаваемых потоков, а в цикле её значение аж с 15 сравнивается.

да и это

PHP код:

for(i=0;i<=4;i++)
recvsresponse1024 ); 

тоже некорректно. память нужно выделять динамически и проверять возвращаемое recv значение.

Сейчас пороверю. Кстати, если не в цикле делать, recv не берет 5кб, а только 1кб, хотя я делаю 1024*5

Hiro Protagonist 25.09.2009 21:16

2mailbrush

дык в цикле и делай, только память динамически выделяй.

чтото типа

int i = 0;
char *buf = calloc(1024);

while(recv(s, &buf[i], 1024, 0))
{
...
i += 1024;
buf = realloc(buf, 1024 + i);

}

free(buf);

PS и не забывай ещё обрабатывать ошибки recv

mailbrush 25.09.2009 21:17

Поставил внутрь цикла while инкремент - первых пять потоков обрабатывают 5й айди, вторые 10, третье 15 и т.д... Эх...

Hiro Protagonist, меня пока интересует инкремент. В любом случае спасибо всем за помощь!

Hiro Protagonist 25.09.2009 21:19

2mailbrush

я вообще не понимаю зачем тебе этот цикл. Делай так. чтобы один id обрабатывал один поток и умирал.

mailbrush 25.09.2009 21:20

Цитата:

Сообщение от Hiro Protagonist
2mailbrush

я вообще не понимаю зачем тебе этот цикл. Делай так. чтобы один id обрабатывал один поток и умирал.

Воо... Вот так я и хочу, но как? Чтобы была многпоточность...

Ra$cal 25.09.2009 22:04

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

mailbrush 25.09.2009 22:08

Цитата:

Сообщение от Ra$cal
вот вы фанаты-изобретатели-мазохисты. ну сделайте вы у функции обработки потока параметр. и скидывайте значение счетчика i. ибо без понимания, почему не канает вариант с глобальной переменной, изобретать вокруг него огород - тупо.
или юзайте мьютексы для ожидания момента окончательного считывания значения глобальной переменной в обработчике потока.

Везде ты должен влезть... А если я не знаю как это? И в поиск меня посылать не надо, а если все таки хочешь послать - сначала сам найди в гугле ответ на это... Или просто помоги...

Ra$cal 25.09.2009 22:16

гугл "beginthread msdn" первая ссылка. скролишь пример и читаешь. правда сложно?
если чтото не понятно - спрашивай. тока леницо не надо.
ps: проще для понимания второй пример. если бы использовал _beginthreadex вопроса о параметрах не возникло бы.

Ra$cal 25.09.2009 22:31

Цитата:

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

Hiro Protagonist 25.09.2009 22:44

2Ra$cal

мьютексы намного тормознее интерлокеда и в этом примере не надо их применять.

2mailbrush

1. Ааа, я понял. Тебе нужно чтобы несколько потоков обрабатывали некий диапазон. Дык разбивай диапазон этот на части и передавай поддиапазон (начало/размер) в поток. Там локальной переменной присваивай начальный uid диапазона и в цикле проходи все. Тогда тебе ненужна синхронизация и глобальная переменная. А то в начале у тебя код другое подразумевал ).

2. пример с передачей параметров в поток я приводил на первой странице.

Chrome~ 25.09.2009 22:57

Цитата:

Сообщение от Hiro Protagonist
пример с передачей параметров в поток я приводил на первой странице.

Ага. Можно сделать еще так: объявить какую-нибудь структуру с двумя значениями: startid и increment и в последующем передавать ее в поток. Соответственно в первое значение записываем начальное значение id, во вторую, - шаг изменения значения. То есть, если создать 5 потоков, каждому присвоить начальные значения id, как 1, 2, 3, 4, 5, значение инкремента установить в 5, то получится, что первый поток обработает следующие id пользователей: 1, 6, 11... второй: 2, 7, 12... пятый: 5, 10, 15 и т. д. Я использовал этот способ в некоторых своих программах, все работало отлично и исправно.

mailbrush 25.09.2009 23:56

Цитата:

Сообщение от Hiro Protagonist
2Ra$cal

мьютексы намного тормознее интерлокеда и в этом примере не надо их применять.

2mailbrush

1. Ааа, я понял. Тебе нужно чтобы несколько потоков обрабатывали некий диапазон. Дык разбивай диапазон этот на части и передавай поддиапазон (начало/размер) в поток. Там локальной переменной присваивай начальный uid диапазона и в цикле проходи все. Тогда тебе ненужна синхронизация и глобальная переменная. А то в начале у тебя код другое подразумевал ).

2. пример с передачей параметров в поток я приводил на первой странице.

В точку! Но опять какая-то фигня... Параметры не передаются, решил сделать по-другому, но и здесь не пашет...

Функция, которая вызывает поток:
Код:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
start[0]=0;
start[1]=20;
start[2]=40;
start[3]=60;

end[0]=20;
end[1]=40;
end[2]=60;
end[3]=80;
//InitializeCriticalSection( &cs );
for(i=1;i<=4;i++)
{
_beginthread(tfunc, 0, NULL);
}
}

Глоб. переменные:
Код:

volatile long uid = 1;
CRITICAL_SECTION cs;
int i;
int myID = 0;
int *start = (int*)malloc(5*4);
int *end = (int*)malloc(5*4);

Сам поток:
Код:

void tfunc(void *argv)
{
int j;
char request[1024*5], response[1024*5];
int first, second;
char* headers;
myID =  InterlockedIncrement(&uid) - 1;
sockaddr_in s_a;
WSADATA wd;
WSAStartup( MAKEWORD( 2, 2 ), &wd );

for(j=start[myID]-1;j<=end[myID];j++)
{
SOCKET s = socket( AF_INET, SOCK_STREAM, 0 );
if (s == INVALID_SOCKET)
ShowMessage("INVALID");
s_a.sin_addr.s_addr = inet_addr( "89.188.109.206" );
s_a.sin_port = htons( 80 );
s_a.sin_family = AF_INET;
if (connect( s, (struct sockaddr*)&s_a, sizeof( s_a ) ) == SOCKET_ERROR)
ShowMessage("Bad Connect!");
sprintf( request, "GET /forum/profile.php?mode=viewprofile&u=%i HTTP/1.1\r\nHost: tfile.ru\r\nConnection: Close\r\n\r\n", j);
send( s, request, strlen( request ), 0 );
//EnterCriticalSection( &cs );
for(i=0;i<=10;i++)
recv( s, response, 1024 , 0 );
//LeaveCriticalSection( &cs );
closesocket( s );
}
WSACleanup(); //
}

Вообще хз как работает...

Буду преблагодарен за помощь...

Ra$cal 26.09.2009 14:56

Код:

#include <iostream>
#include <process.h>
using namespace std;

unsigned __stdcall SecondThreadFunc( void* pArguments )
{
        int* param_index = (int*)pArguments;
        int local_index = *param_index;
        delete param_index;

        cout << local_index << '\n';

        _endthreadex( 0 );
        return 0;
}


void main()
{
        unsigned int threadID;
        for(int i = 0; i < 5; i++){
                _beginthreadex( NULL, 0, &SecondThreadFunc, new int(i), 0, &threadID );
        }

        system("pause");
}

На выходе имеем:

0
2
4
3
1
Press any key to continue . . .

sn0w 26.09.2009 15:01

Цитата:

Сообщение от mailbrush
В общем интересуюсь этой проблемой уже давно. Пишу парсер аккаунтов одного форума. Пока тестовый, в 5 потоках надо отпарсить только 5 юзеов. Есть код
Код:

int uid = 1; //текущий айди юзера

void tfunc(void *argv) 
{
//здесь я создаю WinSock
sprintf( request, "GET /forum/profile.php?mode=viewprofile&u=%s HTTP/1.1\r\nHost: tfile.ru\r\nConnection: Close\r\n\r\n", IntToStr(uid).c_str()); //пишу в запрос id юзера
//здесь завершаю работу WinSock
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
for(i=0;i<=4;i++)
{
uid++;
_beginthread(tfunc, 0, NULL); //старт потока
}
}

Проблема в том, что запрос отправляется всегда с айди = 5, т.е. с последним значением uid. Почему?


утяж аргумент argv == NULL в beginthread, а вообще лучше - апи)


Время: 07:00