Автор Тема: Потоки  (Прочитано 49450 раз)

0 Пользователей и 2 Гостей просматривают эту тему.

Johnny

  • Создатель
  • Герой форума
  • *
  • Сообщений: 556
    • Просмотр профиля
Потоки
« : Октябрь 25, 2014, 09:32:54 am »
Актуально для версии 4.11+

Многопоточность в Clickermann

Как все знают со школы, Windows – многозадачная операционная система. Впрочем, как и большинство современных ОС, за исключением особо специализированных. Это значит, что на машине под управлением ОС могут одновременно выполняться несколько задач, в нашем случае приложений. Можно долго рассуждать на тему, что одно неудачное действие повесит все намертво и что задачи выполняются не параллельно, а просто быстро переключаются друг между другом. Но примем это за аксиому.
Подобно этому, в составе приложения можно организовать несколько потоков (нитей, англ. thread). И они точно так же могут выполняться параллельно, как и сами приложения. С рядом оговорок. Во-первых, потоки могут свободно использовать общие данные своего родительского приложения. Во-вторых (хотя косвенно это вытекает из первого) потоки могут останавливать друг друга, снова запускать и так далее.
Для чего нужны потоки? Для оптимизации работы. Если взять за догмат то, что две параллельные задачи выполняются по крайней мере не медленнее, чем две последовательные (хотя это спорное утверждение), то целесообразно наиболее ресурсоемкие выделять в отдельные потоки. Это позволит выиграть в общем логическом быстродействии. Поясню почему. Если один какой-то поток повиснет, серьезно задумается или в нем произойдет сбой, это не убьет (замедлит) приложение целиком. Это вы можете наблюдать например в браузере Хром, где повисание одной вкладки не парализует весь браузер. Огнелиса так не умеет…
Существует лишь одна особенность – разделение ресурсов. При прочих, равных нельзя знать в какой момент начнет и бросит выполняться поток, помещать в них стоит относительно автономные задачи. Плохая идея в одном потоке наращивать переменную, а в другом записывать ее значение в файл столбиком. Пропуски значений и наоборот повторяющиеся – обычное дело при данном некорректном логическом подходе. Для решения этой задачи используются общие переменные флаги – «семафоры», анализируя которые потоки понимают, какой из них отработал и чья очередь теперь выполнить свою задачу.
Перейдем наконец к коду. Поток в кликермане объявляется следующим, похожим на подпрограмму, образом.
Код: Clickermann
  1. Thread(thr_name)
  2. // тело потока
  3. End_thread

Данный поток с именем thr_name начнет крутиться сразу после старта скрипта. Если вы хотите что бы поток запустился позже, то необходимо в шапке после имени добавить 0. То есть
Код: Clickermann
  1. Thread(thr_name, 0)
  2. // тело потока
  3. End_thread

Теперь данный поток можно запустить лишь из самого скрипта специальной процедурой контроля потоков setThread:
Код: Clickermann
  1. setThread(thr_name, 1)
в которой thr_name – имя потока, а 1 – присваиваемое состояние «запущен», заменив которое на 0 можно так же приостановить поток. Потоки могут невозбранно контролировать друг друга.

Каждый поток обладает собственным адресным пространством, поэтому задержка wait() в одном ни коим образом не влияет на остальные потоки. При этом, потоки имеют доступ к общим переменным и графическому буферу. Стоит отметить, что из-за особенностей архитектуры кликера, подпрограммы Sub() не могут разделяться потоками, поэтому для каждого они индивидуальны и должны быть объявлены внутри потока. Другими словами, подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком.
Основное (абстрактно) тело скрипта, даже если оно описано без thread() .. end_thread так же является отдельным потоком, поэтому для него справедливо все вышесказанное. Поэтому фактически, сценарии вида
Код: Clickermann
  1. Thread(thr1)
  2.   Wait(1)
  3. End_thread
  4.  
  5. Print("Hello")
  6. Wait(3)
И
Код: Clickermann
  1. Thread(thr1)
  2.   Wait(1)
  3. End_thread
  4.  
  5. Thread(thr2)
  6.   Print("Hello")
  7. Wait(3)
  8. End_thread
Абсолютно одинаковы.
Последнее что вам необходимо знать, это то, что как и основной скрипт, потоки должны «давать передохнуть» процессору, поэтому потрудитесь разместить внутри них wait’ы, на которые он обязательно наткнется, иначе ваш сценарий будет серьезно нагружать процессор в ряде случаев.

Несколько показательных примеров
Первый случай, о котором иногда спрашивают – это нажатие клавиш (да и вообще действия) по интервалу. Например, каждые 3 секунды жмем A, каждые 5 секунд – B. В обычном случае нам бы потребовался единичный интервал (секунда) и два счетчика, которые наращиваются. Затем в двух условиях проверяются их значения и если счетчик достиг 3 (или 5) то выполняются действия и счетчик обнуляется. Два счетчика нужны, потому что действия независимы друг от друга. Сам скрипт выглядит так (для удобства сами нажатия заменены на вывод в лог)
Код: Clickermann
  1. wait(1)
  2. $cnt1 = $cnt1 + 1
  3. $cnt2 = $cnt2 + 1
  4.  
  5. if($cnt1 = 3)
  6.   print("A")
  7.   $cnt1 = 0
  8. end_if
  9.  
  10. if($cnt2 = 5)
  11.   print("B")
  12.   $cnt2 = 0
  13. end_if

При этом, если внутри условия будет ряд других затратных инструкций или упаси Боже задержка – в классическом решении реализовать такое было бы весьма затратно (приходилось бы вычислять временные затраты на каждое действие, вводить поправки на разницу и т.д.)
Реализуем тот же самый скрипт на потоках
Код: Clickermann
  1. thread(th1)
  2.   print("A")
  3.   wait(3)
  4. end_thread
  5.  
  6. thread(th2)
  7.   print("B")
  8.   wait(5)
  9. end_thread

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

Второе распространенное решение –  реализация псевдо хоткеев через iskeydown(). Как вы знаете эта функция проверяет нажата ли клавиша. Но проверить это она может только в момент собственного выполнения. То есть если у вас есть два условия с iskeydown() и первое сработало, то в том случае если внутри него будут пресловутые задержки и долгие действия, проверить нажатие второй клавиши удастся только когда первое условие отработает целиком.
Пример старого образца. Два последовательных условия, которые в случае нажатия клавиши печатают ее в лог каждые 3 секунды наши любимые буквы A и B. Поскольку задержка в каждом условии составляет 3 секунды, то у вас не получится вывести в протокол сразу обе буквы даже если вы зажмете обе клавиши одновременно.

Код: Clickermann
  1. if (iskeydown(#A)=1)
  2.   print("A")
  3.   wait(3)
  4. end_if
  5.  
  6. if (iskeydown(#B)=1)
  7.   print("B")
  8.   wait(3)
  9. end_if
  10.  
  11. waitms(10)

Безусловно, можно попробовать добавить третье условие двойной проверки обоих нажатий iskeydown() & iskeydown(), но тогда будут срабатывать и одиночные условия, ведь клавиша то зажата. Неразрешимая для неискушенного программиста задача элементарно решается введением потоков под каждый хоткей.
Код: Clickermann
  1. thread(th1)
  2.   if (iskeydown(#A)=1)
  3.      print("A")
  4.      wait(3)
  5.   end_if
  6.   waitms(10)
  7. end_thread
  8.  
  9.  
  10. thread(th2)
  11.   if (iskeydown(#B)=1)
  12.      print("B")
  13.      wait(3)
  14.   end_if
  15.   waitms(10)
  16. end_thread

Визуально код стал более объемным, однако не стоит забывать, что в отличие от первого, этот – работает. Вы легко можете зажимать клавиши хоть одновременно, хоть как – такты не собьются.
Таким образом, потоки позволяют легко решить ряд прикладных задач, которые до этого решались если не запущенными копиями кликера, то как минимум жуткими костылями. И их потенциал неограничен приведенными примерами, конечно же.
« Последнее редактирование: Октябрь 25, 2014, 03:17:43 pm от Johnny »

i0

  • Оплот сообщества
  • ****
  • Сообщений: 348
  • CMann 4.13.014 final, ie, presto, win7 x86, x64
    • Просмотр профиля
Re: Потоки
« Ответ #1 : Октябрь 25, 2014, 02:19:14 pm »
подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком
прошу пример

Johnny

  • Создатель
  • Герой форума
  • *
  • Сообщений: 556
    • Просмотр профиля
Re: Потоки
« Ответ #2 : Октябрь 25, 2014, 03:10:43 pm »
подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком
прошу пример
да никакого особого примера не нужно. просто если хочется использовать подпрограмму внутри потока то оформление должно быть таким

Код: Clickermann
  1. thread(th1)
  2.  
  3.  sub(inside)
  4.    // ...
  5.  end_sub
  6.  
  7.  // call
  8.  inside()
  9.  
  10. end_thread

а вот так работать НЕ будет:

Код: Clickermann
  1. sub(outside)
  2.  // ...
  3. end_sub
  4.  
  5.  
  6. thread(th1)
  7.  // call
  8.  outside()
  9.  
  10. end_thread

объективно эту проблему можно решить в обозримом будущем, но пока держите это в голове

i0

  • Оплот сообщества
  • ****
  • Сообщений: 348
  • CMann 4.13.014 final, ie, presto, win7 x86, x64
    • Просмотр профиля
Re: Потоки
« Ответ #3 : Октябрь 25, 2014, 04:59:31 pm »
если один и тот же саб нужен для работы разным потокам, его можно (и нужно) включить в каждый тред инклюдом?
норм, и читабельность не портится. главное – не забыть.

Hito

  • Герой форума
  • *****
  • Сообщений: 607
    • Просмотр профиля
Re: Потоки
« Ответ #4 : Октябрь 29, 2014, 12:38:14 pm »
Вопросик...

Код: Clickermann
  1. Thread(thr_name)
  2.   // Фармим мобов
  3. End_thread
  4.  
  5. GETSCREEN
  6. IF_PICTURE_IN (0,0, $_xmax,$_ymax, "file.bmp", -1, 100)
  7.   setThread(thr_name, 0)
  8.   WAITMS(50)
  9. ELSE
  10.   setThread(thr_name, 1)
  11.   WAITMS(50)
  12. END_IF

Возможно ли НЕ выключать поток, А ставить его на паузу и снимать с паузы?
Я не ду... Потому и не бу...

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Сообщений: 3149
  • Котэ
    • Просмотр профиля
Re: Потоки
« Ответ #5 : Октябрь 29, 2014, 01:03:10 pm »
Возможно ли НЕ выключать поток, А ставить его на паузу и снимать с паузы?

Как раз таки он и ставится только на паузу, а мне бы вот перезапуск бы сильно не помешал :\

Hito

  • Герой форума
  • *****
  • Сообщений: 607
    • Просмотр профиля
Re: Потоки
« Ответ #6 : Октябрь 29, 2014, 01:04:05 pm »
Возможно ли НЕ выключать поток, А ставить его на паузу и снимать с паузы?

Как раз таки он и ставится только на паузу, а мне бы вот перезапуск бы сильно не помешал :\

Ну да... Хорошо бы, чтобы, и такая команда была, и такая...
Я не ду... Потому и не бу...

Prorok.18

  • Гость
Re: Потоки
« Ответ #7 : Ноябрь 01, 2014, 04:08:58 pm »
Многопоточность это здорово, но как мне перевести строку из INIREAD в число? ;)
P. S. Дождемся ли мы когда кликер сможет сворачиваться не в трей, а в панель задач? Спасибо.
« Последнее редактирование: Ноябрь 01, 2014, 04:25:13 pm от Prorok »

Atas

  • Активный участник
  • ***
  • Сообщений: 134
    • Просмотр профиля
Re: Потоки
« Ответ #8 : Ноябрь 01, 2014, 04:28:49 pm »
Многопоточность это здорово, но как мне перевести строку из INIREAD в число? ;)

Цитата из справки Clickermann v4.11 (build 000)

INT

Синтаксис
INT (num) - числовая функция; возвращает целую часть числа без округления

Параметры 
num - число

Пример 

$var = int(25.73)
print($var)   // 25

Примечания 
Так же может переводить некоторые строки в числа . Например, строку, содержащую шестнадцатиричное представление числа.
Если вам нужно округлить число до заданной точности, см. функцию ROUND


Prorok.18

  • Гость
Re: Потоки
« Ответ #9 : Ноябрь 01, 2014, 04:56:38 pm »
Цитировать
INT (num) - числовая функция; возвращает целую часть числа без округления
Спасибо большое, работает! И кто догадался до этого

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Сообщений: 3149
  • Котэ
    • Просмотр профиля
Re: Потоки
« Ответ #10 : Ноябрь 01, 2014, 05:00:57 pm »
P. S. Дождемся ли мы когда кликер сможет сворачиваться не в трей, а в панель задач? Спасибо.

Он всегда это мог
Файл Clickermann\data\config.ini
; Если 1, то в свернутом состоянии программа не будет отображаться на панели задач
; Только в системном трее
only_tray = 1

Alekzandr

  • Зашел в гости
  • *
  • Сообщений: 8
    • Просмотр профиля
Re: Потоки
« Ответ #11 : Ноябрь 03, 2014, 04:11:49 pm »
Здравствуйте! Есть 2 потока, у каждого после его работы свой период ожидания, подскажите, можно ли сделать, чтобы если у одного из них вышел назначенный период ожидания, а в это время совершает какую-то работу другой, первый чтоб продлил свое ожидание, дождался когда другой дойдет до своего wait(...),  и только тогда делал свои действия.

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Сообщений: 3149
  • Котэ
    • Просмотр профиля
Re: Потоки
« Ответ #12 : Ноябрь 03, 2014, 04:16:26 pm »
А смысл тебе тогда потоки делать?

Код: Clickermann
  1. IF($time1 < $_time_t)
  2.   // условие сработает снова через 60 сек
  3.  
  4.   $time1 = $_time_t + 60
  5. END_IF
  6.  
  7. IF($time2 < $_time_t)
  8.   // условие сработает снова через 2 минуты
  9.  
  10.   $time2 = $_time_t + 120
  11. END_IF
  12.  
  13. WAIT(1)

Alekzandr

  • Зашел в гости
  • *
  • Сообщений: 8
    • Просмотр профиля
Re: Потоки
« Ответ #13 : Ноябрь 03, 2014, 04:24:58 pm »
Да, спасибо, мне так и нужно.

Alex59

  • Зашел в гости
  • *
  • Сообщений: 6
    • Просмотр профиля
Re: Потоки
« Ответ #14 : Ноябрь 16, 2014, 10:40:10 am »
Доброго времени. Что-то не работает поток в моем случае, я открываю 1 окно (делаю на передний план) - копирую из него текст, затем открываю 2 окно и вставляю текст туда. Но как только в поток эти действия включаю - перестает работать:

#name "test_3"
WNDBUMP(65994)

WAIT(1)

DBLCLICK(462,425)

WAIT(1)
KEYDOWN (#CTRL)
WAITMS (50)
KEYDOWN (#C)
WAITMS (50)
KEYUP (#C)
KEYUP (#CTRL)

WAIT(1)

WNDBUMP(197118)

WAIT(1)

WAITMS(300)
KEYDOWN (#CTRL)
WAITMS (50)
KEYDOWN (#V)
WAITMS (50)
KEYUP (#V)
KEYUP (#CTRL)
waitms(200)

KEYPRESS(#ENTER)

HALT