Baixe o app para aproveitar ainda mais
Prévia do material em texto
INSTITUTO FEDERAL DO ESPÍRITO SANTO BACHARELADO EM SISTEMA DE INFORMAÇÃO AUGUSTO COELHO JEAN CARLOS PENAS SISTEMAS OPERACIONAIS SERRA 2015 P a g i n a 2 AUGUSTO COELHO JEAN CARLOS PENAS SISTEMAS OPERACIONAIS Trabalho apresentado no curso de Bacharelado em Sistemas De Informação, Na disciplina Sistemas Operacionais no Instituto Federal do Espírito Santo Professor: Flavio Giraldeli SERRA 2015 P a g i n a 3 Resumo O relatório está recheado de diversos conceitos que foram identificados em um algoritmo que trabalha com um conjunto de números naturais aleatórios de 0 a 29999 das quais serão escritos em cada campo de uma matriz N x M tal que N e M fazem parte do conjunto dos números naturais não nulos de modo que a matriz possa ser dividida em pequenos blocos de mesmo tamanho chamadas de macro blocos que durante a execução do algoritmo são varridos por N threads criadas por um processo. Vimos que um processo é nada mais nada menos que uma entidade ativa com a penas um fluxo de execução e que este poderá criar vários fluxos chamados de threads que segundo Silberschatz trazem diversos benefícios como responsividade, compartilhamento de recursos, economia e o proveito das arquiteturas multiprocessadas. Nós mostramos como os programas usufruem da programação paralela por meio da criação de diversas threads e desta forma concluímos como o sistema operacional administra os seus recursos utilizando essa mesma tecnologia só que em níveis mais complexos e também as consequências que ela traz quando usada de forma indiscriminada. Além disto, através de um algoritmo na linguagem C identificamos muitos conceitos visto em sala de aula dentre eles as regiões críticas, técnicas para tratar problemas de sincronismo e conceitos sobre os estados das threads cuja chamadas estão implícitas nas API’s da linguagem que vamos trabalhar e uma série de recursos inclusive estruturas que são compartilhadas entre elas por meio da thread principal. P a g i n a 4 Abstract The report is full of many concepts that were identified in an algorithm that works with a random set of natural numbers 0-29999 of which will be written in each field of an N x M matrix such that N and M are part of the set of numbers natural non-null so that the array can be divided into small blocks of equal size called macro blocks for implementing the algorithm are swept by N threads created by a process. We have seen that a process is nothing more nor less than an active entity with feathers a thread, and that this can create multiple streams called threads that according Silberschatz bring many benefits such as responsiveness, resource sharing, economy and advantage of multiprocessor architectures . We show how the programs enjoy the parallel programming through the creation of several threads and thus conclude how the operating system manages its resources using the same technology but in more complex levels and also the consequences that it brings when used indiscriminately . In addition, through an algorithm in C identified many concepts seen in class among them the critical regions, techniques to address timing issues and concepts about the states of the threads whose calls are implied in the API's of the language that will work and a number of features including structures that are shared between them through the main thread. P a g i n a 5 ILUSTRAÇÕES Tabela 1 - Configurações Básicas Dos Computadores Usados Nos Testes. ............................. 10 Gráfico 1 – Tempo Dos Testes Com Matriz De 1000x1000, Blocos De 5x5. .......................... 11 Gráfico 2 – Gráfico Do Percentual De Ganho do Algoritmo Nos Testes. ................................ 11 Gráfico 3 - Testes Com Matrizes Cujo Macro Blocos São Maiores......................................... 12 Figura 1 - Declaração Das Variáveis Globais do Algoritmo. ................................................... 12 Figura 2 – Protegendo as regiões críticas do Algoritmo. .......................................................... 13 P a g i n a 6 SUMÁRIO INTRODUÇÃO .......................................................................................................................... 7 PROCESSOS .............................................................................................................................. 8 THREADS .................................................................................................................................. 9 QUEBRANDO PARADIGMAS ................................................................................................ 9 SINCRONIZAÇÃO ................................................................................................................. 12 O ALGORITMO ...................................................................................................................... 14 CONCLUSÃO .......................................................................................................................... 15 REFERÊNCIAS BIBLIOGRÁFICAS ..................................................................................... 18 P a g i n a 7 INTRODUÇÃO Atualmente quase todos os sistemas operacionais modernos possuem arquiteturas multiprocessadas, isto é uma consequência de uma mudança de organização do processador, mudança que chamamos hoje de multiprocessamento. Mas as arquiteturas MP também são uma consequência de outro fato, a era da informação com a necessidade de processar dados em grandes escalas que segundo a Universidade Federal De Santa Catarina inclui a necessidade de aumentar funcionalidades, interfaces gráficas, comunicação, carga, números de usuários, tempo de resposta e confiabilidade. Foi graças a uma série de modificações que foram acontecendo nas últimas décadas nos níveis da organização e arquitetura computacional, como o nível lógico digital, micro arquitetura, arquitetura de instruções, sistema operacional, linguagem assembly e linguagem de auto nível que foi possível a implementação dos processadores MP que aconteceu exatamente no primeiro nível (Nível lógico digital), que segundo a Universidade Federal De Campina Grande é o nível em que as portas lógicas são combinadas para formar os registradores e principalmente o processador, este por sua vez é o componente principal e que de fato é o primeiro componente a sofrer as mudanças para tornar a programação paralela possível. O resultado dessas mudanças é o que tornou possível a construção de processadores de múltiplos núcleos que possuem unidades de processamento em um único processador e ainda aqueles com tecnologias Hiperthread, que para cada 1 núcleo físico simula 1 lógico. A programação paralela ou concorrente é um mecanismo que consiste em executar de forma simultânea várias partes de uma mesma aplicação (em alguns contextos paralelismo e concorrência possuem significados distintos). Umas das condições para que o paralelismo utilizando threads funcione já foi satisfeita que são as questões que envolve a arquitetura e a organização do computador, ou seja, já temos um processador que possui tecnologia MP e também bibliotecascom diversas API’s para aplicar o conceito da programação paralela como a POSIX PThreads, a outra condição é que o problema que será modelado tenha possibilidade de ser dividido em partes que possam ser processadas de forma independente. Mas ao tentar modelar esse problema surgem outros desafios como a divisão de atividades que foca áreas do código que pode ou não ser dividido em tarefas separadas ou concorrentes. O equilíbrio, associado a importância de como dosar as atividades para que apresentam esforço P a g i n a 8 computacional semelhante. A divisão de dados que se preocupa como os dados devem ser divididos para execução em núcleos separados. A dependência dos dados, que por sua vez cria a necessidade dos dados serem examinados para determinar as suas relações para que posteriormente possam ser feitas as sincronizações e, finalmente, a correção desses programas que também é um desafio pois possuem diversas linhas de execução e por este motivo a sua depuração se torna mais complexa. PROCESSOS O processo é definido como uma entidade ativa, ou seja o código do programa sendo executado por um fluxo de execução. Durante o seu ciclo de execução ele terá diversos estados como Novo, Pronto, Executando, Esperando, Terminando e Interrompido e ficará migrando entre diversas filas. Cada processo possui por um bloco de controle de processo ou PCB que funciona como um repositório de dados. No PCB são encontradas informações como estado do processo, contador de programa, registradores, informações de escalonamento de CPU, informação de gerência de memória, informação contábil e informação de status de E/S. Quando o estado do processo muda de pronto para executando é criada uma thread principal chamada de thread Pai (Alguns autores chamam de thread mãe). Com apenas uma thread o processo executa apenas uma tarefa por vez. Em todo o seu ciclo de vida o processo migra entre diversas filas e o seu estado variando conforme foi dito na introdução deste tópico. Quando ocorre uma interrupção, o sistema operacional salva o contexto do processo atual que é o status do PCB em um determinado instante e então o SO atende a interrupção e volta para restaurar o contexto salvo do antigo processo para o novo, voltando ao ponto onde foi interrompido e executando as suas tarefas como se nada tivesse acontecido, este procedimento é chamado de troca de contexto e o tempo dessa troca é chamado de overhead (custo). Então os projetistas perceberam que ao criar vários processos para executar tarefas de forma simultânea gerava muito custo no sistema operacional (O custo pode estar atrelado desde o tempo de execução até a quantidade de memória ocupada), então a solução foi migrar para a utilização das threads, pois era mais fácil criar e fazer troca de contexto entre threads do que processos (Silberschartz – Sistemas Operacionais). P a g i n a 9 THREADS A thread, segundo Silberschartz é a unidade básica de utilização de CPU e possui um id exclusivo, um contador de programa que calcula o próximo endereço da instrução que será executada, um conjunto de registradores e uma pilha. Além disto, as estruturas da thread principal como a seção de código, dados e arquivos são compartilhados entre as threads filhas. Depois que as threads passaram a ser implementadas em programas de sistemas, aplicativos e softwares, os projetista de SO’s começaram identificar aspectos positivos da implementação, como a responsividade que se refere a capacidade da aplicação continuar funcionando mesmo que parte dela esteja bloqueada, a economia, pois perceberam que a alocação de recursos utilizando processos gerava muito custo e como as threads compartilhavam recursos do processo que as criou era mais econômico fazer troca de contexto entre elas. O compartilhamento de recursos foi outro ponto chave que tornou as threads a melhor opção na programação paralela, porque por padrão as threads já compartilhavam recursos como seção de código, dados e até mesmo os arquivos pertencentes ao processo que as criou, além disto em um dos artigos da Universidade Federal Do Rio De Janeiro revela que várias threads diferentes podem ser criadas dentro do espaço de endereço do processo que as criou. Devido a essas propriedades, as threads passaram a ser o melhor caminho para tornar os sistemas operacionais mais próximo dos objetivos do usuário e ao mesmo tempo aos de projeto. Mas como toda solução tem o seu grau de complexidade haveria um momento em que a criação indiscriminada de threads causaria uma queda de desempenho nas aplicações, então resolvemos conjecturar esta ideia no próximo tópico. QUEBRANDO PARADIGMAS Se a utilização das threads trouxe tantos benefícios, porque não criar milhões de threads no sistema operacional para melhorar o seu rendimento ainda mais, é claro que existem limitações físicas quanto a isto, mas o maior problema ainda não é este. A Tabela 1 abaixo mostras as configurações básicas de dois computadores para este tópico e por meio de um algoritmo paralelizado vamos explorar alguns aspectos e curiosidades dessa nova P a g i n a 10 tecnologia no processamento de vários blocos dentro de uma matriz de números que variam entre 0 a 29999 contabilizando então a quantidade total de números primos. Tabela 1 - Configurações Básicas Dos Computadores Usados Nos Testes. MOBILE DESKTOP Processador INTEL CORE I5 2.4 GHZ INTEL DUAL CORE 2.6 Tamanho Memória 4 4 A princípio achávamos que quanto maior o número de threads trabalhando em vários blocos, menor seria o tempo do processamento de toda a matriz e consequentemente melhor o ganho, mas não foi o que esperávamos. O tempo tende a diminui consideravelmente nos primeiros testes com N threads maior ou igual a 2, mas a duração do processamento com números de threads na ordem de dezenas e centenas teve uma duração que divergia muito pouco em relação aos testes envolvendo números menores. Neste caso a magnitude dos números foi levado em consideração nesta prova e por este motivo todos os testes do gráfico foram feitos com a mesma matriz e esses dados podem ser vistos no Gráfico 1. O Gráfico 1 revela que o teste que teve menor tempo de duração foi o de 600 threads com 265 segundos, em média o tempo de duração ficou em torno de 298 segundos considerando os testes feitos a partir 2 threads. Depois de calcularmos o coeficiente de variação desses dados apresentados no Gráfico 1 ficamos surpresos, ele ficou em torno 5%, isto quer dizer que a maioria dos testes obteve um tempo de duração com uma amplitude muito pequena e com isto concluímos que ao aumentar indiscriminadamente o número de threads o ganho de tempo é corroído pela troca de contexto das threads criadas, causando o que chamamos de overhead. Uma explicação possível e mais técnica para comprovar essa conjectura estaria baseado no modelo de múltiplas threads que são modelos que descrevem a forma como as threads no nível do usuário são mapeadas para as threads no kernel que segundo a Microsoft, o modelo do Windows seria o de “um para um”. Qual a desvantagem deste modelo? A desvantagem deste modelo é que para cada thread que é criada no nível do usuário, uma thread correspondente no kernel também é criada. Cada vez que uma thread de kernel ou mesmo uma thread de usuário é criada, várias chamadas de sistemas estão sendo feitas e trocas de contextos estão acontecendo e quanto maior for o número de troca de contextos, maior será o tempo que o processador ficará ocioso. P a g i n a 11 Apesar do tempo de duração entre os testes serem bem próximos, o percentual de ganho éconsideravelmente grande. No Gráfico 2, calculamos que o ganho médio foi de 43% em relação ao serial no Core I5, mas ao longo dos testes o ganho varia, pois o coeficiente de variação dos dados desse gráfico foi de 18%, isto quer dizer que houve testes com percentual de ganho baixo como por exemplo o teste com 3 threads que teve 30% e ganhos maiores como o de 600 threads que obteve 59% de ganho. A conclusão desses dados é que até uma certa quantidade de threads o ganho tenderá aumentar e o tempo de execução a diminuir, mas haverá um momento em que o custo para manter várias threads rodando no sistema operacional vai ser tão grande que o tempo de duração tenderá a uma padronização ou seja o tempo de processamento com 4 threads terá uma amplitude muito pequena em relação ao de 500 threads, isto acontece por que cada vez que um conjunto maior threads está sendo criado, várias chamadas de sistemas, dentre elas para efetuar a troca de contextos estão sendo feitas na mesma proporção e isto acaba atrasando o algoritmo no processamento da matriz. Mas o que se espera-se desta tecnologia não é a redução do tempo de execução e sim o usufruto de outras propriedades como a responsividade, compartilhamento de recursos, economia e o uso das propriedades dos processadores de múltiplos núcleos. Gráfico 1 – Tempo Dos Testes Com Matriz De 1000x1000, Blocos De 5x5. Gráfico 2 – Gráfico Do Percentual De Ganho do Algoritmo Nos Testes. P a g i n a 12 Fizemos um outro teste para saber se as threads demoravam mais tempo processando matrizes com a mesma quantidade de blocos, mas com blocos maiores. Esse teste foi com um dual core. Este resultado já era esperado apesar de todas essas matrizes terem a mesma quantidade de blocos, o tempo do dual core com 2 threads é proporcional ao tamanho dos macros blocos. Então concluímos que mantendo a quantidade blocos da matriz e aumentando o tamanho dos macros blocos as threads demoravam mais tempos para processar toda a matriz. SINCRONIZAÇÃO Regiões críticas são regiões compartilhadas por várias unidades de utilização da cpu criadas pelo mesmo processo e por este motivo essas regiões podem receber acessos concorrentes capazes de alterar o resultado final da aplicação. No algoritmo foram declaradas diversas variáveis globais que são as candidatas as regiões críticas, mas apenas duas delas obedecem a definição. A primeira é um vetor de blocos que carrega informações a respeito da posição inicial e a disponibilidade do bloco na matriz. Através desse vetor de blocos é que será feita alocação dos blocos da matriz a cada thread do processo. A outra região receberá a quantidade total de números primos contidos na matriz e será manipulada diretamente pelas threads e por este motivo foi definida como uma seção crítica. Existem várias modelos de soluções para regiões críticas, algumas implementadas em nível de hardware e outras em software, como a primeira possui uma implementação mais complexa do ponto de vista do programador a maioria das soluções acabam sendo implementadas a nível de software. Existem várias 0 100 200 300 400 500 600 700 800 TE M PO (S ) mATRIZES DUAL CORE INTEL Gráfico 3 - Testes Com Matrizes Cujo Macro Blocos São Maiores. Figura 1 - Declaração Das Variáveis Globais do Algoritmo. P a g i n a 13 soluções em níveis softwares e que usufruem de diversas técnicas para o tratar o problema, mas neste caso vamos nos contentar com a exclusão mútua e o mais utilizado e fácil de ser implementado é o Semáforo. O semáforo pode ser implementado de duas formas como contador, podendo ter um domínio de controle mais complexo e como binário podendo variar entre 0 e 1. O semáforo binário também é chamado de mutex, pois são os locks que fornecem a exclusão mútua. Para resolver o problema da região crítica em nosso código usamos a técnica de exclusão mútua, ou seja, quando a thread entrar numa zona crítica, ela ganha o lock da região e nenhuma outra poderá interferir, portanto apenas uma thread poderá acessar essa região, desta forma a exclusão mútua fica garantida. Então, quando a thread pegar o primeiro bloco da matriz, primeiramente, irá verificar se o bloco foi visitado ou não, caso não, a thread ganha o lock dessa seção crítica e ajusta o bloco como visitado e libera a região. Quando essas regiões ficam desprotegida existe uma grande possibilidade do resultado do algoritmo não estar certo. Mas se existe a possiblidade do resultado ser alterado devido algum acesso concorrente a uma região crítica é melhor tratá-la. Tentamos alterar o algoritmo para que o acesso concorrente fosse possível, mas devido a ordem sequencial com que cada thread apanha o bloco e a magnitude dos números primos que eram escolhidos de forma aleatória, foi difícil chegar nessa conclusão, mas se a alocação do bloco a cada thread fosse feita de forma aleatória as condições para que o resultado fosse alterado seriam mais fáceis de serem atingidas. Figura 2 – Protegendo as regiões críticas do Algoritmo. P a g i n a 14 O ALGORITMO O primeiro procedimento que nos preocupamos em executar foi verificar se esse problema podia ser otimizado, depois de modelá-lo na sua forma paralelizada verificamos no código quais são as regiões críticas, depois de definidas procuramos a melhor forma de protegê-las. Alocamos uma quantidade de blocos que receberá os dados essenciais dos blocos contidos na matriz, e na inicialização deles atribuímos as posições iniciais dos blocos da matriz e ajustamos a disponibilidade de todos os blocos para zero para informar as threads, que todos os blocos da matriz estão livres para serem processados, feito isto será possível localizar os blocos dentro da matriz e processá-los. Definimos o que cada thread terá que fazer e os parâmetros essenciais como o id do bloco que ela irá processar quando criada e executada na função “Threads” dentro da função “ExecutaThreads”. Quando a primeira thread for criada e executada, ela verificará a disponibilidade do primeiro bloco, caso tenha sido visitado ela vai para o próximo bloco, caso não, ela bloqueia essa região para que as outras threads não interfira e marca o bloco como visitado e então ela obtém a posição inicial do bloco e através de algum cálculos algébricos ela obtém a posição final e desta forma ela determina as extremidades do bloco na matriz e começa calcular a quantidade de primos no respectivo bloco. Depois de calculados, ela ganha o lock da região da variável “global” para que possa entregar o resultado e libera região e volta a repetir o ciclo novamente em outro bloco. Sabemos que a criação indiscriminada de threads gera overhead no SO, então tivemos a necessidade de trabalhar só com o número de threads informado na entrada pelo usuário, depois que esse número for atingido todas as threads terão que esperar até que a última termine a sua tarefa para que todas elas possam apanhar as próximas tarefas. No final dessa iteração a thread principal ainda terá que esperar pela última thread em execução, então esta espera é feita. Definimos o tempo inicial da sua execução e o tempo final para marcar a sua duração e enfim é feita a impressão da duração e o resultado esperado e então depois que todas threads terminarem a sua tarefa a thread principal termina sua execução. P a g i n a 15 CONCLUSÃO Por meios de artigos, enciclopédias, aulas com profissionais da área e livros de autores como Silberschartz identificamos vários conceitos e conjecturamos alguns. Dentreeles, as causas que obrigaram as tecnologias a se desenvolveram e chegarem ao nível que estão hoje, como o aumento do número de funcionalidades em programas, interfaces gráficas cada vez mais complexas, aumento da comunicação, carga e execução, número de usuários, menor tempo de resposta e maior confiabilidade. Conceituamos também o grau de dependência que existe entre a programação paralela e os níveis da organização e arquitetura que começa com o nível lógico digital, micro arquitetura, arquitetura de instruções, sistema operacional, linguagem assembly e a linguagem de auto nível onde as mudanças acontecem em praticamente em todos níveis, principalmente no primeiro nível. Depois que a organização e arquitetura do ambiente computacional sofreu mudanças para atender as expectativas das arquiteturas MP, as próximas mudanças aconteceriam em nível de software, ou seja um novo campo na área de desenvolvimento viria a tona e é chamado de programação paralela. Com ela diversos desafios teriam que ser encarados na implementação de um software, os mesmos que enfrentamos ao desenvolver o algoritmo deste trabalho como a divisão de atividades, equilíbrio, divisão de dados, dependência de dados e teste e depuração. Na divisão de atividades, nós identificamos no código quais áreas poderiam ser divididas e separadas para serem tarefas paralelas como por exemplo a busca em um bloco da matriz. O equilíbrio das atividades foi associada com uso da instrução join fazendo com que todas as threads tivesse que esperar umas pelas outras para que todas pudessem pegar as próximos blocos em conjunto. Na divisão de dados tivemos como desafio identificar quais seriam as variáveis locais e funções que seriam usadas durante a tarefa efetuada por cada thread. A dependência dos dados seria a análise para identificar partes do código que seriam compartilhadas entre as threads, identificando assim uma relação de pendência entre elas, que no nosso caso foi a variável global que recebia o resultado de números primos de cada thread e o vetor que controlava a alocação de cada bloco e o método de solução para manter a sincronização foi o de exclusão mútua, utilizando semáforos binários que também são P a g i n a 16 chamados de mutex. Quanto aos testes e a depuração do código, não tivemos muita dificuldade mas acreditamos que isto depende da complexidade do modelo do algoritmo, que neste caso não era tão grande, mas em outras situações como na construção de um software com mais funcionalidades, o nível de dificuldade seria notável. Mesclamos alguns conceitos sobre processos quanto aos seus estados, o seu repositório de dados chamado PCB (Bloco De Controle Do Processo) que guarda informações como estado do processo, contador de programa, registradores da CPU, informações de escalonamento de CPU, informações de gerência de memória, informações contábeis e informações de status de E/S. Além disto, o overhead que é um termo muito comum entre projetistas de hardware que neste caso é o tempo em que o processador fica ocioso na troca de contexto de um processo e algo similar acontece também com as chamadas de sistemas em que o SO tem que ficar mudando de modo para alocar e receber recurso solicitados pelas chamadas de sistemas, segundo Silberchartz, o instante em que o SO muda de modo, não importando se é para usuário ou para kernel também é chamado de overhead, por este motivo é que algumas arquiteturas de sistemas operacionais foram modificadas como por exemplo a arquitetura em camadas. Encontramos em livros o porquê do uso das threads e não de processos em execuções concorrentes e chegamos na conclusão de que não se tratava só de terminar as tarefas em menos tempo mas sim de uma série de fatores como economia, responsividade, compartilhamento de recursos e a utilização das arquiteturas multiprocessadas. Quantificamos o ganho que o algoritmo teve no processamento dos blocos que variava entre 30 e 59% no Core I5 Mobile a partir de 2 threads. Uma outra curiosidade relacionado a threads foi apontada pelos testes que foram feitos durante este trabalho, ao aumentarmos o número de threads e mantermos a mesma matriz com os mesmos valores e portanto com as mesma magnitudes, o tempo de execução tendia a um padrão, ou seja, uma hora ele aumentava ou diminuía mas sempre numa amplitude bem pequena então para conjecturar essa teoria usamos a estatística como ferramenta de prova, calculamos média, desvio padrão e finalmente o coeficiente de variação dos valores obtidos com os testes e chegamos a um cv de aproximadamente 5%, isto quer dizer que o tempo de execução dos testes eram homogêneos. A única explicação que poderíamos dar para isto seria o overhead causado pela criação indiscriminada de threads, ou seja, chegaria um momento em que haveria tantas trocas de contexto acontecendo na criação de novas threads que o tempo de execução acabaria sendo penalizado. Se aumentarmos o zoom em cima da API’ responsável pela criação de threads P a g i n a 17 nosso algoritmo, veremos as API’s podem efetuar diversas chamadas de sistemas e cada vez que isto ocorre, o sistema fica mudando de modo usuário para modo kernel e vice versa, o tempo todo e o tempo que essa mudança ocorre também é overhead, pois o processador fica ocioso. P a g i n a 18 REFERÊNCIAS BIBLIOGRÁFICAS Sítios Eletrônicos IFSP. Processos e Threads. Disponível em: http://ctd.ifsp.edu.br/~marcio.andrey/images/Implementacao-processos- threads.pdf Acesso em 19 de Fevereiro, 2015. Training. Multithread. Disponível em: http://www.training.com.br/lpmaia/multithread.pdf Acesso em 19 de Fevereiro, 2015. UFRGS. Threads. Disponível em: http://www.inf.ufrgs.br/~johann/sisop1/aula07.threads.pdf Acesso em 19 de Fevereiro, 2015. UFRJ. Multthread. Disponível em: http://equipe.nce.ufrj.br/gabriel/microarq/Multithread.pdf Acesso em 19 de Fevereiro, 2015. UFRJ. Arquitetura De Computadores II. Disponível em: http://equipe.nce.ufrj.br/gabriel/arqcomp2/Multithread.pdf Acesso em 19 de Fevereiro, 2015. UFSCAR. Processos e Threads. Disponível em: http://www2.dc.ufscar.br/~regina/RecursosSO/cap-02-2004.pdf Acesso em 19 de Fevereiro, 2015. USP. Programação Threads. Disponível em: http://www.ime.usp.br/~danielc/aulas/mac431-ProgramacaoThreads.pdf Acesso em 19 de Fevereiro, 2015. UFSC. Threads. Disponível em: http://www.inf.ufsc.br/~fernando/ine5412/INE5412Cap2-threads.pdf Acesso em 19 de Fevereiro, 2015. UFU. Threads. Disponível em: http://www.inf.ufsc.br/~fernando/ine5412/INE5412Cap2-threads.pdf Acesso em 19 de Fevereiro, 2015. P a g i n a 19 Livros Silberschartz Abraham, Peter Baer Galvin, Greg Gagne. Sistemas Operacionais Com Java. Traduzido por Daniel Vieira. 7ª edição. Rio De Janeiro: Editora Elsevier, ano 2008,673 páginas.
Compartilhar