Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Fernando R. Rannou
Departamento de Ingeniera Informatica
Universidad de Santiago de Chile
May 8, 2014
Concurrencia y sincronizacion
p = malloc(sizeof(struct list));
p->data = item;
p->next = Lista;
Lista = p;
}
Condicion de carrera
Una condici
on de carrera (CC) es cuando el resultado de una operaci
on
depende del orden de ejecucion de dos o mas hebras
Secci
on crtica
Una seccion crtica (SC) es un trozo de codigo, ejecutado por m
ultiples
hebras, en el cual se accede a datos compartidos y, al menos una de las
hebras escribe sobre los datos
p = malloc(sizeof(struct list));
p->data = item;
p->next = Lista;
Lista = p;
}
Exclusion mutua
Exclusi
on mutua
Exclusion mutua (EM) es un requerimiento sobre una SC, que dice que
solo una hebra puede estar ejecutando dicha SC
Atomicidad
Una operaci
on es at
omica si no puede dividirse en partes
Una operaci
on at
omica se ejecuta completamente o no se ejecuta
Una operaci
on at
omica se ejecuta sin ser interrumpida
La ilusion de atomicidad
Sencillo de entender
Habilidad del procesador para hacer context-switch queda limitada
No sirve para multiprocesadores
Funciona para cualquier n
umero de hebras
No esta libre de inanici
on
Solucion por hardware 2, testset()
Ventajas
Aplicable a cualquier n umero de hebras y procesadores que
comparten memoria fsica
Simple y f
acil de verificar
Puede ser usada con varias SC, cada una controlada por su propia
variable
Desventajas
Busy-waiting (o spin-lock) consume tiempo de procesador
Es posible inanici
on
Solucion por software 1
} }
} }
void main() {
turno = 0;
parbegin(T0, T1);
}
Garantiza exclusion m
utua
Solo aplicable a dos procesos
Alternacion estricta de ejecucion
No satisface progreso
Solucion por software 2
void main() {
flag[0] = flag[1] = FALSE;
parbegin(T0, T1);
}
No satisface el requerimiento de EM
Solucion por software 3
El problema mas evidente de la solcuion anterior es que una tarea
puede cambiar su estado despues que otra tarea haya consultado
dicho estado, pero antes que el primero haya entrado a la SC.
Intentemos solucionar este problema cambiando el orden de las dos
instrucciones involucradas
boolean flag[2];
T0 { T1 {
while (TRUE) { while (TRUE) {
flag[0] = TRUE; flag[1] = TRUE;
while (flag[1]); while (flag[0]);
SC(); SC();
flag[0] = FALSE; flag[1] = FALSE;
... ...
} }
} }
void main() {
flag[0] = flag[1] = FALSE;
parbegin(T0, T1);
}
T0 { T1 {
while (true) { while (true) {
... ...
flag[0] = true; flag[1] = true;
turno = 1; turno = 0;
while (flag[1] && turno == 1); while (flag[0] && turno == 0);
SC(); SC();
flag[0] = false; flag[1] = false;
... ...
} }
} }
void main(){
flag[0] = flag[1] = false;
parbegin(T0, T1);
}
int num[N] = 0;
1 T(int i) {
2 num[i] = max(num[0],num[1],...,num[N-1]) + 1;
3 for (p=0, p < N; p++) {
4 while (num[p] != 0 && num[p] < num[i]) ;
5 }
6 SC();
7 num[i] = 0;
8 }
1 T(int i) {
2 choosing[i] = true;
3 num[i] = max(num[0],num[1],...,num[N-1]) + 1;
4 choosing[i] = false;
5 for (p=0, p < N; p++) {
6 while (choosing[p]);
7 while (num[p] != 0 && (num[p],p) < (num[i],i)) ;
8 }
9 SC();
10 num[i] = 0;
11 }
Solucion de SO: Semaforos y Locks
Un sem aforo es una variable especial usada para que dos o mas
procesos se se
nalicen m
utuamente
Un semaforo es una variable entera, pero
Accesada solo por dos operaciones
1 wait(s) decrementa el sem aforo; si el valor resultante es negativo, el
proceso se bloquea; sino, contin
ua su ejecuci on
2 signal(s) incrementa el sem aforo; si el valor resultante es menor o
igual que cero, entonces se despierta un proceso que fue bloqueado
por wait()
Se denominan sem aforos contadores a aquellos semaforos que
pueden tomar valores mayor o menor que cero.
El valor de inicializaci
on de un semaforo contandor depende de la
aplicaci
on.
Primitivas sobre semaforos contadores
struct semaphore {
int count;
queueType queue;
}
void signal();
void wait();
};
A esta clase a
un le falta mucho para ser una implementaci
on correcta.
Semaforo binario o mutex o lock en C
wait(s) signal(s)
{ {
if (s.value == 1) if (s.queue is empty())
s.value = 0; s.value = 1
else { else {
place this process remove a process P
in s.queue from s.queue
block this process place P on the ready list;
} }
} }
semaphore s;
Process(i) {
while (true) {
codigo no critico
...
wait(s)
SC();
signal(s);
..
codigo no critico
}
}
void main() {
s = 1;
parbegin(P(1), P(2),...,P(n));
}
Note que para esta solucion se puede usar semaforos binario o semaforos
contadores
Ejemplo
El problema del productor/consumidor
producer() { consumer() {
while (true) { while (true) {
v = produce(); while (in == out);
while ((in + 1) % n == out); w = b[out];
b[in] = v; out = (out + 1) % n;
in = (in + 1) % n; consume(w);
} }
} }
producer() { comsumer() {
while(true) { wait(delay);
v = produce(); while(true) {
wait(s); wait(s);
put_in_buffer(v); w = take_from_buffer();
n++; n--;
if (n == 1) signal(s);
signal(delay); consume();
signal(s); if (n == 0) wait(delay);
} }
} }
main() {
n = 0;
parbegin(producer, consumer);
}
producer() { comsumer() {
while(true) { int m; //variable loacal
v = produce(); wait(delay);
wait(s); while(true) {
put_in_buffer(v); wait(s);
n++; w = take_from_buffer();
if (n == 1) n--;
signal(delay); m = n;
signal(s); signal(s);
} consume();
if (m == 0) wait(delay);
} }
}
main() {
n = 0;
parbegin(producer, consumer);
}
semaphore s = 1;
semaphore n = 0;
producer() consumer()
{ {
while(true) { while(true) {
v = produce(); wait(n);
wait(s); wait(s);
put_in_buffer(v); w = take_from_buffer();
signal(s); signal(s);
signal(n); consume();
} }
} }
Productor/consumidor, buffer finito, solucion con
semaforos contadores
binary_semaphore s = 1;
semaphore full = 0;
semaphore empty = sizeofbuffer;
producer() consumer()
{ {
while(true) { while(true) {
v = produce(); wait(full);
wait(empty); wait(s);
wait(s); w = take_from_buffer();
put_in_buffer(v); signal(s);
signal(s); signal(empty);
signal(full); consume(w);
} }
} }
main()
{
parbegin(philosopher(0),philosopher(1),
philosopher(2),philosopher(3),
philosopher(4));
}
Deadlock!!!
filosofos: solucion 1
Una posible solucion al problema de deadlock es controlar que solo 4
tenedores puedan ser usados simultaneamente
Semaphore fork[5] = {1}
Semaphore room = 4;
void philosopher(int i) {
while (true) {
think();
Wait(room);
Wait(fork[i]);
Wait(fork[(i+1)mod 5]);
eat();
Signal(fork[(i+1] mod 5);
Signal(fork[i]);
Signal(room);
}
}
main() {
parbegin(philosopher(0),philosopher(1),
philosopher(2),philosopher(3),
philosopher(4));
}
El problema de los lectores/escritores
main() {
readcount = 0;
parbegin(reader, counter);
}
main() {
readcount = writecount = 0; // Escritores tiene prioridad
parbegin(reader, counter);
}
Problema tpico con semaforos
main() {
parbegin(producer, consumer);
}
Productor/consumidor con monitor, a lo C++
monitor M {
char buffer[N];
int in, out;
int count;
cond notfull, notempty;
public:
M() : in(0), out(0), count(0) {}
void append(char x);
char take();
};
main() {
parbegin(philosopher(0), philosopher(1), philosopher(2), philosopher(3), philosopher(4))
}
void philosopher(int i) {
while (true) {
think();
P.get_forks();
eat();
P.put_forks();
}
}
Filosofos con monitor
Monitor P
state[5] of {THINKING, HUNGRY, EATING};
cond self[5];
public:
P() : state[0](THINKING), state[1](THINKING), state[2](THINKING),
state[3](THINKING), state[4](THINKING) {}
void get_forks(int i) {
state[i] = HUNGRY;
test(i);
if (state[i] != EATING then Wait(self[i]);
}
void put_forks(int i) {
state[i] = THINKING;
test((i+4) mod 5);
test((i+1) mod 5);
}
private:
void test(int i) {
if (state[i+4 mod 5] != EATING && state[i] == HUNGRY && state[i+1 mod 5] != EATING) {
state[i] = EATING;
Signal(self[i]);
}
}
Paso de mensajes
Direccionamiento directo
La primitiva send() incluye el identificador del proceso destino
La primitiva receive() podra (o no) saber quien le enva el
mensaje y tomaria la informacion del mensaje fuente
Asi, podra enviar una confirmaci
on de recepci
on del mesaje
Direccionamiento indirecto
Los mensajes no se envia de un proceso a otro, sino a una estructura
de datos compartida consistente de colas que almacenan
temporalmente mensajes
Mailboxes
Asi, un proceso deposita un mensaje en el mailbox y otro lo recupera
Permite tener mas modelos de comuniucacion, como se presenta a
continuaci
on
Direccionamiento
Exclusion mutua con paso de mensajes
Asumimos blocking receive() y nonbloquing send()
Se crea un mailbox mutex
Para entrar a la SC, un proceso debe recibir un mensaje
Cuando sale de la SC pone devuelta el mensaje en el mailbox
Asumimos que si dos o mas procesos hacen un receive() al mismo
tiempo, solo uno de ellos lo recibe
int n = ... // number of processes
void P(int i){
message msg;
while (true) {
receive(mutex, msg);
SC();
send(mutex, msg);
}
}
void main() {
mailbox mutex = new mailbox();
send(mutex, NULL);
parbegin{P(1), P(2), ..., P(n));
}
Productor/Consumidor con paso de mensajes
void main() {
mailbox mayproduce = new mailbox();
mailbox mayconsume = new mailbox();
for (int i=1; i <= size; i++)
send(mayproduce, null);
parbegin(produce, consumer);
}
Sincronizacion con hebras POSIX
void main() {
buffer_t *buffer;
pthread_t pro, con;
buffer = bufferInit();
pthread_mutex_init(&buffer->mutex, NULL);
pthread_cond_init(&buffer->notFull, NULL);
pthread_cond_init(&buffer->notEmpty, NULL);
while (true) {
v = produce();
pthread_mutex_lock (&buffer->mutex);
while (buffer->full) {
pthread_cond_wait (&buffer->notFull, &buffer->mutex);
}
put_in_buffer(buffer, v);
pthread_cond_signal(&buffer->notEmpty);
pthread_mutex_unlock(&buffer->mutex);
}
}
El Consumidor
while (true) {
pthread_mutex_lock (&buffer->mutex);
while (buffer->empty) {
pthread_cond_wait (&buffer->notEmpty, &buffer->mutex);
}
v = take_from_buffer(buffer);
pthread_cond_signal(&buffer->notFull);
pthread_mutex_unlock(&buffer->mutex);
}
}
Barreras para hebras
typedef struct {
int maxcnt; // numero de hebras participando en la barrera
struct _sb sb[2];
struct _sb *sbp;
} barrier_t;
Inicializacion de barrera
bp->maxcnt = count;
bp->sbp = &bp->sb[0];
pthread_mutex_lock(&sbp->mutex);
if (sbp->runners == 1) {
if (bp->maxcnt != 1) {
sbp->runners = bp->maxcnt;
bp->sbp = (bp->sbp == &bp->sb[0])? &bp->sb[1] : &bp->sb[0];
pthread_cond_broadcast(&sbp->cond);
}
} else {
sbp->runners--;
while (sbp->runners != bp->maxcnt)
pthread_cond_wait(&sbp->cond, &sbp->mutex);
}
pthread_mutex_unlock(&sbp->wait_mutex);
}
Uso de barreras
void *proceso(void *arg) {
int *tidptr, tid;
tidptr = (int *) arg; tid = *tidptr;
printf("hola justo antes de la barrera%d\n", tid);
barrier_wait(&barrera);
}
barrier_init(&barrera, 10);
for (i=0; i < 10; i++){
taskids[i] = (int *) malloc(sizeof(int));
*taskids[i] = i;
pthread_create(&hebra[i], NULL, &proceso, taskids[i]);
}
barrier_destroy(&barrera);
}
Semaforos POSIX
POSIX define semaforos con nombre y semaforos sin nombre.
semaforos con nombre: Para crear un semaforo con nombre
invocamos la funcion:
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, mode_t mode ,
void *mmap(void *addr, size_t length, int prot, int flags, int fd,
off_t offset);
addr es la direcci
on virtual del proceso donde el descriptor fd
debiera ser mapeado. Lo usal es especificar NULL y dejar al SO
decidir d
onde mapearlo
offset es el desplazamiento, a partir del inicio del descriptor, donde
comienza el mapeo
length es el largo (en bytes) del segmento a mapear
prot especifica flags de proteccion del segmento. Generalmente
usamos PROT READ | PROT WRITE para acceso de lectura y escritura
flags especifica la visibilidad de los cambios al segmento
1 MAP SHARED: los cambios se ven en todos los procesos
2 MAP PRIVATE: los cambios son s
olo visibles al proceso que los realiza
3 MAP FIXED: mejor no usar
mmap() retorna la direcci
on donde se mape
o el segmento
Ejemplo completo productor.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
int main() {
int ch;
int shmid;
key_t key;
int *shm,*s;
sem_t *mutex;
s = shm;
for(ch=0;ch<=2700;ch++){
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
exit(0);
cliente.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int ch;
int shmid;
key_t key;
int *shm,*s;
sem_t *mutex;
shm = shmat(shmid,NULL,0);
s = shm;
for(s=shm;*s!=2700 s++) {
sem_wait(mutex);
printf("%d\n",*s);
sem_post(mutex);
}
*shm = -1;
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}