Author Topic: Потоки  (Read 102819 times)

0 Members and 1 Guest are viewing this topic.

Johnny

  • Создатель
  • Герой форума
  • *
  • Posts: 593
    • View Profile
Потоки
« on: October 25, 2014, 09:32:54 AM »
Актуально для версии 4.11+

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

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

Данный поток с именем thr_name начнет крутиться сразу после старта скрипта. Если вы хотите что бы поток запустился позже, то необходимо в шапке после имени добавить 0. То есть
Code: (clickermann) [Select]
Thread(thr_name, 0)
// тело потока
End_thread

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

Каждый поток обладает собственным адресным пространством, поэтому задержка wait() в одном ни коим образом не влияет на остальные потоки. При этом, потоки имеют доступ к общим переменным и графическому буферу. Стоит отметить, что из-за особенностей архитектуры кликера, подпрограммы Sub() не могут разделяться потоками, поэтому для каждого они индивидуальны и должны быть объявлены внутри потока. Другими словами, подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком.
Основное (абстрактно) тело скрипта, даже если оно описано без thread() .. end_thread так же является отдельным потоком, поэтому для него справедливо все вышесказанное. Поэтому фактически, сценарии вида
Code: (clickermann) [Select]
Thread(thr1)
   Wait(1)
End_thread

Print("Hello")
Wait(3)
И
Code: (clickermann) [Select]
Thread(thr1)
   Wait(1)
End_thread

Thread(thr2)
   Print("Hello")
Wait(3)
End_thread
Абсолютно одинаковы.
Последнее что вам необходимо знать, это то, что как и основной скрипт, потоки должны «давать передохнуть» процессору, поэтому потрудитесь разместить внутри них wait’ы, на которые он обязательно наткнется, иначе ваш сценарий будет серьезно нагружать процессор в ряде случаев.

Несколько показательных примеров
Первый случай, о котором иногда спрашивают – это нажатие клавиш (да и вообще действия) по интервалу. Например, каждые 3 секунды жмем A, каждые 5 секунд – B. В обычном случае нам бы потребовался единичный интервал (секунда) и два счетчика, которые наращиваются. Затем в двух условиях проверяются их значения и если счетчик достиг 3 (или 5) то выполняются действия и счетчик обнуляется. Два счетчика нужны, потому что действия независимы друг от друга. Сам скрипт выглядит так (для удобства сами нажатия заменены на вывод в лог)
Code: (clickermann) [Select]
wait(1)
$cnt1 = $cnt1 + 1
$cnt2 = $cnt2 + 1

if($cnt1 = 3)
   print("A")
   $cnt1 = 0
end_if

if($cnt2 = 5)
   print("B")
   $cnt2 = 0
end_if

При этом, если внутри условия будет ряд других затратных инструкций или упаси Боже задержка – в классическом решении реализовать такое было бы весьма затратно (приходилось бы вычислять временные затраты на каждое действие, вводить поправки на разницу и т.д.)
Реализуем тот же самый скрипт на потоках
Code: (clickermann) [Select]
thread(th1)
   print("A")
   wait(3)
end_thread

thread(th2)
   print("B")
   wait(5)
end_thread

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

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

Code: (clickermann) [Select]
if (iskeydown(#A)=1)
   print("A")
   wait(3)
end_if

if (iskeydown(#B)=1)
   print("B")
   wait(3)
end_if

waitms(10)

Безусловно, можно попробовать добавить третье условие двойной проверки обоих нажатий iskeydown() & iskeydown(), но тогда будут срабатывать и одиночные условия, ведь клавиша то зажата. Неразрешимая для неискушенного программиста задача элементарно решается введением потоков под каждый хоткей.
Code: (clickermann) [Select]
thread(th1)
   if (iskeydown(#A)=1)
      print("A")
      wait(3)
   end_if
   waitms(10)
end_thread


thread(th2)
   if (iskeydown(#B)=1)
      print("B")
      wait(3)
   end_if
   waitms(10)
end_thread

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

i0

  • Оплот сообщества
  • ****
  • Posts: 353
  • CMann 4.13.014 final, ie, presto, win7 x86, x64
    • View Profile
Re: Потоки
« Reply #1 on: October 25, 2014, 02:19:14 PM »
подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком
прошу пример

Johnny

  • Создатель
  • Герой форума
  • *
  • Posts: 593
    • View Profile
Re: Потоки
« Reply #2 on: October 25, 2014, 03:10:43 PM »
подпрограммы описанные внутри одного потока не могут быть вызваны другим потоком
прошу пример
да никакого особого примера не нужно. просто если хочется использовать подпрограмму внутри потока то оформление должно быть таким

Code: (clickermann) [Select]
thread(th1)

  sub(inside)
    // ...
  end_sub

  // call
  inside()

end_thread

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

Code: (clickermann) [Select]
sub(outside)
  // ...
end_sub


thread(th1)
  // call
  outside()

end_thread

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

i0

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

Hito

  • Герой форума
  • *****
  • Posts: 1212
    • View Profile
Re: Потоки
« Reply #4 on: October 29, 2014, 12:38:14 PM »
Вопросик...

Code: (clickermann) [Select]
Thread(thr_name)
   // Фармим мобов
End_thread

GETSCREEN
IF_PICTURE_IN (0,0, $_xmax,$_ymax, "file.bmp", -1, 100)
   setThread(thr_name, 0)
   WAITMS(50)
ELSE
   setThread(thr_name, 1)
   WAITMS(50)
END_IF

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

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Posts: 3685
  • Котэ
    • View Profile
Re: Потоки
« Reply #5 on: October 29, 2014, 01:03:10 PM »
Возможно ли НЕ выключать поток, А ставить его на паузу и снимать с паузы?

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

Hito

  • Герой форума
  • *****
  • Posts: 1212
    • View Profile
Re: Потоки
« Reply #6 on: October 29, 2014, 01:04:05 PM »
Возможно ли НЕ выключать поток, А ставить его на паузу и снимать с паузы?

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

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

Prorok.18

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

Atas

  • Активный участник
  • ***
  • Posts: 147
    • View Profile
Re: Потоки
« Reply #8 on: November 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

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

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Posts: 3685
  • Котэ
    • View Profile
Re: Потоки
« Reply #10 on: November 01, 2014, 05:00:57 PM »
P. S. Дождемся ли мы когда кликер сможет сворачиваться не в трей, а в панель задач? Спасибо.

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

Alekzandr

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

Oraven

  • Супермодератор
  • Герой форума
  • *
  • Posts: 3685
  • Котэ
    • View Profile
Re: Потоки
« Reply #12 on: November 03, 2014, 04:16:26 PM »
А смысл тебе тогда потоки делать?

Code: (clickermann) [Select]
IF($time1 < $_time_t)
   // условие сработает снова через 60 сек

   $time1 = $_time_t + 60
END_IF

IF($time2 < $_time_t)
   // условие сработает снова через 2 минуты

   $time2 = $_time_t + 120
END_IF

WAIT(1)

Alekzandr

  • Зашел в гости
  • *
  • Posts: 8
    • View Profile
Re: Потоки
« Reply #13 on: November 03, 2014, 04:24:58 PM »
Да, спасибо, мне так и нужно.

Alex59

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

Code: [Select]
#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