Les processus légers : threads. Système L3, 2014-2015 1/31



Documents pareils
Cours de Systèmes d Exploitation

Problèmes liés à la concurrence

03/04/2007. Tâche 1 Tâche 2 Tâche 3. Système Unix. Time sharing

INTRODUCTION À LA PROGRAMMATION CONCURRENTE

Introduction à la programmation concurrente

4. Outils pour la synchronisation F. Boyer, Laboratoire Lig

Cours Programmation Système

Exclusion Mutuelle. Arnaud Labourel Courriel : arnaud.labourel@lif.univ-mrs.fr. Université de Provence. 9 février 2011

J2SE Threads, 1ère partie Principe Cycle de vie Création Synchronisation

Cours d Algorithmique-Programmation 2 e partie (IAP2): programmation 24 octobre 2007impérative 1 / 44 et. structures de données simples

École Polytechnique de Montréal. Département de Génie Informatique et Génie Logiciel. Cours INF2610. Contrôle périodique.

INTRODUCTION AUX SYSTEMES D EXPLOITATION. TD2 Exclusion mutuelle / Sémaphores

1/24. I passer d un problème exprimé en français à la réalisation d un. I expressions arithmétiques. I structures de contrôle (tests, boucles)

IN Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

Cours 2: Exclusion Mutuelle entre processus (lourds, ou légers -- threads)

Algorithmique et Programmation, IMA

Threads. Threads. USTL routier 1

Introduction aux Systèmes et aux Réseaux

L exclusion mutuelle distribuée

4. Outils pour la synchronisation F. Boyer, Laboratoire Sardes

Programmation Système (en C sous linux) Rémy Malgouyres LIMOS UMR 6158, IUT département info Université Clermont 1, B.P.

On appelle variable condition une var qui peut être testée et

Info0604 Programmation multi-threadée. Cours 5. Programmation multi-threadée en Java

Exceptions. 1 Entrées/sorties. Objectif. Manipuler les exceptions ;

Un ordonnanceur stupide

Gestion des processus

Java Licence Professionnelle CISII,

Synchro et Threads Java TM

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

Introduction à Java. Matthieu Herrb CNRS-LAAS. Mars

Cours 6 : Tubes anonymes et nommés

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.

Programmation d Applications Concurrentes et Distribuées (INF431)

Les processus. Système L3, /39

Processus! programme. DIMA, Systèmes Centralisés (Ph. Mauran) " Processus = suite d'actions = suite d'états obtenus = trace

Bases de programmation. Cours 5. Structurer les données

Cette application développée en C# va récupérer un certain nombre d informations en ligne fournies par la ville de Paris :

Ordonnancement temps réel

Les structures. Chapitre 3

Introduction au langage C

TD3: tableaux avancées, première classe et chaînes

TD2/TME2 : Ordonnanceur et Threads (POSIX et fair)

Introduction : les processus. Introduction : les threads. Plan

Cours 1 : La compilation

Projet gestion d'objets dupliqués

Cours intensif Java. 1er cours: de C à Java. Enrica DUCHI LIAFA, Paris 7. Septembre Enrica.Duchi@liafa.jussieu.fr

Conception des systèmes répartis

Programmation système en C/C++

Programmation système de commandes en C

Métriques de performance pour les algorithmes et programmes parallèles

DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51

Introduction aux systèmes temps réel. Iulian Ober IRIT

OS Réseaux et Programmation Système - C5

Cours de Génie Logiciel

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if

Chapitre 2. Les processus. 2.1 Introduction. 2.2 les différents états d un processus

Cours de Base de Données Cours n.12

DAns un système d exploitation multiprogrammé en temps partagé, plusieurs

OCL - Object Constraint Language

Premiers Pas en Programmation Objet : les Classes et les Objets

#include <stdio.h> #include <stdlib.h> struct cell { int clef; struct cell *suiv; };

ENSP Strasbourg (Edition ) Les Systèmes Temps Réels - Ch. DOIGNON. Chapitre 3. Mise en œuvre : signaux, gestion du temps et multi-activités

Cours d Algorithmique et de Langage C v 3.0

INITIATION AU LANGAGE JAVA

Chapitre 4 : Outils de communication centralisés entre processus

Introduction à la Programmation Parallèle: MPI

LOG4430 : Architecture et conception avancée

Exercices INF5171 : série #3 (Automne 2012)

Programmer en JAVA. par Tama

Algorithmes et Programmes. Introduction à l informatiquel. Cycle de vie d'un programme (d'un logiciel) Cycle de vie d'un programme (d'un logiciel)


Programme Compte bancaire (code)

Argument-fetching dataflow machine de G.R. Gao et J.B. Dennis (McGill, 1988) = machine dataflow sans flux de données

Sixième partie. Programmation multi-activités Java & Posix Threads. Généralités Java Threads POSIX Threads Autres approches

Derrière toi Une machine virtuelle!

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

Le prototype de la fonction main()

1 Mesure de la performance d un système temps réel : la gigue

Conventions d écriture et outils de mise au point

as Architecture des Systèmes d Information

Les diagrammes de modélisation

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation :

ACTIVITÉ DE PROGRAMMATION

Programmation C++ (débutant)/instructions for, while et do...while

NIVEAU D'INTERVENTION DE LA PROGRAMMATION CONCURRENTE

Les transactions 1/46. I même en cas de panne logicielle ou matérielle. I Concept de transaction. I Gestion de la concurrence : les solutions

Les structures de données. Rajae El Ouazzani

Cours 1 : Qu est-ce que la programmation?

Le langage C. Séance n 4

Introduction à MATLAB R

3. SPÉCIFICATIONS DU LOGICIEL. de l'expression des besoins à la conception. Spécifications fonctionnelles Analyse fonctionnelle et méthodes

STS SE. FreeRTOS. Programmation réseau WIFI. Programmation réseau. Socket Tcp. FlyPort smart Wi-Fi module

Auto-évaluation Programmation en Java

Exécutif temps réel Pierre-Yves Duval (cppm)

Présentation du langage et premières fonctions

Algorithmique et programmation : les bases (VBA) Corrigé

EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE

UEO11 COURS/TD 1. nombres entiers et réels codés en mémoire centrale. Caractères alphabétiques et caractères spéciaux.

Transcription:

Les processus légers : threads Système L3, 2014-2015 1/31

Les threads Les threads sont des processus légers exécutés à l intérieur d un processus L exécution des threads est concurrente Il existe toujours au moins un thread : le thread principal La durée de vie d un thread ne peut pas dépasser celle du processus qui l a crée Les threads d un même processus partagent la même mémoire Le coût de création moindre que pour les processus lourds ; échange de (grosses) structures de données sans recopie, par échange de pointeur. Mais cela est risqué! Controler la synchronisation, en utilisant des verrous. Système L3, 2014-2015 2/31

Threads préemptifs vs Threads coopératifs Threads préemptifs Le système peut suspendre l exécution d un thread à tout moment pour exécuter un autre thread Problème du non-déterminisme Threads coopératifs Chaque thread décide quand il peut être suspendu Problème de réactivité (un thread ne rend jamais la main!) Dans ce cours : threads préemptifs Certains langages s appuient sur un modèle de parallélisme coopératif (e.g., ReactiveML). On peut définir des analyses statiques pour garantir l absence de boucle instantanée. Il existe des librairies de threads coopératifs qui miment les threads Posix. E.g., Lwt et Async en OCaml. Système L3, 2014-2015 3/31

Les threads Posix : Pthread La création #include <pthread.h> int pthread_create(pthread_t *pthread, const pthread_attr_t *attr, void *(*fonction)(void *), void *arg); thread : pour récupérer l identité du thread qui est créé attr : attributs du thread. Si attr est NULL, la valeur par défaut est prise fonction : fonction à exécuter en parallèle arg : arguments de la fonction en cas de succès, renvoit NULL ; code d erreur sinon. Identité d un thread pthread_t pthread_self(void); Égalité ente threads int pthread_equal(pthread_t t1, pthread_t t2); Système L3, 2014-2015 4/31

Exemple : gcc thread-0.c -lpthread (thread-0.c) void *f(void *arg) { int boucle; while (1) { printf("je suis la Pthread\n"); for(boucle=0; boucle < 10000000; boucle++); return NULL; int main() { pthread_t tid; int rep, boucle; printf("processus %d lance une Pthread\n", getpid()); if ( 0 == (rep = pthread_create(&tid, NULL, f, NULL)) ) { printf("pthread creee\n"); else { perror("pthread_create"); while (1) { printf("je suis la fonction main\n"); for(boucle=0; boucle < 10000000; boucle++); return 0; Système L3, 2014-2015 5/31

Exemple : gcc thread-1.c -lpthread (thread-1.c) #include <stdio.h> #include <pthread.h> pthread_t tid[3]; char *nom[3] = { "ainee", "cadette", "benjamine" ; void *annexe(void *arg) { int boucle; printf("je suis la Pthread %s d identite %u\n", (char *)arg, (int)pthread_self()); for(boucle=0; boucle < 10000000; boucle++); printf("%s: je suis apres la premiere boucle\n", (char *)arg); for(boucle=0; boucle < 10000000; boucle++); printf("%s: je suis apres la deuxieme boucle\n", (char *)arg); return NULL; int main() { int ind, rep; printf("processus %d lance\n", getpid()); for (ind=0; ind<3; ind++) { if ( 0 == (rep = pthread_create(tid+ind, NULL, annexe, nom[ind])) ) { printf("pthread %d creee\n", ind); else { fprintf(stderr, "%d : ", ind); perror("pthread_create"); sleep(20); return 0; Système L3, 2014-2015 6/31

Exemple (thread-2.c) durée de vie limitée à celle du processus #include <stdio.h> #include <pthread.h> void *immortelle(void *arg) { while (1) { printf("coucou\n"); int main() { pthread_t tid; int rep; if ( 0 == (rep = pthread_create(&tid, NULL, immortelle, NULL)) ) { printf("pthread creee\n"); else { perror("pthread_create"); sleep(1); return 0; Système L3, 2014-2015 7/31

Terminaison (thread-4.c) void *f(void *arg) { exit(1); int main() { pthread_t tid; int rep; if ( 0 == (rep = pthread_create(&tid, NULL, f, NULL)) ) { printf("pthread creee\n"); else { perror("pthread_create"); sleep(5); printf("fin"); return 0; Le message FIN est-il affiché? Système L3, 2014-2015 8/31

Terminaison Terminaison d un thread void pthread_exit(void *value_ptr); Si le thread a l attribut PTHREAD_CREATE_JOINABLE, à sa mort, le thread devient un zombie jusqu à ce qu un autre thread en prenne connaissance Si le thread a l attribut PTHREAD_CREATE_DETACHED, à sa mort, le thread disparaît directement L attribut detachstate il peut être fixé à la création du thread être modifié : int pthread_detach(pthread_t thread); Système L3, 2014-2015 9/31

Terminaison (thread-5.c) void *f(void *arg) { pthread_exit(null); int main() { pthread_t tid; int rep; if ( (rep = pthread_create(&tid, NULL, f, NULL)) == 0) { printf("pthread creee\n"); else { perror("pthread_create"); sleep(5); printf("fin"); return 0; Système L3, 2014-2015 10/31

Synchronisation Attendre la fin d un thread int pthread_join(pthread_t tid, void **value) Appel bloquant jusqu à ce que le thread tid termine (ou soit résilié, cf pthread_cancel) Si value n est pas NULL pointeur vers la valeur de retour du thread (ou, pointeur vers PTHREAD_CANCELED en cas de résiliation) tid ne doit pas être détaché pthread_join revoie 0 en cas de succès (errno n est pas positionné en cas d échec) Système L3, 2014-2015 11/31

Exemple (thread-6.c) pthread_t tid[3]; void *annexe(void *arg) { int boucle; printf("je suis la Pthread %d d identite %d\n", *((int *)arg), (int)pthread_self()); *((int *)arg) = (*((int *)arg) + 1); pthread_exit(arg); int main() { int ind, rep; int *valeur; printf("processus %d lance\n", getpid()); for (ind=0; ind<3; ind++) { int *pi = malloc(sizeof(int)); *pi=ind; if ( 0 == (rep = pthread_create(tid+ind, NULL, annexe, pi)) ) { printf("pthread %d creee\n", ind); else { fprintf(stderr, "%d : ", ind); perror("pthread_create"); for (ind=0; ind<3; ind++) { pthread_join(tid[ind], (void **) &valeur); printf("fin de la Pthread d identite %d et de valeur %d\n", (int)tid[ind], *valeur); return 0; Système L3, 2014-2015 12/31

Mémoire partagée (conc-1.c) int cpt = 0; void *incr(void *arg) { int i; printf("[%d]: adr de i = %p, adr de cpt = %p\n", (int)pthread_self(), &i, &cpt); for(i=0; i < 1000000; i++) { if ( i % 10000 == 0) { printf("[%d]: i = %d\n", (int)pthread_self(), i); cpt++; printf("[%d]: FIN\n", (int)pthread_self()); return NULL; int main() { pthread_t tid[2]; pthread_create(tid, NULL, incr, NULL); pthread_create(tid+1, NULL, incr, NULL); pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); printf("cpt = %d", cpt); Système L3, 2014-2015 13/31

Sections critiques Les accès en lecture et en écriture à des données partagées doivent être limités à un seul thread à la fois : les threads doivent être en exclusion mutuelle Les parties du code qui doivent être accédées par au plus un processus sont des sections critiques Pour que des threads coopèrent correctement et efficacement, ils doivent respecter les conditions suivantes : 1. Deux processus ne peuvent pas être simultanément dans une section critique relative à une ressource commune 2. Aucune hypothèse ne doit être faite sur les vitesses relatives des processus et sur le nombre de processeurs 3. Aucun processus suspendu en dehors en dehors de sa section critique ne doit bloquer les autres 4. Aucun processus ne doit attendre trop longtemps avant de pouvoir entrer dans sa section critique Système L3, 2014-2015 14/31

Exclusion mutuelle par accès atomiques en mémoire La programmation de l exclusion mutuelle est difficile (au moyen de la seule indivisibilité des accès en mémoire) Plusieurs solutions erronées ont été publiées Pour montrer qu une solution est fausse, on construit un scénario avec des entrelacements d opérations qui conduisent à un problème Les preuves de corrections sont difficiles Première solution prouvée correcte publiée par Deckker et Dijkstra en 1966 Peterson à publié une solution plus simple en 1981 Solutions reposant sur de l attente active test répétitifs des variables utilisées pour la synchronisation Système L3, 2014-2015 15/31

Solution 1 (fausse) Variables partagées : isc1, isc2 (vraies si intention d entrer en section critique) Initialement : isc1 = FAUX, isc2 = FAUX Processus 1 Processus 2 while (VRAI) { done isc1 = VRAI; while (isc2) { ; // section critique... isc1 = FAUX; // section restante Scénario fautif? while (VRAI) { done isc2 = VRAI; while (isc1) { ; // section critique... isc2 = FAUX; // section restante Système L3, 2014-2015 16/31

Solution 1 (fausse) Variables partagées : isc1, isc2 (vraies si intention d entrer en section critique) Initialement : isc1 = FAUX, isc2 = FAUX Processus 1 Processus 2 while (VRAI) { done isc1 = VRAI; while (isc2) { ; // section critique... isc1 = FAUX; // section restante while (VRAI) { done isc2 = VRAI; while (isc1) { ; // section critique... isc2 = FAUX; // section restante Scénario fautif : P1 affecte isc1 = VRAI ; P2 affecte isc1 = VRAI ; interblocage Système L3, 2014-2015 17/31

Solution 2 (fausse) Variables partagées : sc1, sc2 (vraies si en section critique) Initialement : sc1 = FAUX, sc2 = FAUX Processus 1 Processus 2 while (VRAI) { done while (sc2) { ; sc1 = VRAI; // section critique... sc1 = FAUX; // section restante Scénario fautif? while (VRAI) { done while (sc1) { ; sc2 = VRAI; // section critique... sc2 = FAUX; // section restante Système L3, 2014-2015 18/31

Solution 2 (fausse) Variables partagées : sc1, sc2 (vraies si en section critique) Initialement : sc1 = FAUX, sc2 = FAUX Processus 1 Processus 2 while (VRAI) { done while (sc2) { ; sc1 = VRAI; // section critique... sc1 = FAUX; // section restante while (VRAI) { done while (sc1) { ; sc2 = VRAI; // section critique... sc2 = FAUX; // section restante Scénario fautif : P1 test sc2 à faux ; P2 test sc1 à faux P1 affecte sc1 = VRAI ; P2 affecte sc1 = VRAI ; P1 et P2 sont en même temps en section critique! Système L3, 2014-2015 19/31

Variables partagées : tour Initialement : tour = 1 Solution 3 (fausse) Processus 1 Processus 2 while (VRAI) { done while (tour!= 1) { ; // section critique... tour = 2; // section restante Scénario fautif? while (VRAI) { done while (tour!= 2) { ; // section critique... tour = 1; // section restante Système L3, 2014-2015 20/31

Variables partagées : tour Initialement : tour = 1 Solution 3 (fausse) Processus 1 Processus 2 while (VRAI) { done while (tour!= 1) { ; // section critique... tour = 2; // section restante while (VRAI) { done while (tour!= 2) { ; // section critique... tour = 1; // section restante Scénario fautif : P1 stoppé hors section critique P2 ne peut pas entrer en section critique Système L3, 2014-2015 21/31

Solution de Peterson (peterson.c) #define N 2 #define VRAI 1 #define FAUX 0 int tour; /* a qui le tour */ int interesse[n] = { FAUX, FAUX ; /* initialise a FAUX */ void entrer_region(int process) { int autre = 1-process; interesse[process] = VRAI; tour = autre; while ( (interesse[autre] == VRAI) && (tour == autre) ) { ; void quitter_region(int process) { interesse[process] = FAUX; Système L3, 2014-2015 22/31

Interlude : comment prouver Peterson? Cas de deux processus : P 1 P 2 en parallèle. Montrer que les deux ne sont jamais dans la section critique in i, où i {1, 2. : P1 = while true do out: y1 := true; tour := 2 req: wait (not y2) or (tour = 1) in: <section critique> exit: y1 := false P2 = while true do out: y2 := true; tour := 1 req: wait (not y1) or (tour = 2) in: <section critique> exit: y2 := false P 1 et P 2 peuvent être représentés sous la forme d un système de transitions. Système L3, 2014-2015 23/31

Système de transitions Représenter chaque programme sous la forme d un système de transitions. La composition parallèle est le produit. Système de transition : M = (S 0, S, R) défini par : S 0 : ensemble (fini) d états initiaux (S 0 S) ; S : ensemble (fini) d états ; R : relation de transition totale (pour tout s, il existe t tq R(s, t)). Un chemin de M de début s 0 est une suite s 0, s 1,..., s k tq R(s i, s i+1 ) pour tout i 0. Le chemin est initialisé si s 0 S 0. Système de transition étiqueté : M = (S 0, S, E, R). E est un ensemble d étiquettes. R S E S est une relation totale : pour tout q S, il existe e E et q S tel que (q, e, q ) R. Notation : q e q lorsque (q, e, q ) R. Représentation explicite = un graphe ; représentation implicite = une formule booléenne. Système L3, 2014-2015 24/31

Représentation implicite Représentation implicite : M = (S 0, V, R). V est un ensemble fini de symboles de variables. Chaque variable x V prend ses valeurs dans un domaine D x fini (e.g., booléen, type énuméré) S 0 est une assertion (formule booléenne) sur V. Ex : (x = 0) (y = 1). R est une assertion sur V V. Lorsque V = {x 1,..., x d, V dénote V = {x 1,..., x d, i.e., la copie primée des variables de V. Ex : (x = 0) (x = 1) (x = 0) (y = 0) (x = 0) (y = 1) Convention : si x ne change pas, implicitement x = x. Système L3, 2014-2015 25/31

Application a Peterson (pour N = 2) Variables : pc 1, pc 2 : {out, req, in, exit ; y 1, y 2 : {false, true ; tour : {1, 2. S : valuation des variables ; S 0 = (pc 1 = out) (pc 2 = out) (y 1 = false) (y 2 = false). R(pc 1, pc 2, y 1, y 2, tour) est la disjonction de : (pc 1 = out) (pc 1 = req) (tour = 2) y 1 (pc 1 = req) y 2 (pc 1 = in) (pc 1 = req) (tour = 1) (pc 1 = in) (pc 1 = in) (pc 1 = exit) (pc 1 = exit) (pc 1 = out) y 1 (pc 2 = out) (pc 2 = req) (tour = 1) y 2 (pc 2 = req) y 1 (pc 2 = in) (pc 2 = req) (tour = 2) (pc 2 = in) (pc 2 = in) (pc 2 = exit) (pc 2 = exit) (pc 2 = out) y 2 Système L3, 2014-2015 26/31

Preuve automatique de l algo. de Peterson Problème à résoudre : la configuration (pc 1 = in) (pc 2 = in) est-elle accessible? L ensemble des configurations (états) du système étant fini, on peut vérifier cette propriété par énumération. Il suffit ici de vérifier que la propriété est vraie dans chaque état accessible. C est le principe du Model checking inventé pas Sifakis, Clarke et Emerson qui leur a valu le Prix Turing en 2007. Dans le cas présent, le système se modélise et se prouve aisément avec l outil Cubicle (http://cubicle.lri.fr). Nous étudierons ces questions dans la deuxième partie du cours. Fin de l interlude Système L3, 2014-2015 27/31

Attente passive : les mutex L attente active n est jamais la bonne solution on préfère endormir un thread plutôt que faire de l attente active on devra alors être capable de le réveiller Les mutex un mutex peut être libre ou verrouillé un thread peut obtenir le verrouillage d un mutex et en devenir propriétaire un mutex ne peut être verrouillé que par un seul thread à la fois toute demande de verrouillage d un mutex déjà verrouillé entraîne le blocage du thread qui fait la demande ou l échec de la demande Système L3, 2014-2015 28/31

Les mutex Un mutex est une variable de type pthread_mutex_t Initialisation d un mutex pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Verrouillage d un mutex prendre un verrou existant ; exécution bloquée jusqu à ce que le verrou soit libéré. int pthread_mutex_lock(pthread_mutex_t *mutex); (tenter de) prendre le verrou mais ne pas rester bloqué. Retourne 0 en cas de succès (le verrou était libre). int pthread_mutex_trylock(pthread_mutex_t *mutex); Déverrouillage d un mutex int pthread_mutex_unlock(pthread_mutex_t *mutex); Cf. Fonctions Thread.create, Thread.lock, Thread.try_lock, Thread.unlock en OCaml. Système L3, 2014-2015 29/31

Exemple (mutex.c) int cpt = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *incr(void *arg) { int i; printf("[%d]: adr de i = %p, adr de cpt = %p\n", (int)pthread_self(), &i, &cpt); for(i=0; i < 1000000; i++) { if ( i % 10000 == 0) { printf("[%d]: i = %d\n", (int)pthread_self(), i); pthread_mutex_lock(&mutex); cpt++; pthread_mutex_unlock(&mutex); printf("[%d]: FIN\n", (int)pthread_self()); return NULL; int main() { pthread_t tid[2]; pthread_create(tid, NULL, incr, NULL); pthread_create(tid+1, NULL, incr, NULL); pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); printf("cpt = %d", cpt); return 0; Système L3, 2014-2015 30/31

Implémentation d un Mutex Un mutex peut s implémenter à partir d une instruction machine atomique test-and-set. atomique signifie que lorsqu un CPU exécute l instruction, celle-ci ne peut être interrompue en cours d exécution par un autre CPU. L instruction testand-set(boolean *lock)- essaie d écrire la valeur 1 à l adresse adr de manière atomique. Si d autre processus essaient d écrire en même temps, ils ne peuvent le faire tant que le premier n a pas terminé. La sémantique correspond à la séquence d instruction exécutée de manière atomique : init = lock; lock = true; return lock Système L3, 2014-2015 31/31