BlackTDN Search

sexta-feira, 28 de novembro de 2008

Protheus :: Variáveis Globais, Threads e outros bichos

Falarei hoje sobre Variáveis Globais (Global Variables) e Threads. Usarei como primeiro exemplo o programa u_sudoku.prg depois escreverei dois estudos de caso.

Conhecemos as variáveis de escopo Public, Private, Static e Local. As variáveis Public e Private podem ser declaradas em qualquer parte do programa. As variáveis Static podem ser externas ou internas. Externas, quando declaradas fora de qualquer Function ou Procedure e no cabeçalho do programa em que estão sendo declaradas. E, internas, quando atreladas à uma Procedure ou Function e logo após a declaração das variáveis de escopo Local. Já as variáveis de escopo Local  apenas depois da declaração da Procedure ou Function e antes de qualquer procedimento.

As variáveis Private tem um tratamento especial, elas podem ser declaradas de três formas:

Explicitamente:

Private nPrivateVar

Implicitamente, atribuindo-se um valor a uma variável não declarada anteriormente, como por exemplo:

IF ( Type("nPrivateNaoDeclarada") == "U" )
nPrivateNaoDeclarada := "Terei o escopo Private e a partir desse ponto todo mundo me vê"
ENDIF

Ou, ainda, através da função _SetOwnerPrvt( cVar , uValue ) uma grande sacada da microsiga. _SetOwnerPrvt() é interessante pois a Private declarada à partir dela pode ser acessada pela função um nível acima de onde foi declarada. Veja como é interessante.

User Function Foo()

Private nPrvtFoo := 10

TestSetOwp()

U_Foo() não tem  acesso a variável Publica declarada Explicitamente em uma rotina de nível inferior.

Type("nPrvtNoSetOwp")  -> "U"

Aqui U_Foo() tem acesso à variável nPrvtSetOwp declarada pela _SetOwnerPrvt()

Type("nPrvtSetOwp")  -> "N"

Return( NIL )

Static Function TestSetOwp()

Neste Ponto TestSetOwp() tem acesso à variável Private declarada um nível acima
Type("nPrvtFoo")  -> "N" 

Declarando dessa forma todas as rotinas a partir deste ponto terão acesso e a rotina chamadora também terá acesso à esse variável

_SetOwnerPrvt( "nPrvtNoSetOwp" , 1512 )

Type("nPrvtNoSetOwp") -> "N" 

Declarando dessa forma apenas as rotinas chamadas à partir desse ponto terão acesso

Private nPrvtNoSetOwp := 1215

Type("nPrvtNoSetOwp")  -> "U" 

Return( NIL )

O Problema dessas variáveis é que elas só estão disponíveis dentro da Thread que as criou, se necessitarmos ter acesso ao conteúdo dessar variáveis em outra Thread não será possível. É aí que entra a variável se escopo Global. Para manipularmos as variáveis de escopo Global teremos que usar as seguintes funções da API:

Para executar o Lock nas Global

GlbLock() 

Para Declarar uma Variável e/ou Atribuir um novo valor

PutGlbValue(  cVar  , cValue ) 

Para liberar o Lock nas Global

GlbUnlock() 

Para obter os valores das Global.

GetGlbValue( cVar )   

Um pequeno fragmanto do código u_sudoku.prg modificado para exemplificar o uso da variável de escopo Global

Tento obter a exclusividade na pilha variáveis de escopo Global

While !( GlbLock() )
Sleep(5)
End While

Se a variavel bStartSudoku não estiver declarada, declaro-a, setanto seu valor Inicial

PutGlbValue( "bStartSudoku" , "1" )

Se a variavel cSudokuTime não estiver declarada, declaro-a, setanto seu valor Inicial

PutGlbValue( "cSudokuTime" , "00:00:00" )

Libero o Lock na pilha de variáveis de escopo Global

GlbUnlock()

Executo StartJob para a criação de uma nova Thread

StartJob( "U_SudokuExec" , GetEnvServer() , .F. , "SudokuTime" , { Time() , 1 } )

Tanto bStartSudoku quanto cSudokuTime poderão ser lidas através de GetGlbValue(cVar)   e modificadas, utilizando  PutGlbValue(cVar,cVal), por qualquer Thread criada à partir de sua declaração e terão validade até que a última Thread seja liberada.

9 comentários:

  1. Saudações Naldo,

    Sobre variaveis de escopo STATIC, tenho um arquivo fisico que contem abaixo dos INCLUDES variaveis estaticas declaradas, por exemplo:

    #include 'Protheus.ch'

    STATIC xBarrType := Iif(IsSrvUnix(),'/','\')

    .
    .
    .
    Varias U_Functions => (UFn)
    .
    .
    .
    Minha duvida é, se ao chamar uma UFn nesse fonte
    a variavel STATIC sera iniciada também?

    Abs,
    Sergio

    ResponderExcluir
  2. Cara a função _SetOwnerPrvt() é a maior sacada com certeza... eu sempre me ferrava nos pontos de entrada... com ela acabou as gambiarras !!! vlww

    ResponderExcluir
  3. Pessoal, boa noite.

    Eu estou usando esse conceito para resolver um problema aqui, mas travei na seguinte situação:

    Consigo carregar uma tabela em uma Thread e ler em outra ? Ou seja:

    Na função A, inicio a função B como JOB. Nessa função B, eu carrego uma tabela com o TcGenQry, no alias "view1".
    Gostaria de ler os campos dessa "view1" na função A.

    É possivel?

    ResponderExcluir
    Respostas
    1. Não. Não é possível por causa da forma que threads foram implementadas no sistema. Exceto pelas variáveis do tipo Global, nenhuma outra é compartilhada entre threads. Mas a solução do seu problema é simples: crie uma tabela temporária a partir da sua view e ela poderá ser acessada por qualquer thread.

      Excluir
  4. Vlw Naldo. Brigado mesmo.

    Agora só mais uma forcinha. Sabe qual melhor forma de fazer isso, pensando na performance? Essa rotina em questão eh um relatório, que já leva horas para carregar a view, e a ideia seria não dobrar esse tempo ao jogar em uma tabela temporaria...

    ResponderExcluir
    Respostas
    1. Já tentou extrair os dados (criando uma tabela) via Procedure???

      Excluir
  5. Consegui um resultado muito bacana com as temporárias globais do sql mesmo. Assim que terminar posto o resultado completo aqui.

    ResponderExcluir