PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Параллельные потоки, мьютексы и семафоры


Параллельные потоки, мьютексы и семафоры

Сообщений 1 страница 18 из 18

1

Пётр
Есть статейка или видеурок по потокам? Не въезжаю, что такое мьютексы и семафоры, для чего нужны и как работают. Примеры из справки непонимаю :no:

0

2

Что не понятно: принцип построения программы или само понятие - потоки?

Вот одна и та же программа в потоке и без, попробуй перемещать по экрану...

Код:
Enumeration 
  #Window_0 
  #Start 
  #Text
EndEnumeration 

Procedure Progr()
 For a=0 To 100000
  SetGadgetText(#Text,Str(a))
  Delay(100)
 Next   
EndProcedure  
  
    
OpenWindow(#Window_0, 100, 150, 400, 200, "Работа без потока",#PB_Window_SystemMenu|#PB_Window_WindowCentered) 
 ButtonGadget(#Start,20,50,70,20,"Старт")   
 TextGadget(#Text, 150,50, 30,20,"")
      
  Repeat 
  Event = WaitWindowEvent() 
    If Event = #PB_Event_Gadget    
      Select EventGadget() 
        Case #Start 
         
           Progr()
 
      EndSelect 
    EndIf               
  Until Event = #PB_Event_CloseWindo
Код:
Enumeration 
  #Window_0 
  #Start
  #Text 
EndEnumeration 

Procedure Progr(*x)
  For a=0 To 100000
  SetGadgetText(#Text,Str(a))
  Delay(100)
 Next    
EndProcedure  
  
    
OpenWindow(#Window_0, 100, 150, 400, 200, "Работа в потоке",#PB_Window_SystemMenu|#PB_Window_WindowCentered) 
 ButtonGadget(#Start,20,50,70,20,"Старт")   
 TextGadget(#Text, 150,50, 30,20,"")

  Repeat 
    Event = WaitWindowEvent() 
    If Event = #PB_Event_Gadget    
      Select EventGadget() 
        Case #Start  

              If IsThread(ThreadID)=0
               ThreadID=CreateThread(@Progr(), x)
              Else
               MessageRequester("", "Программа занята!", #MB_OK|#MB_ICONWARNING)
              EndIf  

      EndSelect 
    EndIf               
  Until Event = #PB_Event_CloseWindow

Отредактировано mirashic (27.01.2011 15:06:21)

0

3

mirashic
Как создать и запустить поток знаю. Но управлять им, т.е. допустим корректно завершить :dontknow:  Я объявляю глобальную переменную(флаг) и выставляю его допустим в единицу, в потоке постоянно приходиться проверять этот флаг, и если он равен 1 то завершить все операции в потоке(чтоб не потерять данные) и выйти ProcedureReturn. Но зачем тогда  нужны мьютексы и семафоры?

0

4

Мьютексы нужны для предотвращения конфликтов при коллективном (одновременном) доступе к ресурсу.
Вот пример из справки:

Код:
Procedure WithoutMutex(*Number)     
    Shared Mutex
    
    For a = 1 To 5      
      ;LockMutex(Mutex)    ; uncomment this to see the difference
    
      PrintN("Thread "+Str(*Number)+": Trying to print 5x in a row:")
      For b = 1 To 5
        Delay(50)
        PrintN("Thread "+Str(*Number)+" Line "+Str(b))
      Next b          
      
      ;UnlockMutex(Mutex) ; uncomment this to see the difference
    Next a    
  EndProcedure
  
  OpenConsole()
  Mutex = CreateMutex()
  
  thread1 = CreateThread(@WithoutMutex(), 1)
  Delay(25)
  thread2 = CreateThread(@WithoutMutex(), 2)
  Delay(25)
  thread3 = CreateThread(@WithoutMutex(), 3)
  
  WaitThread(thread1)
  WaitThread(thread2)
  WaitThread(thread3)
  
  Input()

Если функции LockMutex и UnlockMutex закомментированы, то в консоль выводится инфа в в беспорядочном виде, но стоит разкомментировать функции, то данные выводятся в строгом порядке.
В данном случае, работает только поток, захвативший Mutex, (LockMutex) а остальные ждут своей очереди пока не освободится Mutex (UnlockMutex).

Использование Mutex'ов желательно, скажем, при работе нескольких потоков с одним динамическим списком, чтобы не попортились данные в списке.

0

5

Примерно понял, но на ум, не приходит практического применения :canthearyou: , и по мьютексу ещё один вопрос, LockMutex и UnlockMutex должны быть обязательно, внутри процедуры, или нет? Всё же хотелось бы увидеть реальный пример применения мьютекса и семафора. И про коректное завершение потока я правильно делаю, устанавливая флаг, или есть более изящный способ.

0

6

lakomet написал(а):

и по мьютексу ещё один вопрос, LockMutex и UnlockMutex должны быть обязательно, внутри процедуры, или нет

Они должны быть в процедуре, выполняемой в отдельном потоке.

lakomet написал(а):

И про коректное завершение потока я правильно делаю, устанавливая флаг, или есть более изящный способ.

Ну наверное правильно, но без примера кода сложно сказать...
Поток можно "убить" с помощью KillThread, но это не безопасно в том, плане, что поток "убивается" сразу же, т. е. если при завершении потока нужно освободить память или др. объекты, то убив поток, этого не произойдет, и из-за этого, могут появиться различные глюки!

Немедленно убивает заданный поток, ранее созданный с помощью функции CreateThread(). Эта функция очень опасна и должна использоваться как можно реже. Проблема в том, что поток немедленно убивается и не получает шанса выполнить какой-либо код завершения (например освободить память, удалить элементы, освободить собственный стек).

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

Вообще, функцию KillThread нужно использовать только в крайнем случае, скажем, если поток повис и поэтому с помощью флага не завершить его работу.

0

7

Вот кстати пример с динамическим списком показывающий как решить вопрос коллизий при работе с ним.

Код:
Global Semaphore = CreateSemaphore()
Global Mutex     = CreateMutex()
Global NewList Queue()


Procedure Producer(Total)

  For i = 1 To Total
    Delay(Random(750) + 250)
    
    ; Блокируем остальные потоки (в т. ч. и основной) чтобы не произошло коллизий при работе с динамическим списком
    LockMutex(Mutex)
      LastElement(Queue())
      AddElement(Queue())
      Queue() = i
    UnlockMutex(Mutex) ; Разблокируем остальные потоки.   

    ; Подаём сигнал что нужно обработать данные
    SignalSemaphore(Semaphore)
  Next i
    
EndProcedure

CreateThread(@Producer(), 30)

  For i = 1 To 30  
    ; Ждем сигнала от дополнительного потока
    WaitSemaphore(Semaphore)
    
    ; Блокируем остальные потоки, использующие данный Mutex, чтобы не произошло коллизий при работе с динамическим списком
    LockMutex(Mutex)
      Queue$ = "Queue:"
      ForEach Queue()
        Queue$ + " " + Str(Queue())
      Next Queue()
      Debug Queue$
    
      ; remove head element from the queue
      FirstElement(Queue())
      DeleteElement(Queue())
    UnlockMutex(Mutex) ; Разблокируем остальные потоки.  
    
  Next i

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

0

8

Придумал применение LockMutex и UnlockMutex, завтра буду пробовать. Ещё про семафоры разжуйте, а то вообще тёмный лес.

0

9

Пока писал пример появился. В примере

Пётр написал(а):

Блокируем остальные потоки

вопрос какие остальные, если CreateThread, вызывается один раз?

0

10

lakomet написал(а):

вопрос какие остальные, если CreateThread, вызывается один раз?

В том примере два потока, основной и дополнительный, созданный с помощью CreateThread.
Там, данные генерируются в дополнительном потоке, а основной (с помощью WaitSemaphore) ожидает их появления.
Как в основном, так и дополнительном потоках, осуществляется работа с динамическим списком, а мьютексы, блокируют один из потоков чтобы не произошла такая ситуация, что основной дополнительный поток создал элемент в списке, а основной его тут же удалил что приведет к ошибке в программе.

0

11

Пётр
Не понял, ткни носом где начинается основной, где дополнительный?

0

12

Дополнительный поток:

Код:
Procedure Producer(Total)

  For i = 1 To Total
    Delay(Random(750) + 250)
    
    ; Блокируем остальные потоки (в т. ч. и основной) чтобы не произошло коллизий при работе с динамическим списком
    LockMutex(Mutex)
      LastElement(Queue())
      AddElement(Queue())
      Queue() = i
    UnlockMutex(Mutex) ; Разблокируем остальные потоки.   

    ; Подаём сигнал что нужно обработать данные
    SignalSemaphore(Semaphore)
  Next i
    
EndProcedure

Основной поток:

Код:
CreateThread(@Producer(), 30)

  For i = 1 To 30  
    ; Ждем сигнала от дополнительного потока
    WaitSemaphore(Semaphore)
    
    ; Блокируем остальные потоки, использующие данный Mutex, чтобы не произошло коллизий при работе с динамическим списком
    LockMutex(Mutex)
      Queue$ = "Queue:"
      ForEach Queue()
        Queue$ + " " + Str(Queue())
      Next Queue()
      Debug Queue$
    
      ; remove head element from the queue
      FirstElement(Queue())
      DeleteElement(Queue())
    UnlockMutex(Mutex) ; Разблокируем остальные потоки.  
    
  Next i

0

13

Пётр
В упор не вижу второго потока, или подразумевается второй поток For i = 1 To 30Next i?

0

14

lakomet написал(а):

lakomet

любой код, выполняемый в твоей программе, это и есть уже созданный первый (основной) поток.

Для примера, для чего нужны Мьютексы, например запущены два поток, кот. периодически будут записывать данные а один ЛистИконГаджет, если один поток обратится к гаджету именно в тот момент когда ID гаджета "открыто" вторым потоком (используется/записывается в гаджет), то произойдет вылет программы с ошибкой. Для это нужно использовать, например Мьютекс, чтобы Мьютекс "закрывался" когда один из потоков использует гаджет, а после окончания записи в гаджет "открывался", для того чтобы мог туда писать другой поток....
....  примерно так

0

15

lakomet написал(а):

В упор не вижу второго потока, или подразумевается второй поток For i = 1 To 30 -  Next i?

Сама программа это основной поток, а процедура Producer() - дополнительный поток, в итоге, 2 потока!

Adrenalin написал(а):

будут записывать данные а один ЛистИконГаджет, если один поток обратится к гаджету именно в тот момент когда ID гаджета "открыто" вторым потоком (используется/записывается в гаджет), то произойдет вылет программы с ошибкой

Не факт.
Дело в том, что происходит не прямая запись в гаджет, а посылка сообщения в обработчик событий гаджета, с помощью SendMessage или PostMessage (это в винде). Так что сообщения будут обработаны по очереди, а не одновременно.

0

16

Ну допустим есть три кнопки a,b,c и три процедуры A(),B(),C(). Нажатием кнопки a запускаем поток A(), b запускаем поток B(), c запускаем поток C(), используя мьютекс в потоке B(), я могу запретить выполнение потоков A() и C()?

0

17

Пётр написал(а):

Не факт.

почему привел именно этот пример: была именно такая ситуация, и мьютекс помог.

PS: сначала долго не мог понять почему прога вылетала, могла отработать без проблемм долго, а то вдруг дохла (очень редко могли они одновременно попасть), в итоге пришел к тому что именно в такой момент (одновременное обращение к гаджету), вылетала прога...  сори привести именно тот пример не могу, было давно это... спорить не буду, но мне помог мьютекс, именно "закрывающийся" до команды "запись в гаджет данных" и тут же после команды 'мьютекс на открытие'.  (до мьютекса безопасный поток был тоже включен).

0

18

Внимание, грабли! %-)  :hobo:

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

Код:
Global Mutex = CreateMutex()

Procedure Thread(*x)
  LockMutex(Mutex)
EndProcedure

CreateThread(@Thread(), 0)
Delay(1000)
LockMutex(Mutex)
MessageRequester("","OK")

0


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Параллельные потоки, мьютексы и семафоры