Раз поток, два поток

ThreadCreate

Любая программа имеет как минимум один поток. В нем все происходит последовательно в соответствии с логикой программы. Его можно назвать основным потоком. Но очень часто при разработке ресурсоемких программ, программисты делят выполнение программы на несколько автономных частей (потоков), выполняющихся одновременно и это дает существенный прирост производительности. К примеру известный MessageBox останавливает выполнение программы, а если нам нужен непрерывный вывод графики или сортировка данных в это время?  Вот тут как раз и выручат потоки. Хотя есть конечно и другая возможность, например запуск процедур по таймеру. Потоки выполняются в функциях или в процедурах, чаще всего в отдельном цикле. Как только происходит выход из функции или процедуры, поток завершается.

Для запуска нового потока разработчики определили функцию ThreadCreate. Ее синтаксис:

id = ThreadCreate (@Proc, [@param] , [stack_size])

  • id   - идентификатор потока
  • @Proc - адрес процедуры для запуска
  • @param - адрес на структуру или массив с данными (необязательный параметр)
  • stack_size - дополнительное выделение байт для стека (необязательный параметр)

Пример:

Sub ONE(param As Any Ptr)
    Dim a As Integer
    Do
        a+=1        
        View Print 1 To 2        
        Print "thread One, a=";a
        Sleep(70)
        If a=100 Then Exit Do
    Loop
    Print "Thread ONE ended   "
End Sub
Sub TWO(param As Any Ptr)
    Dim a As Integer
    Do
        a+=1
        View Print 3 To 4
        Print "thread TWO, a=";a        
        Sleep(50)
        If a=100 Then Exit Do
    Loop
    View Print 3 To 4
    Print "Thread TWO ended   "
End Sub

Threadcreate(@ONE())
Threadcreate(@TWO())
Sleep

В данном примере я создал два потока. Для каждого потока определил свою процедуру, хотя при желании можно запускать потоки в одной процедуре. Каждый из потоков в этом примере выполняется в своем цикле и имеет свою скорость, заданную с помощью Sleep . Именно поэтому второй поток хоть и запущен позже, но завершается быстрее. Для вывода информации каждого потока выделена отдельная область печати с помощью VIEW PRINT. Функция ThreadCreate как вы поняли не ждет завершения выполнения потока, она запускает его и передает выполнение следующей за ней команде. Второй параметр функции ThreadCreate очень полезен для передачи совокупности параметров при запуске процедуры. Третий параметр я в работе не встречал. Функция возвращает идентификатор потока, который нужен для управления этим самым потоком.

 

ThreadWait

Иногда может потребоваться приостановка выполнения текущего потока программы на время выполнения нового запускаемого потока. Этим заведует функция ThreadWait

ThreadWait(id)

Как видите у функции всего один параметр. Этим параметром выступает идентификатор, возвращенный функцией ThreadCreate.

Пример:

Sub ONE(param As Any Ptr)
    Dim a As Integer
    Do
        a+=1
        View Print 1 To 2
        Print "thread One, a=";a
        Sleep(40)
        If a=100 Then Exit Do
    Loop
End Sub

Dim As Any Ptr ID
ID = Threadcreate(@ONE())
Threadwait(ID)
Print "Thread ONE ended   "
Sleep

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

 

Мьютексы

Как я и писал выше, потоки могут использовать одну процедуру или функцию, но при этом появляются сложности по обработке данных, поскольку каждый из потоков может обращаться к переменным, объектам и пр. практически одновременно. Так может получиться, что один поток подпортит вычисления другого, третьего и так далее. В результате в программах появляются серьезные ошибки. Для того чтобы избежать подобных ошибок, нужны определенные переключатели, способные разрешать и запрещать доступ к данным. Такими переключателями стали мьютексы. Они способны открывать и закрывать доступ потокам, синхронизируя их работу. Так к примеру первый поток получил доступ к переменным, которые являются защищенными мьютексом. Другие потоки будут дожидаться своей очереди, пока первый поток не отработает с защищенными данными в области мьютекса. А теперь о функциях:

  • MutexCreate - создает мьютекс
  • MutexDestroy - уничтожает мьютекс
  • MutexLock - запирает область данных для одного потока
  • MutexUnLock - отпирает область данных и освобождает область для следующего потока

А теперь пример:

Dim Shared As Any Ptr Mutex
Sub ONE(param As Any Ptr)
    Static a As Integer
    Do
        Mutexlock(Mutex)
        a+=1
        Print a
        If a=100 Or a>1000 Then Exit Do
        Sleep(10)
        Mutexunlock(Mutex)
    Loop
End Sub

Mutex = Mutexcreate
Threadcreate(@ONE())
Threadcreate(@ONE())
Threadcreate(@ONE())
Sleep
Mutexdestroy(Mutex)

Здесь я создал мьютекс и далее три потока. В процедуре , и далее в цикле стоят переключатели MutexLock и MutexUnlock , которые управляют потоками. По моей идее потоки должны отработать время, пока A меньше 100. Для того, чтобы вам было более понятна вся польза мьютексов, закомментируйте строчки MutexLock и MutexUnlock  и запустите пример. Как видите я не зря создал лишнее условие a>1000.

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

 

Временная приостановка потоков

При работе с потоками может потребоваться приостанавливать их на определенное время в зависимости от определенных условий. Для этого можно создать специальную условную переменную, которую в свою очередь нужно передавать в параметре условным многопоточным командам.
Для создания переменной можно воспользоваться функцией CondCreate() Эта функция без параметров. Ее возвращаемое значение адрес условной переменной(хендл), который мы будем использовать в функциях:

  • CondWait(Condition,Mutex)  временно останавливает поток. Ее второй параметр идентификатор мьютекса
  • CondSignal(Condition)  возобновляет работу одного потока.
  • Condbroadcast(Condition)  возобновляет все остановленные потоки,  связанные с этой переменной
  • CondDestroy(Condition)  освобождает ресурсы условной переменной

Функция CondWait для того, чтобы приостанавливать поток, должна использоваться в блоке мьютекса.

Идентификаторы мьютексов и хедлы условных переменных должны в обязательном порядке высвобождаться с помощью MutexDestroy и CondDestroy!

Пример:

Dim Shared As Any Ptr Mutex,Condition

Sub ONE(param As Any Ptr)
    Static As Byte a,b
    Mutexlock(Mutex)
    b+=1
    Print "streams number " & b & " are locked!"
    Condwait(Condition,Mutex)
    a+=1
    Mutexunlock(Mutex)
    Print "streams number " & a & " are free!"
End Sub

Condition=CondCreate()
Mutex = Mutexcreate
Threadcreate(@ONE())
Sleep(1000)
Threadcreate(@ONE())
Sleep(1000)
Threadcreate(@ONE())
Sleep(1000)

Condbroadcast(Condition)

Sleep
Mutexdestroy(Mutex)
Conddestroy(Condition)

В примере с периодом в одну секунду запускается новый поток и запирается в мьютексе с помощью команды CondWait. После запуска всех потоков, функцией CondBroadCast все потоки высвобождаются. Если вы вместо CondBroadCast поставите функцию CondSignal, то высвободится только один из трех потоков. Для того, чтобы достичь того же эффекта, что и при вызове функции CondBroadCast, функцию CondSignal придется в данном случае вызвать три раза.

Это все по потокам. Всего доброго!

содержание | назад | вперед