Escolar Documentos
Profissional Documentos
Cultura Documentos
Por lo general, los hilos son proporcionados en la forma de un paquete de hilos. Dicho paquete contiene
operaciones para crear y destruir hilos, así como operaciones con respecto a la sincronización de variables
tales como los mútex y las variables de condición. Existen básicamente dos métodos para implementar un
paquete de hilos. El primer método es la construcción de una biblioteca de hilos que se ejecutan por completo
en el modo de usuario; el segundo es que el kernel esté al tanto de los hilos y los pueda calendarizar.
Una biblioteca de hilos a nivel de usuario tiene algunas ventajas.
Primero, es barato crearla y destruirla. Debido a que toda la administración de los hilos se mantiene en el
espacio de direcciones del usuario, el precio de la creación de un hilo es determinado primordialmente por el
costo de la ubicación de memoria para establecer una pila de hilos. De manera análoga, destruir un hilo
involucra liberar la memoria para la pila, la cual ya no es utilizada. Las dos operaciones son baratas.
Otra ventaja de los hilos a nivel de usuario es que con frecuencia el intercambio de un contexto de hilo puede
realizarse mediante unas cuantas instrucciones. Básicamente, sólo se requiere almacenar los valores de los
registros de la CPU y, posteriormente, recargarla con valores almacenados previamente del hilo al cual se
hace el intercambio. No hay necesidad de modificar los mapas de memoria, el reinicio del TLB, ni hacer un
conteo de la CPU, etc. El intercambio de contextos de hilo se efectúa cuando dos hilos requieren
sincronización, por ejemplo, cuando entramos a una sección de datos compartidos.
Sin embargo, una desventaja importante de los hilos a nivel de usuario es que, al invocar una llamada de
bloqueo de sistema, ésta bloqueará todo el proceso al cual pertenece el hilo, y entonces bloqueará todos los
hilos presentes en dicho proceso. Como ya explicamos, los hilos son particularmente útiles para estructurar
grandes aplicaciones como partes que podemos ejecutar de manera lógica al mismo tiempo. En tal caso, el
bloqueo de E/S pudiera no prevenir a otras partes de ser ejecutadas en la máquina. Para tales aplicaciones, los
hilos a nivel usuario no ayudan.
Por lo general, estos problemas están circunscritos en su mayoría mediante la implementación de hilos dentro
del kernel del sistema operativo. Desafortunadamente, se debe pagar un precio alto: cada operación de un hilo
(creación, eliminación, sincronización, etc.) debe llevarse a cabo por el kernel, lo cual requiere una llamada de
sistema. El intercambio de contextos de hilo puede ser ahora tan costoso como el intercambio de contextos de
proceso. Entonces, como resultado, la mayor parte de los beneficios del uso de hilos en lugar del uso de
procesos desaparece.
Una solución radica en una forma híbrida entre hilos de nivel usuario y nivel kernel, por lo general se le
conoce como procesos de peso ligero (LWP, por sus siglas en inglés). Un LWP se ejecuta en el contexto de
un solo proceso (de peso completo), y puede haber distintos LWP por proceso. Además de contar con LWP,
un sistema ofrece además un paquete de hilos a nivel usuario, lo cual permite a las aplicaciones efectuar las
operaciones necesarias para crear y destruir hilos.
Además, el paquete proporciona los medios para la sincronización de hilos, tales como el mútex y variables
de condición. El asunto importante es que el paquete de hilos está implementado por completo en el espacio
de usuario. En otras palabras, todas las operaciones sobre hilos son procesadas sin la intervención de un
kernel.
Podemos compartir el paquete de hilos mediante múltiples LWP, tal como ilustra la figura 3-2.
Esto significa que cada LWP puede ejecutar su propio hilo (a nivel usuario). Las aplicaciones multihilos se
construyen a partir de la creación de hilos, y por consecuencia se asigna cada hilo a un LWP. Por lo general,
la asignación de un hilo a un LWP está implícita y oculta del programador.
La combinación de (a nivel usuario) hilos y LWP trabaja de la siguiente manera. El paquete de hilos tiene una
rutina simple para calendarizar el siguiente hilo. Al crear un LWP (lo cual se hace por medio de una llamada
de sistema), el LWP cuenta con su propia pila, y tiene la instrucción de ejecutar la rutina de calendarización
en busca de un hilo para ejecución. Si existen varios LWP, entonces cada uno invoca al planificador. La tabla
de hilos, utilizada para seguir la pista del conjunto de hilos actuales, es compartida por los LWP. Al proteger
esta tabla garantizamos que el acceso mutuamente exclusivo se lleve a cabo por medio de los mútex que se
implementan por completo en el espacio de usuario. En otras palabras, la sincronización entre LWP no
requiere soporte alguno del kernel.
Cuando un LWP encuentra un hilo ejecutable, cambia el contexto hacia ese hilo. Mientras tanto, otros LWP
pudieran buscar otros hilos ejecutables. Si un hilo requiere bloquearse mediante un mútex o variable de
condición, lleva a cabo la administración necesaria y, en algún momento, llama a la rutina de planificación.
Cuando otro hilo ejecutable es localizado, se lleva a cabo un intercambio hacia dicho hilo. La belleza de todo
esto es que el LWP que ejecuta el hilo no tiene que estar informado: el intercambio de contexto se implementa
por completo en el espacio de usuario y ante el LWP pasa como un código de programa normal.
Ahora veamos qué sucede cuando un hilo aplica una llamada de bloqueo de sistema. En ese caso, la ejecución
cambia de modo usuario a modo kernel, pero aún así continúa en el contexto del LWP actual. En el punto
donde el actual LWP ya no puede continuar, el sistema operativo pudiera decidir cambiar el contexto hacia
otro LWP, lo cual también implica que un cambio de contexto se regresa al modo usuario. El LWP
seleccionado simplemente continuará en donde se había quedado previamente.
Existen muchas ventajas en el uso de LWP en combinación con el paquete de hilos a nivel del usuario. En
primer lugar, el crear, destruir, y sincronizar hilos es relativamente más barato y no requiere intervención
alguna del kernel. Segundo, en el supuesto de que un proceso no cuente con LWP suficientes, una llamada de
sistema no suspenderá todo el proceso. Tercero, no hay necesidad de que una aplicación sepa algo acerca de
los LWP; todo lo que ve son hilos a nivel del usuario. Cuarto, los LWP pueden utilizarse fácilmente en
ambientes multiproceso, mediante la ejecución de diferentes LWP en diferentes CPU. Se puede ocultar por
completo este multiproceso a la aplicación.
La única desventaja de los procesos ligeros en combinación con hilos de nivel usuario es que aún requieren
crear y destruir LWP, lo cual es tan caro como los hilos a nivel del kernel. Sin embargo, se requiere que la
creación y destrucción de LWP sea sólo ocasional, y por lo general controlado completamente por el sistema
operativo.
Una alternativa, pero similar al método del peso ligero, es hacer uso de las activaciones del calendario
(Anderson y cols., 1991). La diferencia esencial entre las activaciones de calendario y los LWP es que cuando
un hilo bloquea una llamada de sistema, el kernel hace una llamada hacia el paquete de hilos, llamando
efectivamente a la rutina de calendarización para seleccionar el siguiente hilo ejecutable. El mismo
procedimiento se repite cuando el hilo es desbloqueado. La ventaja de este método es que ahorra
administración de los LWP por parte del kernel. Sin embargo, el uso de llamadas es considerado menos
elegante ya que viola la estructura de los sistemas basados en capas, en los cuales solamente son permitidas
las llamadas a la capa inmediata de más bajo nivel.
https://msdn.microsoft.com/es-es/communitydocs/win-
dev/os/manejador-de-procesos-los-threads
http://www.jtech.ua.es/dadm/2011-2012/restringido/android-av/sesion01-apuntes.html
2 Colaboradores
Por Juan Carlos Ruiz Pacheco, Microsoft Senior Technology Evangelist
Network Url
Twitter https://twitter.com/JuanKRuiz
Facebook https://www.facebook.com/JuanKDev
LinkdIn http://www.linkedin.com/in/juankruiz
Blog https://juank.io
Ahora revisaremos un tema muy importante, hasta ahora todo ha girado entorno a lo
que es un proceso de manera cruda, pero las cosas están a punto de cambiar gracias a
los Threads.
Si, es parecido, es la misma filosofia que utiliza el OS para ejecutar varios procesos a la
vez, pero enfocada ejecutar sub procesos de un mismo proceso, lo cual es un poco
diferente ya que por definición los procesos no comparten el espacio de memoria
entre si, mientras que los Threads (o hilos como los quieras llamar) si.
Los Threads son una ampliación del concepto de multitarea, si bien multitarea se
refiere a la capacidad de un sistema para ejecutar varios procesos a la vez, en un
comienzo esto hacia referencia a que más de una aplicación se estuviera ejecutando de
manera concurrente, sin embargo pronto se hizo notoria la necesidad de que una
misma aplicación hiciera varias cosas a la vez. Allí nacieron los Threads.
La memoria de trabajo del proceso (de lo cual hablábamos en la parte2) sigue siendo
asignada por proceso, los thread dentro del proceso comparten todos la misma región
de memoria, el mismo espacio de direcciones.
Cada hilo tiene su propio contexto (estado de ejecución), así que cada vez que que se
suspende un hilo para permitir la ejecución de otro, su contexto es guardado y
restablecido nuevamente solo cuando es su turno de ejecución.
El hilo es solo quien se ejecuta (sin demeritar en absoluto algo tan importante como
esto).
Si bien la impresión del usuario es que se están ejecutando varias cosas al tiempo ya
esta claro que esto no es así pues en la CPU solo se puede ejecutar una cosa a la vez,
lo que esta pasando realmente es que los thread están alternando tiempo de ejecución
de una manera tan rápida que el usuario percibe que se están ejecutando al tiempo.
En este caso las cosas pueden cambiar, si nuestra aplicación tiene dos hilos y nuestra
maquina tiene dos CPU (o core) en efecto cada thread se podría ejecutar en una CPU
diferente, en este caso si se puede habar de ejecución en paralelo, aunque no
necesariamente pues puede darse el caso en que, debido a la necesidad del sistema de
calendarizar threads de otros procesos, ambos thread se ejecuten en la misma CPU en
un momento dado, en ese momento no habría paralelismo.
Que pasa si mi maquina tiene 2 CPU pero mi aplicación esta utilizando más de 2
thread?
Lo que sucederá es que solo dos de esos thread se estarán ejecutando en paralelo en
un momento dado (aunque ya vimos que esto no es necesariamente lo que sucede), y
el sistema operativo alternara la ejecución de dichos thread de tal forma que todos
tengan Quantums asignados, pero solo podrá haber máximo 2 en paralelo.
¿Es cierto que usar threads hará que mi aplicación se ejecute más rápido?
El efecto contrario se evidencia toda vez que trates de ejecutar más threads que las
CPU que tienes, es decir si vas a ejecutar 20 threads y solo tienes 2 CPU en vez de
ganar rendimiento realmente lo que harás será castigarlo puesto que esos thread
estarán compitiendo por el tiempo de CPU, lo cual se traduce en múltiples y
frecuentes cambios de contexto que harán perder el preciado tiempo de CPU en la
lógica necesaria par cambiar de un thread a otro.
Recordemos que cada proceso solo puede acceder a los objetos o áreas de memoria
dentro de su propio espacio de direcciones, entonces Cómo hace un proceso para
acceder a un objeto thread que esta en otro espacio de direcciones ( el del kernel )?
bueno el kernel como tal se encarga de eso asignándole al proceso un manejador al
thread, el kernel mantiene una tabla de que identificadores de recursos tiene asignado
el proceso, así que cuando un proceso quiere acceder a algún objeto del kernel, en
este caso threads, utiliza funciones de la API de Windows que con el identificador del
objeto hacen llamados al kernel los cuales son quienes en ultima instancia manipulan
al objeto en su propio espacio de direcciones del kernel.
Entonces, cada vez que en nuestro proceso utilizamos un thread y queremos modificar
su comportamiento o verificar su información estadística lo que sucede tras
bambalinas es que se hacen llamados al kernel. El kernel proporciona acceso a
funcionalidades que puedes modificar o supervisar el funcionamiento del thread.
Un ejemplo de estos son los thread creados en .Net Framework o en java, todos estos
thread son creados, calendarizados y administrados por el runtime de cada uno de
ellos, el sistema operativo en esencia no sabe nada de ellos. Cada thread del kernel
puede tener tener dentro de si uno o mas thread de usuario. El sistema operativo solo
calendariza threads de kernel.
Entonces, cada vez que en nuestro proceso utilizamos un user thread y queremos
modificar su comportamiento o verificar su información estadística lo que sucede tras
bambalinas es que se hacen llamados a funciones dentro del propio espacio de
direcciones del proceso y este se encarga de hacer el trabajo necesario. En adelante
me referiré a los thread de usuario como fiber.
El propio programa se encarga de manera automática de calendarizar la ejecución de
cada uno de los fiber en ejecución.
Para solucionar esto, Windows ofrece mecanismos que permiten asociar un fiber a un
thread de kernel nuevo independiente, de tal forma que si por ejemplo el java virtual
machine detecta que uno de sus thread (que son en realidad fibers) queda bloqueado
en espera de un dispositivo, la maquina de java para no bloquear los otros thread del
proceso crea un nuevo thread de sistema operativo y lo asocia con ese fiber para que
el thread principal (donde corren los demas fiber) no quede bloqueado.
Diferencias
Dado lo que he explicado anteriormente podemos contemplar los siguientes aspectos:
Los thread de usuario (fibers) son mucho más eficientes en escenarios con varios
thread que los thread del kernel. Principalmente por dos razones:
Los thread de usuario bloquean a todos los thread del proceso cuando estos están
bloqueados a espera de una llamada al kernel o a un dispositivo de IO, lo cual hace
que se pierda la funcionalidad de procesamiento paralelo.
Algunos sistemas operativos como es el caso de Windows, proveen funcionalidades
para convertir fiber a kernel thread y viceversa, lo cual facilita dar solución a estos
escenarios de bloqueo.
En términos generales es mucho más recomendable trabajar con Kernel Threads que
con Fibers, dada su mayor complejidad los fibers pueden traer más problemas de lo
que solucionan. Sin embargo hay escenarios donde la implementación de fibers es
muy recomendable y de hecho casi un deber como es en los siguientes casos:
En la programación de dispositivos móviles se suele dar el caso de tener que ejecutar una
operación en segundo plano para no entorpecer el uso de la aplicación, produciendo
incómodas esperas. Algunas operaciones, como la descarga de un archivo grande, son
lentas de forma evidente. Otras operaciones que pueden parecer rápidas, a veces también
resultan lentas, si dependen del tamaño de un archivo o de algún factor externo como red.
Los dispositivos continuamente pierden calidad de la señal o pueden cambiar de Wifi a 3G
sin preguntarnos, y perder conexiones o demorarlas durante el proceso. Los hilos también
sirven para ejecutar simultáneamente tareas o para operaciones que se ejecutan con una
periodicidad temporal determinada.
En cuanto a la interfaz gráfica, los hilos son fundamentales para una interacción fluida con
el usuario. Si una aplicación realiza una operación lenta en el mismo hilo de ejecución de la
interfaz gráfica, el lapso de tiempo que dure la conexión, la interfaz gráfica dejará de
responder. Este efecto es indeseable ya que el usuario no lo va a comprender, ni aunque la
operación dure sólo un segundo. Es más, si la congelación dura más de dos segundos, es
muy probable que el sistema operativo muestre el diálogo ANR, "Application not
responding", invitando al usuario a matar la aplicaicón:
Para evitar esto hay que crear otro hilo (Thread) de ejecución que realice la operación
lenta.
Creación y ejecución
Un hilo o Thread es un objeto con un método run(). Hay dos formas de crearlos. Una es
por herencia a partir de Thread y la otra es implementando la interfaz Runnable, que nos
obliga a implementar un método run().
1 @Override
2 public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
3
4 setContentView(R.layout.main);
5 new Hilo1(getApplicationContext());
}
6
7
public class Hilo1 extends Thread {
8 Context context;
9 public Hilo2Thread(Context context){
10 this.context = context;
11 this.start();
12 }
@Override
13
public void run() {
14 while(condicion_de_ejecucion){
15 //Realizar operaciones
16 //...
17 try {
18 // Dejar libre la CPU durante
// unos milisegundos
19
Thread.sleep(100);
20 } catch (InterruptedException e) {
21 return;
22 }
23 }
24 }
}
25
26
27
28
En el anterior ejemplo también se ha ejecutado el hilo desde su propio constructor, de
manera que la creación del objeto Hilo1 ha sido suficiente para ejecutarlo.
New: El primer estado de un hilo recién creado. Permanece en este estado hasta que el hilo
es ejecutado.
Runnable: Una vez ejecutado pasa a este estado, durante el cuál ejecuta su tarea.
Not runnable: Estado que permite al hilo desocupar la CPU en espera a que otro hilo
termine o le notifique que puede continuar, o bien a que termine un proceso de E/S, o bien
a que termine una espera provocada por la función Thread.sleep(100);. Tras ello
volverá al estado Runnable.
Dead: Pasa a este estado una vez finalizado el método run().
El siguiente diagrama resume las transiciones entre los estados del ciclo de vida de un hilo.
Es muy importante asegurar que un hilo saldrá de la función run() cuando sea necesario,
para que pase al estado dead. Por ejemplo, si el hilo ejecuta un bucle while, establecer
a true una variable booleana como condición de que se siga ejecutando. En el momento
que deba terminar su ejecución es suficiente con poner esa variable a false y esperar a
que el hilo de ejecución llegue a la comprobación del bucle while.
No hay un método stop() (está deprecated, por tanto no hay que usarlo), en su lugar el
programador debe programar el mecanismo que termine de forma interna la ejecución del
hilo. Sin embargo si un hilo se encuentra en estado Not Runnable, no podrá hacer
el return de su función run() hasta que no vuelva al estado Runnable. Una forma de
interrumpirlo es usar el método hilo1.interrupt();.
1 hilo1.setDaemon(true);
2 hilo1.start();
Aún así es recomendable salir de la función run() cuando la lógica del programa lo
considere oportuno.
Hilos y actividades
La información que le haga falta a un hilo se puede pasar por parámetro al construir el
objeto, o bien a posteriori con algún método setter del hilo. Otra alternativa más compacta
sería que la propia actividad implemente Runnable.
Nota
Al cerrar una Activity los hilos secundarios no terminan. Al pausarla tampoco se pausan. El
programador debe encargarse de eso.
Por defecto para cerrar una actividad hay que pulsar el botón hacia atrás del dispositivo
Android. Sin embargo si se pulsa la tecla Home o se recibe una llamada de teléfono la
actividad pasa a estado pausado. En este caso habría que pausar los threads en el
método onPause() y reestablecerlos en onResume(), con los mecanismos de
sincronización wait y notifyAll. Otra manera aplicable en ciertos casos sería terminar
los hilos y volver a crearlos al salir del estado de pausa de la actividad.
Prioridades
Cada hilo tiene asociada una prioridad que se puede cambiar con el método:
1 hilo1.setPriority(prioridad)
Sincronización de hilos
Cuando varios hilos pueden acceder simultáneamente a algún dato, hay que asegurarse de
que no lo hagan simultáneamente en el caso de estar modificándolo. Para ello utilizaremos
el modificador synchronized sobre métodos:
En el acceso a datos compartidos se ha visto cómo hacer que los demás hilos esperen a
que otro salga de una sección crítica. Java también proporciona en la
clase Thread métodos que permiten bloquear un hilo de manera explícita y desbloquearlo
cuando sea necesario, son los métodos wait() y notify() (o notifyAll()).
Para evitar interbloqueos ambos métodos deben ser llamados siempre desde secciones de
código sincronizadas. En el siguiente ejemplo una estructura de datos está preparada para
ser accedida simultáneamente por varios hilos productores y consumidores. Es capaz de
almacenar un único objeto pero si un consumidor accede y la estructura no contiene ningún
objeto, dicho consumidor queda bloqueado hasta que algún productor introduzca un objeto.
Al introducirlo, desbloqueará de manera explícita al hilo consumidor. Durante dicho bloqueo
la CPU está completamente libre del hilo consumidor.
Igual que para detener un hilo suele ser conveniente tener una variable booleana que
indique esta intención, en el siguiente ejemplo se añade una variable paused para indicar
que el hilo debe ser bloqueado. La llamada a wait() bloquea el hilo utilizando el lock de un
objeto pauseLock. Cuando se debe reanudar el hilo, se realiza una llamada
a notifyAll(). Ambas llamadas están en una secciónsynchronized.
Android cuenta con un framework de mensajería y concurrencia que está basado en las
clases Thread, Looper, Message, MessageQueue y Handler. Por conveniencia existe
también la clase AsyncTask, para usos comunes de actualización de la UI. Un Looper se
utiliza para ejecutar un bucle de comunicación por mensajes en determinado hilo. La
interacción con el Looper se realiza a través de la clase Handler, que permite enviar y
procesar un Message y objetos que implementen Runnable, asociados a una
cola MessageQueue.
Mediante el uso de Handler se pueden encolar una acciones que que deben ser realizadas
en otro hilo de ejecución distinto. Esto permite programar acciones que deben ser
ejecutadas en el futuro, en un orden determinado.
En el siguiente ejemplo se realiza una carga lenta de datos en otro hilo, al tiempo que se
muestra una splash screen para indicar que se están cargando los datos. Una vez finalizada
la tarea se envía un mensaje vacío. Este mensaje se captura con un Handler propio y se
realiza el cambio de vistas de la Actividad.
Los pools de hilos permiten no sólo encolar tareas a ejecutar, sino también controlar
cuántas ejecutar simultáneamente. Por ejemplo, si se quieren cargar decenas de imágenes,
pero máximo de dos en dos.
1 queue.size() );
2 pool.getActiveCount() );
pool.getTaskCount() );
3
Una de las principales motivaciones para el uso de hilos es permitir que la interfaz gráfica
funcione de forma fluida mientras se están realizando otras operaciones. Cuando una
operación termina, a menudo hay que reflejar el resultado de la operación en la interfaz de
usuario (UI, de user interface). Otro caso muy común es ir mostrando un progreso de una
operación lenta.
Considérese el siguiente ejemplo en el cuál se descarga una imagen desde una URL y al
finalizar la descarga, hay que mostrarla en un imageView.
1 ImageView imageView =
2 (ImageView)findViewById(R.id.ImageView01);
new Thread(new Runnable() {
3
public void run() {
4 Drawable imagen = descargarImagen("http://...");
5 //Desde aquí NO debo acceder a imageView
6 //imageView.setImageDrawable(imagen)
7 //daría error en ejecución
8 }
}).start();
9
10
Tras cargar la imagen no podemos acceder a la interfaz gráfica porque la GUI de Android
sigue un modelo de hilo único: sólo un hilo puede acceder a ella. Se puede solventar de
varias maneras:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
AsyncTask
El método Activity.runOnUiThread(Runnable) es similar a View.post(Runnable).
Ambos permitirían que el Runnable que pasamos por parámetro se ejecute en el mismo
hilo que la UI.
1 ImageView imageView =
2 (ImageView)findViewById(R.id.ImageView01);
new Thread(new Runnable() {
3
public void run() {
4 Drawable imagen = descargarLaImagen("http://...");
5 imageView.post(new Runnable() {
6 public void run() {
7 imageView.setDrawable(imagen);
8 }
});
9
}
10 }).start();
11
12
Otra manera sería por medio de Handlers, con el mismo fin: que la actualización de la UI se
ejecute en su mismo hilo de ejecución. El Handler se declara en el hilo de la UI (en la
actividad) y de manera implícita se asocia al Looper que contiene el hilo de la UI. Después
en el hilo secundario se realiza un post a dicho Handler.
1 public class Actividad extends Activity {
2 private Handler handler;
private ImageView imageView;
3
4
@Override
5 public void onCreate(Bundle savedInstanceState) {
6 // ... Inicializar views ...
7
8 handler = new Handler();
9
10 Runnable runnable = new Runnable() {
11 @Override
public void run() {
12
Drawable imagen =
13 descargarLaImagen("http://...");
14 handler.post(new Runnable() {
15 @Override
16 public void run() {
17 imageView.setDrawable(imagen);
}
18
});
19 }
20 };
21 new Thread(runnable).start();
22 }
23 }
24
25
26
AsyncTask
Otra manera es utilizar una AsyncTask. Es una clase creada para facilitar el trabajo con
hilos y con interfaz gráfica, y es muy útil para ir mostrando el progreso de una tarea larga,
durante el desarrollo de ésta. Nos facilita la separación entre tarea secundaria e interfaz
gráfica permitiéndonos solicitar un refresco del progreso desde la tarea secundaria, pero
realizarlo en el hilo principal.
1 TextView textView;
2 ImageView[] imageView;
3
public void bajarImagenes(){
4
textView = (TextView)findViewById(R.id.TextView01);
5 imageView[0] = (ImageView)findViewById(R.id.ImageView01);
6 imageView[1] = (ImageView)findViewById(R.id.ImageView02);
7 imageView[2] = (ImageView)findViewById(R.id.ImageView03);
8 imageView[3] = (ImageView)findViewById(R.id.ImageView04);
9
10 new BajarImagenesTask().execute(
"http://a.com/1.png",
11 "http://a.com/2.png",
12 "http://a.com/3.png",
"http://a.com/4.png");
13
}
14 private class BajarImagenesTask
15 extends AsyncTask<String, Integer, List<Drawable>> {
16 @Override
17 protected List<Drawable> doInBackground(String... urls) {
18 ArrayList<Drawable> imagenes =
new ArrayList<Drawable>();
19
for(int i=1;i<urls.length; i++){
20 cargarLaImagen(urls[0]);
21 <strong>publishProgress(i);</strong>
22 return imagenes;
23 }
24
25 @Override
protected void onPreExecute() {
26
super.onPreExecute();
27 textView.setText("Cargando imagenes...");
28 }
29
30 @Override
31 protected void <strong>onProgressUpdate(String... values)</strong> {
32 textView.setText(values[0] +
" imagenes cargadas...");
33
}
34
35 @Override
36 protected void onPostExecute(List<Drawable> result) {
37 for(int i=0; i<result.length; i++){
38 imageView[i].setDrawable(result.getItemAt(i));
39 }
textView.setText("Descarga finalizada");
40
}
41
42 @Override
43 protected void onCancelled() {
44 textView.setText("Cancelada la descarga");
45 }
46 }
47
48
49
50
51
52
53
Nota
La notación (String ... values) indica que hay un número indeterminado de parámetros, y se
accede a ellos con values[0], values[1], ..., etcétera. Forma parte de la sintaxis estándar de
Java.
8.52K 5
Los hilos son el elemento central de un entorno de programación multitarea. Por definición, un
hilo es un contexto de ejecución en un proceso; por lo tanto, cada proceso tiene al menos un
hilo. Multi-threading implica la existencia de contextos de ejecución múltiples, concurrentes
(en sistemas multiprocesador) y, a menudo sincronizados en un proceso.
Los subprocesos tienen su propia identidad (ID de subproceso) y pueden funcionar de forma
independiente. Comparten el espacio de direcciones dentro del proceso y aprovechan los
beneficios de evitar cualquier canal de IPC (comunicación entre procesos) (memoria
compartida, tuberías, etc.) para comunicarse. Los hilos de un proceso pueden comunicarse
directamente entre sí, por ejemplo, los hilos independientes pueden acceder / actualizar una
variable global. Este modelo elimina la sobrecarga potencial de IPC en la que el kernel debería
haber incurrido. Como los hilos están en el mismo espacio de direcciones, un cambio de
contexto de hilo es barato y rápido.
Una aplicación multiproceso utiliza los recursos de manera óptima y es altamente eficiente. En
dicha aplicación, los subprocesos se cargan con diferentes categorías de trabajo, de tal manera
que el sistema se usa de manera óptima. Un hilo puede estar leyendo un archivo del disco y otro
escribiéndolo en un socket. Ambos trabajan en tándem, pero son independientes. Esto mejora la
utilización del sistema y, por lo tanto, el rendimiento.
Algunas preocupaciones
La preocupación más importante con los hilos es la sincronización, especialmente si hay un
recurso compartido, marcado como una sección crítica. Este es un fragmento de código que
accede a un recurso compartido y no debe accederse simultáneamente por más de un hilo. Dado
que cada subproceso se puede ejecutar de forma independiente, el acceso al recurso compartido
no se modera de forma natural, sino que utiliza primitivas de sincronización que incluyen
mutexes (exclusión mutua), semáforos, bloqueos de lectura / escritura, etc.
Los hilos existen en dos espacios de ejecución separados en Linux: en el espacio de usuario y
en el kernel. Los subprocesos del espacio de usuario se crean con la pthreadAPI
de la biblioteca (compatible con POSIX). Estos subprocesos de espacio de usuario se asignan a
hilos de kernel. En Linux, los hilos del kernel son considerados como "procesos livianos". Un
LWP es la unidad de un contexto de ejecución básico. A diferencia de otras variantes de UNIX,
como HP-UX y SunOS, no existe un tratamiento especial para los hilos. Un proceso o un hilo
en Linux se trata como una "tarea", y comparte la misma representación de estructura (lista
de struct task_structs).
#include <stdio.h>
#include <syscall.h>
#include <pthread.h>
int main()
return 0;
kanaujia@ubuntu:~/Desktop$ ps -fL
2 #include <sys/types.h>
3 #include <sys/wait.h>
#include <signal.h>
4
#include <sched.h>
5
#include <stdio.h>
6
#include <fcntl.h>
7
8
// 64kB stack
9
#define STACK 1024*64
10
14 close((int*)argument);
18
int main() {
19
void* stack;
20
pid_t pid;
21 int fd;
22
23 fd = open("/dev/null", O_RDWR);
24 if (fd < 0) {
25 perror("/dev/null");
26 exit(1);
27 }
28
// Allocate the stack
29
stack = malloc(STACK);
30
if (stack == 0) {
31
perror("malloc: could not allocate stack");
32
exit(1);
33
}
34 printf("Creating child thread\n");
35
37 pid = clone(&threadFunction,
42
if (pid == -1) {
43
perror("clone");
44
exit(2);
45 }
46
49 if (pid == -1) {
50 perror("waitpid");
51 exit(3);
52 }
53
// Attempt to write to file should fail, since our thread has
54
// closed the file.
55
if (write(fd, "c", 1) < 0) {
56
printf("Parent:\t child closed our file descriptor\n");
57
}
58
59
// Free the stack
60 free(stack);
61
62 return 0;
63 }
64
65
66
67
68
#include <sched.h>
int clone (int (*fn) (void *), void *child_stack, int flags, void *arg);
El siguiente campo, flagses el más crítico. Le permite elegir los recursos que desea compartir
con el proceso recién creado. Hemos elegido SIGCHLD | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_VM, que se explica a continuación:
El hilo cierra el archivo ( /dev/null) abierto por el padre. Como el padre y este subproceso
comparten la tabla de descriptores de archivos, la operación de cierre del archivo también se
reflejará en el contexto write()primario y fallará una operación de archivo posterior en el
elemento principal. El padre espera hasta que finalice la ejecución del hilo (hasta que reciba
a SIGCHLD). Luego, libera la memoria y regresa.
Compila y ejecuta el código como de costumbre; y debería ser similar a lo que se muestra a
continuación:
$gcc demo.c
$./a.out
Linux proporciona soporte para una infraestructura eficiente, simple y escalable para
subprocesos. Alienta a los programadores a experimentar y desarrollar bibliotecas de hilos
utilizando clone()como componente central.
https://es.slideshare.net/AlejandroCalderonMat/diseo-de-sistemas-operativos-procesos-hilos-y-
planificacin-v2b
Arquitectura Virtualización
Existen diferentes formas mediante las cuales podemos ejecutar la virtualización. En el libro de Smith y Nair
(2005) podemos ver una descripción de tales métodos. Para comprender las diferencias en la virtualización, es
importante darse cuenta de que, por lo general, los sistemas de cómputo ofrecen cuatro tipos de interfaz
distintos, y en cuatro niveles diferentes:
1. Una interfaz entre el hardware y el software, constituida por instrucciones máquina que se pueden invocar
desde cualquier programa.
2. Una interfaz entre el hardware y el software, constituida por instrucciones máquina que se pueden invocar
solamente desde programas privilegiados, tales como los sistemas operativos.
3. Una interfaz que consta de llamadas de sistema como las que ofrece un sistema operativo.
4. Una interfaz que consta de llamadas a bibliotecas, las cuales forman, por lo general, lo que conocemos
como interfaz de programación de aplicaciones (API, por sus siglas en inglés). En muchos casos, las
llamadas de sistema ya mencionadas están ocultas por una API.
En la figura 3-6 mostramos los diferentes tipos de virtualización. La esencia de la virtualización es imitar el
comportamiento de estas interfaces.
La virtualización puede tener lugar en dos formas diferentes. Primero, podemos construir un sistema en
tiempo de ejecución que esencialmente proporcione un conjunto de instrucciones para ser utilizado en la
ejecución de aplicaciones. Las instrucciones se pueden interpretar (como en el caso del ambiente en tiempo de
ejecución de Java JRE, por sus siglas en inglés), pero pudieran ser emuladas también como se hace en
aplicaciones que se ejecutan en Windows sobre plataformas UNIX. Observe que en el caso referido a
continuación, el emulador tendrá que imitar el comportamiento de las llamadas de sistema, las cuales han
mostrado ir más allá de lo trivial.
Este tipo de virtualización nos lleva a lo que Smith y Nair (2005) llaman una máquina virtual de proceso, lo
cual enfatiza que la virtualización se implementa esencialmente solamente para un proceso.
Tal como argumentan Rosenblum y Garfinkel (2005), las VMM serán cada vez más importantes en el
contexto de la confiabilidad y la seguridad para los sistemas (distribuidos). Dado que permiten el aislamiento
de toda una aplicación y de su ambiente, una falla ocasionada por un error o un ataque a la seguridad no
afectará a una máquina en su totalidad. Además, como ya mencionamos, la portabilidad mejora de manera
importante dado que las VMM proporcionan un desacoplamiento posterior entre hardware y software, lo cual
permite mover un ambiente completo desde una máquina a otra.
Maquina Virtual y Modelo Cliente-
Servidor
Máquina virtual
En informática una máquina virtual es un software que emula a una computadora y puede
ejecutar programas como si fuese una computadora real. Este software en un principio fue
definido como “un duplicado eficiente y aislado de una máquina física”. La acepción del
término actualmente incluye a máquinas virtuales que no tienen ninguna equivalencia
directa con ningún hardware real.
Una característica esencial de las máquinas virtuales es que los procesos que ejecutan están
limitados por los recursos y abstracciones proporcionados por ellas. Estos procesos no
pueden escaparse de esta “computadora virtual”.
Uno de los usos domésticos más extendidos de las máquinas virtuales es ejecutar sistemas
operativos para “probarlos”. De esta forma podemos ejecutar un sistema operativo que
queramos probar (GNU/Linux, por ejemplo) desde nuestro sistema operativo habitual (Mac
OS X por ejemplo) sin necesidad de instalarlo directamente en nuestra computadora y sin
miedo a que se desconfigure el sistema operativo primario.
¿Qué es la virtualización?
15 abril, 2014 por J053M4 3ZQU3RR4
El hardware informático x86 actual está diseñado desde sus orígenes para ejecutar
al mismo tiempo un sólo sistema operativo, lo que supone la infrautilización de
gran parte de las máquinas.
Hipervisores
Un hipervisor o monitor de máquina virtual es un código implementado en el
ordenador para establecer una plataforma de control de virtualización.
Se crearon en los años 70, mediante una compleja programación que agrupaba
o consolidaba varias computadoras en un mainframe con el objetivo de reducir
costos. IBM fue uno de los creadores e impulsores de esta tecnología, IBM 7044, el
Sistema de Tiempo Compartido Compatible (CTSS –Compatible
Time SharingSystem). El sistema operativo se llamó Supervisor.
En la década de los 80 se abandonó la virtualización por hipervisor, por no
encontrarle una exacta funcionalidad y fundamentalmente, por la complejidad de
la arquitectura X86 de los ordenadores compatibles (PC).
A principios de los 90 se crearon los primeros hipervisor para PC, relanzando esta
técnica que hoy en día es fundamental en cualquier entorno de sistemas.
El hipervisor constituye una plataforma de virtualización, que se define como
un conjunto de software y hardware que simula la ejecución de equipos o sistemas
operativos distintos a los reales. Esto se consigue ocultando las características
físicas de la plataforma real y proporcionando otra plataforma abstracta y
simulada.
Máquinas virtuales
La virtualización permite ejecutar una o varias máquinas virtuales en una misma
máquina física, donde cada una de las máquinas virtuales comparte los
recursos de ese único ordenador físico entre varios entornos. Las distintas
máquinas virtuales pueden ejecutar sistemas operativos diferentes y varias
aplicaciones en el mismo ordenador físico.
Por lo tanto, podemos definir una máquina virtual como un software que corre en
un entorno de ejecución real, pero embebido en otro entorno físico, que es el que
le proporciona los recursos que administra.
Virtualización de servidores
También llamada consolidación de servidores es una tecnología clave para
mejorar la utilización de los servidores físicos y por lo tanto de cualquier centro de
datos.
Mediante el uso de la tecnología de virtualización de servidores, las organizaciones
mejoran enormemente la utilización de sus servidores actuales pasando de utilizar
un 10% a un 70-80% de su capacidad total de cálculo, haciendo así un uso más
completo de los servidores que adquieren. Paralelamente, la administración se
facilita, se multiplican las posibilidades de medios y se economizan recursos.
Ventajas y desventajas de
la Virtualización
VENTAJAS
Seguridad externa
Aislamiento: El Sistema anfitrión no corre riesgo alguno
Fácil migración
Mayor aprovechamiento de recursos
Migración en vivo
Ahorro: energético, espacio físico, refrigeración, administración
DESVENTAJAS
Muchos sistemas dependen de un solo equipo
No existe un software libre de virtualización consolidado