Concorrência e Paralelismo

Marlon Jerold - Mar 14 - - Dev Community

Certamente você está utilizando seu computador ou celular, ambos estão mostrando essa bela tela que é esse artigo, será que você está escutando música? Talvez um lo-fi? Ou você está com um word rodando em sua máquina, mas o que isso tem a ver com esse artigo?

Quando estamos programando, vemos que existem coisas além do nosso código, e que devemos entender bem como funcionam para tirarmos um melhor proveito de nossos equipamentos, que no caso, são nossos processadores.

O que faz ser possível você está com mais de uma aba aberta em seus celular?

Vimos que antigamente os processadores se comportavam de uma forma que hoje quando olhamos para trás pensávamos, como conseguíamos sobrevier fazendo uma coisa de cada vez.

E isso é tecnologia, vamos lá.

No período de 2006, se tornou possível e normal a utilização de processadores que usam mais de um core, os famosos dual core.

Mas antes de entrarmos nos cores, devemos entender o que são processos...

Quando iniciamos algum programa, é criado um processo, e cada processo tem recursos próprios, como espaço de memória. Entendendo essa base dos processos e sabendo que os processos são independentes, porém não se engane, é possível que eles se comuniquem entre si.

E como contabilizar?

Temos os Escalonamentos. Basicamente nosso sistema operacional, atribui tempo de de CPU para cada processo de forma eficietes.

Vemos que é possível que realizemos os contextos de execução, saber onde inicia determinado processo, quando termina outro e quando estamos usando apenas um núcleo, como antigamente, tínhamos essas ideias de que os programas estavam sendo executados de forma simultânea.

Então percebemos que precisamos entender o que são processos e contextos, mesmo trabalhando apenas com um núcleo.

Sim, os programadores eram fodas, a capacidade de trabalhar em diferentes processos, otimizando para que seja possível esses contextos sendo processados com a falsa ideia que estavam rodando simultaneamente.

Quando os processadores de mais de um núcleo ficou mais acessível, técnicas de programação foi surgindo para que aproveitemos o avanço dos poderosos dual cores.

Pensa que antigamente usávamos um núcleo para nossos processos, mas vemos que se torna necessário programar para esses novos contextos.

Certo, certo.

Pense que você está em uma rua movimentada, você tem seu carro e vai andando tranquilo, nas ruas temos os faróis, os faróis representam os mecanismos de sincronização, porque vem carros de lados que podem colidir caso não tenha esses faróis.

Na programação os carros são os processos e os faróis seria os nodulos que fazem com que o seu trânsito não colida.

Concorrência seria isso, carros não podem bater um no outro e precisam de algo para dar start ou stop nos processos para que eles não colidam.

`
import java.util.concurrent.Semaphore;

class Carro extends Thread {
private Semaphore semaphore;
private String nome;

public Carro(Semaphore semaphore, String nome) {
    this.semaphore = semaphore;
    this.nome = nome;
}

public void run() {
    try {
        System.out.println(nome + " se aproximou do semáforo.");
        semaphore.acquire(); 

        System.out.println(nome + " está atravessando o ponto crítico.");
        Thread.sleep(2000); 

        System.out.println(nome + " atravessou o ponto crítico e saiu.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        semaphore.release(); // Libera o semáforo
    }
}
Enter fullscreen mode Exit fullscreen mode

}

public class Main {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);

    Carro carro1 = new Carro(semaphore, "Carro 1");
    Carro carro2 = new Carro(semaphore, "Carro 2");

    carro1.start();
    carro2.start();
}
Enter fullscreen mode Exit fullscreen mode

}
`

Cada carro é responsável por uma Thread, e cada Thread é independente uma das outras, cada instância que é criada é executada com seu próprio método run(), vemos que mesmo que elas sejam idependentes, cada uma pode compartilhar recursos. Como nesse exemplo.

`
import java.util.concurrent.Semaphore;

class Carro extends Thread {
private Semaphore semaphore;
private String nome;

public Carro(Semaphore semaphore, String nome) {
    this.semaphore = semaphore;
    this.nome = nome;
}

public void run() {
    try {
        System.out.println(nome + " está se aproximando da estrada.");
        semaphore.acquire();

        System.out.println(nome + " está atravessando a estrada.");
        Thread.sleep(2000); 

        System.out.println(nome + " atravessou a estrada e saiu.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        semaphore.release(); // Libera o semáforo
    }
}
Enter fullscreen mode Exit fullscreen mode

}

public class Main {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);

    Carro carro1 = new Carro(semaphore, "Carro 1");
    Carro carro2 = new Carro(semaphore, "Carro 2");

    carro1.start();
    carro2.start();
}
Enter fullscreen mode Exit fullscreen mode

}
`

Quando processadores com mais de um núcleo ficaram mais acessíveis, o veio com tudo para que seja usado de forma mais eficiênte o poder desses núcleos.

Antes tinhamos uma rua com uma faixa, hoje podemos fazer com que a rodovia tenha mais de uma faixas, permitindo que os processos sejam executados de maneiras paralelas, antes tinhamos a ideia de que os processos eram executados de forma simultâneas, porém, só ideologicamente. No contexto de várias faixas podemos dizer que processos entram e sai, passa por uma rua, entra e outra, o outro processo que estava vindo.

Dois carros podem andar um ao lado do outro, tenho um processo que tem o semafóro, porém posso fazer com que o trânsito vá mais rápido fazendo com que os carros andem paralelamente.

Image description

Mais de uma faixa, fazendo com que o trânsito ande mais rápido, permitindo usarmos melhor os poderes dos processadores.

Linguagens como Go é interessante para esses contextos por sua facilidade de implementar, com Java é possível, porém Go foi criado justamente por essa demanda existir na época e facilitar a vida dos desenvolvedores.

. . . . . . .