11497번: 통나무 건너뛰기

남규는 통나무를 세워 놓고 건너뛰기를 좋아한다. 그래서 N개의 통나무를 원형으로 세워 놓고 뛰어놀려고 한다. 남규는 원형으로 인접한 옆 통나무로 건너뛰는데, 이때 각 인접한 통나무의 높이

www.acmicpc.net

 

 

 


 

 

  • 풀이

 

testcase만큼 작업을 하는데 크기가 N인 배열이 원으로 되어있을 때 인접한 값과의 차이의 최대값이 최소가 되는 값을 출력하면 되는 문제입니다.

 

문제는 그리디 방식으로 접근했습니다. 오름차순으로 정렬 후 해당 index 별로 index + 1은 왼쪽, index + 2 오른쪽으로 해준 뒤 차이 값 중 최대값을 초기화 시켜주는 방식으로 구현했습니다.

 

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, M;
	static StringBuilder sb;
	static int[] array;

	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(sb);
	}

	// 데이터
	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);

		int testcase = in.nextInt();
		sb = new StringBuilder();
		
		for(int i = 0; i < testcase; i++) {
			N = in.nextInt();
			array = new int[N];
			for(int j = 0; j < N; j++) {
				array[j] = in.nextInt();
			}
			Arrays.sort(array);
			
			int max = 0;
			for(int j = 0; j < N-2 ;j++) {
				max = Math.max(max, array[j+1] - array[j]);
				max = Math.max(max, array[j+2] - array[j]);
			}
			sb.append(max).append("\n");
		}
	}
}


class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

2636번: 치즈

아래 <그림 1>과 같이 정사각형 칸들로 이루어진 사각형 모양의 판이 있고, 그 위에 얇은 치즈(회색으로 표시된 부분)가 놓여 있다. 판의 가장자리(<그림 1>에서 네모 칸에 X친 부분)에는 치즈가 놓

www.acmicpc.net

 

 

 

 


 

 

 

 

  • 풀이

 

시간 제한은 1초 입니다. 가로, 세로 최대값이 100인 것을 감안해서 보면 빡빡하지 않은 시간으로 문제설명대로 구현하면 된다고 생각했습니다.

 

문제의 구현 방법은 bfs방식을 사용했습니다.

  1. bfs를 통해 (0, 0)에서 0을 타고다니며 0과 인접한 1만 melt라는 queue에 추가시켜주었습니다. (같은 index가 추가되지 않게 배열 하나를 추가로 만들어 중첩을 방지해 주었습니다.)
  2. 현재가 마지막 치즈를 녹이는 작업이 될 수 있으므로 size라는 변수를 만들어 melt의 size로 초기화 시켜주었습니다. (만약, melt의 size가 0일경우 초기화 시켜주기 전 반복문을 종료합니다.)
  3. 위의 bfs가 종료되면 melt에는 현재 시간에 녹을 치즈의 index만 남아있습니다. melt 큐에 저장된 모든 위치의 치즈인 1을 0으로 바꾸는 작업을 합니다.
  4. count변수를 만들어 횟수를 1 증가시켜줍니다.
  5. 위의 1~4 작업을 반복합니다.

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
    static int N, M;
    static StringBuilder sb;
    static int[][] array;
    static int[] dx = {-1,1,0,0};
    static int[] dy = {0,0,-1,1};
    static boolean[][] check;
    
	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(sb);
	}

	// 데이터
	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		N = in.nextInt();
		M = in.nextInt();
		array = new int[N][M];
		check = new boolean[N][M];
		sb = new StringBuilder();
		
		for(int i = 0; i < N; i++) {
			for(int j = 0; j < M; j++) {
				array[i][j] = in.nextInt();
	
			}
		}
		
		bfs();
	}
	
	private static void bfs() {
		Queue<Node> queue = new LinkedList<>();
		Queue<Node> melt = new LinkedList<>();
		boolean[][] visit = new boolean[N][M];
		int a = 0, b = 0, count = 0, size = 0;
		
		while(true) {
			for(int i = 0; i < N; i++)
				Arrays.fill(check[i], false);
			queue.add(new Node(0, 0));
			check[0][0] = true;
			while(!queue.isEmpty()) {
				Node node = queue.poll();
				
				for(int direction = 0; direction < 4; direction++) {
					int r = node.x + dx[direction];
					int c = node.y + dy[direction];
					
					if(r < 0 || c < 0 || r >= N || c >= M) continue;
					if(check[r][c]) continue;
					if(array[r][c] == 1) {
						if(!visit[r][c]) {
							visit[r][c] = true;
							melt.add(new Node(r,c));
						}
						continue;
					}
					
					queue.add(new Node(r, c));
					check[r][c] = true;
				}
			}
			if(melt.size() == 0) break;
			
			size = melt.size();
			while(!melt.isEmpty()) {
				Node node = melt.poll();
				array[node.x][node.y] = 0;
			}
			
			count++;
		}
		sb.append(count).append("\n").append(size);
	}
}

class Node {
	int x;
	int y;
	
	public Node(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

3197번: 백조의 호수

입력의 첫째 줄에는 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1500. 다음 R개의 줄에는 각각 길이 C의 문자열이 하나씩 주어진다. '.'은 물 공간, 'X'는 빙판 공간, 'L'은 백조가 있는 공간으로 나타낸다.

www.acmicpc.net

 

 

 

 


 

 

 

  • 풀이

 

bfs를 사용해서 문제를 해결했습니다.

 

처음에 빙하를 녹이는 과정에서 (0,0)~(R-1,C-1)까지 반복문을 돌며 빙하를 녹이는 코드를 작성하니 시간초과가 발생했습니다.

 

시간을 줄이기위해 다음에 녹을 빙하를 queue에 저장하여 녹여주었습니다. 해당 작업으로도 시간초과가 발생하여 첫번째 'L'이 두번째 'L'로 가는 과정에서의 X를 모두 queue에저장하여 다음 빙하를 녹인 뒤엔 queue에 저장되어있는 X부터 다시 탐색하니 시간초과는 해결되었습니다.

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int R, C, answer;
	static char[][] array;
	static Node one, two;
	static boolean[][] check;
	static Queue<Node> water, queue;
	static int[] dx = {0,0,-1,1};
	static int[] dy = {-1,1,0,0};
	
	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	// 데이터
	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		R = in.nextInt();
		C = in.nextInt();
		answer = 0;
		array = new char[R][C];
		check = new boolean[R][C];
		water = new LinkedList<>();
		queue = new LinkedList<>();
		
		for(int i = 0; i < R; i++) {
			String s = in.nextLine();
			for(int j = 0; j < C; j++) {
				array[i][j] = s.charAt(j);
				if(array[i][j] == 'L') {
					if(one == null)
						one = new Node(i, j);
					else
						two = new Node(i, j);
				}
				if(array[i][j] != 'X') {
					water.add(new Node(i, j));
				}
			}
		}
		queue.add(one);
		check[one.x][one.y] = true;
		while(bfs()) {
			Melt();
			answer++;
		}
	}
	
	private static void Melt() {
		int size = water.size();
		
		for(int i = 0 ; i < size ; i++) {
			Node now = water.poll();
			
			for(int direction = 0 ; direction < 4 ; direction++) {
				int r = now.x + dx[direction];
				int c = now.y + dy[direction];
				
				if(r < 0 || r >= R || c < 0 || c >= C) continue;
				if(array[r][c] != 'X') continue;
				
				array[r][c] = '.';
				water.add(new Node(r, c));
				
			}
		}
	}
	
	private static boolean bfs() {
		Queue<Node> temp = new LinkedList<>();
		
		while(!queue.isEmpty()) {
			Node node = queue.poll();
			if(node.x == two.x && node.y == two.y) return false;
			
			for(int direction = 0; direction < 4; direction++) {
				int r = node.x + dx[direction];
				int c = node.y + dy[direction];
				
				if(r < 0 || r >= R || c < 0 || c >= C) continue;
				if(check[r][c]) continue;
				check[r][c] = true;
				if(array[r][c] == 'X') {
					temp.add(new Node(r, c));
					continue;
				}
				
				queue.add(new Node(r, c));
			}
		}
		queue = temp;
		return true;
	}
}

class Node {
	int x;
	int y;
	
	public Node(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

10159번: 저울

첫 줄에는 물건의 개수 N 이 주어지고, 둘째 줄에는 미리 측정된 물건 쌍의 개수 M이 주어진다. 단, 5 ≤ N ≤ 100 이고, 0 ≤ M ≤ 2,000이다. 다음 M개의 줄에 미리 측정된 비교 결과가 한 줄에 하나씩

www.acmicpc.net

 

 

 

 


 

 

 

  • 풀이

 

자신보다 집적접으로 무게가 낮거나 높은 물건을 통해 간접적으로 낮거나 높은 물건을 모두 찾는 문제입니다.

 

이를 풀기 위한 알고리즘은 모든 정점에서 모든 정점의 최단거리를 구하는 플로이드 와샬을 이용했습니다. 플로이드 와샬은 O(N^3)의 시간복잡도를 가지기 때문에 시간을 잘 고려해야합니다. 현재 문제의 N의 최대값은 100으로 100^3은 1000000으로 1초안에 들어오기에 충분한 연산횟수입니다. (참고로 1초에 1억번 정도의 연산을 한다고 생각하시면 됩니다.)

 

해당 문제를 풀기 위해선 자신보다 무게가 많이 나가는 물건과 무게가 적게 나가는 물건을 구별해야합니다. 이유는 예시를 들어보면 1번의 무게보다 낮은 2번과 2번의 무게보다 낮은 3번이 있다고 가정하면 1>2>3>이 될 것 입니다. 하지만 1번의 무게보다 낮은 2번과 2번의 무게보다 높은 3번이 있다고 가정하면 1>2<3 ???? 이 될 것입니다. 그렇기 때문에 본인보다 무게가 높은 것과 무게가 낮은 것을 구별해야합니다.

 

저의 경우에는 a > b의 경우 array[a][b] = 1, array[b][a] = -1로 초기화 하였습니다.

 

이제 플로이드 와샬 알고리즘을 통해, array[i][k]가 0이 아닌 가정하에 array[i][k] == array[k][j]이라면 array[i][j] = array[i][k]로 초기화하며 자신보다 무게가 낮거나 높은 물건을 확인해주었습니다. 즉, i보다 k가 높으면서 k보다 j가 높다면 i보다 j도 높다는 뜻의 알고리즘입니다. (낮은 방식도 마찬가지)

 

위의 플로이드 와샬 알고리즘이 종료가 되면 현재 무게보다 높거나 낮은 모든 물건을 구해줄 수 있을 것 입니다.

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static int N, M;
	static StringBuilder sb;	
	static int[][] array;
	
	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(sb);
	}

	// 데이터
	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		N = in.nextInt();
		M = in.nextInt();
		sb = new StringBuilder();
		array = new int[N+1][N+1];
		
		for(int i = 0; i < M; i++) {
			int a = in.nextInt();
			int b = in.nextInt();
			
			array[a][b] = 1;
			array[b][a] = -1;
		}
		
		for(int k = 1; k <= N; k++) {
			for(int i = 1; i <= N; i++) {
				for(int j =1; j <= N; j++) {
					if (array[i][k] != 0 && array[i][k] == array[k][j]) {
                        array[i][j] = array[i][k];
                    }
				}
			}
		}
		
		for(int i = 1; i <= N; i++) {
			int count = 0;
			for(int j =1; j <= N; j++) {
				if(i == j) continue;
				if(array[i][j] != 0) continue;
				
				count++;
			}
			sb.append(count).append("\n");
		}
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

 

1351번: 무한 수열

첫째 줄에 3개의 정수 N, P, Q가 주어진다.

www.acmicpc.net

 

 

 


 

 

 

  • 풀이

 

처음엔 재귀형식으로 풀어보려했지만 시간초과가 떴습니다. 

 

다음 dp를 통해 풀어보려 했지만 N의 최대값이 너무 크기때문에 배열을 만들 수 없었습니다. 그래서 Map을 통해 dp배열처럼 사용했습니다. 

 

 

 

  • 코드

 

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Main {
	static long N, answer;
	static int P, Q;
	static Map<Long, Long> map;
	
	public static void main(String[] args) throws Exception {
		SetData();
		System.out.println(answer);
	}

	// 데이터
	private static void SetData() throws Exception {
		InputReader in = new InputReader(System.in);
		
		N = in.nextLong();
		P = in.nextInt();
		Q = in.nextInt();
		answer = 0;
		map = new HashMap<>();
		
		answer = recursion(N);
	}
	
	private static long recursion(long value) {
		if(value == 0) return 1;
		if(map.containsKey(value)) return map.get(value);
		
		map.put(value, recursion(value/P) + recursion(value/Q));
		return map.get(value);
	}
}

class InputReader {
	private final InputStream stream;
	private final byte[] buf = new byte[8192];
	private int curChar, snumChars;

	public InputReader(InputStream st) {
		this.stream = st;
	}

	public int read() {
		if (snumChars == -1)
			throw new InputMismatchException();
		if (curChar >= snumChars) {
			curChar = 0;
			try {
				snumChars = stream.read(buf);
			} catch (IOException e) {
				throw new InputMismatchException();
			}
			if (snumChars <= 0)
				return -1;
		}
		return buf[curChar++];
	}

	public int nextInt() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		int res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public long nextLong() {
		int c = read();
		while (isSpaceChar(c)) {
			c = read();
		}
		int sgn = 1;
		if (c == '-') {
			sgn = -1;
			c = read();
		}
		long res = 0;
		do {
			res *= 10;
			res += c - '0';
			c = read();
		} while (!isSpaceChar(c));
		return res * sgn;
	}

	public int[] nextIntArray(int n) {
		int a[] = new int[n];
		for (int i = 0; i < n; i++) {
			a[i] = nextInt();
		}
		return a;
	}

	public String nextLine() {
		int c = read();
		while (isSpaceChar(c))
			c = read();
		StringBuilder res = new StringBuilder();
		do {
			res.appendCodePoint(c);
			c = read();
		} while (!isEndOfLine(c));
		return res.toString();
	}

	public boolean isSpaceChar(int c) {
		return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
	}

	private boolean isEndOfLine(int c) {
		return c == '\n' || c == '\r' || c == -1;
	}
}

동기와 비동기

 

 

 

동기

  • 직렬적으로 작업을 수행합니다. 즉, 작업은 순차적으로 실행되며 어떤 작업이 수행중이면 다른 작업은 대기하게 됩니다.
  • 서버에서 데이터를 가져와 화면에 표시하는 작업을 수행할 때, 서버에 데이터를 요청하고 응답이 올 때까지 이후의 작업들은 Blocking됩니다.

 

비동기

  • 병렬적으로 작업을 수행합니다. 즉, 작업이 종료되지 않은 상태라도 대기하지 않고 다음 작업을 실행합니다.
  • 서버에서 데이터를 가져와서 화면에 표시하는 작업을 수행할 때, 서버에 데이터를 요청하고 응답이 올 때까지 기다리지 않고(Non-Blocking) 이후의 작업을 수행합니다. 만약, 서버로부터 응답오게되면 이벤트가 발생하고 이벤트 핸들러가 데이터를 가지고 수행할 작업을 연속적으로 수행합니다.

 

 

프로세스 동기화

  • 프로세스는 서로 메세지를 보내고 프로세스 내부에서는 스레드끼리 자원을 공유하면서 동기화에 대한 문제가 항상 발생할 수 있습니다. 즉, 공유된 자원에 여러 프로세스, 여러 스레드가 동시에 접근하면서 문제가 발생합니다.

 

Critical Section(임계 구역)

  • Critical Section은 멀티 프로세스 환경에서 둘 이상의 프로세스가 동시에 접근해서는 안되는 공유 자원의 코드 영역입니다. Critical Section은 시간이 지나면 종료되며 어떤 프로세스가 Critical Section에 접근하기 위해서는 지정된 시간만큼 대기해야 합니다.
  • 이 때, 스레드나 프로세스가 사용권을 보장받기 위해서 세마포어 같은 동기화 메커니즘이 사용됩니다.

 

Critical Section 문제를 해결하기 위한 3가지 조건

  1. Mutual Exclusion(상호 배제)
    • 하나의 프로세스가 임계 구역에 들어가 있으면 다른 프로세스는 임계구역에 들어갈 수 없다.
  2. Progress (진행)
    • 임계 구역에 들어간 프로세스가 없다면 어떤 프로세스가 임계 구역에 들어갈지 선택해야한다.
  3. Bounded Waiting (한정 대기)
    • 기아 상태를 방지하기 위해 이미 한 번 들어간 프로세스는 다음에 들어갈 때 제한을 준다.

 

동기화를 제공하는 방법

  • Lock
    • 하드웨어 기반 해결책으로써 동시에 공유 자원에 접근하는 것을 막기 위해 Critical Section에 진입하는 프로세스는 Lock을 획득하고 Critical Section을 빠져나올 때 Lock을 풀어줌으로써 동시 접근이 불가능하게 합니다.
  • 세마포어
    • 동시에 리소스에 접근할 수 있는 허용 가능한 개수를 가지고있는 counter로 보면 됩니다. 
    • counter의 개수에 따라 1개일 경우 이진세마포어(뮤텍스), 2개 이상일 경우 카운팅 세마포어라고 부릅니다.
  • 세마포어와 뮤텍스의 차이
    • 공유 자원에 대한 접근권한, Lock이라는 키를 한개만 가지고 있는 것은 뮤텍스이고, 키를 여러개 가질 수 있는 것은 세마포어 입니다.

 

교착 상태(Deadlock)

  • 서로 상대방의 작업이 끝나기만을 기다리는 두 개 이상의 작업이 서로 종료되지 않아 작업 진행이 불가능한 상태를 말합니다.

 

교착 상태 발생 조건

  • Mutual Exclusion (상호 배제)
    • 한 번에 한 개의 프로세스만이 공유 자원을 사용할 수 있어야 한다.
  • Hold ans Wait (점유 대기)
    • 프로세스가 자원을 할당한 채로 다른 자원을 기다린다.
  • No Preemption (비선점)
    • 다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없어야 한다.
  • Circular Wait (순환 대기)
    • 공유 자원을 사용하기 위해 대기하는 프로세스들이 원형으로 구성되어 있어 자신에게 할당된 자원을 점유하면서 앞이나 뒤에 있는 프로세스의 자원을 요구해야 한다.
  • 위의 네 가지 조건을 모두 충족시킬 때 교착 상태가 발생할 수 있습니다. 위 네 가지 조건 중 하나라도 충족되지 않으면 교착 상태는 발생하지 않습니다.

 

 

프로세스 스케줄러

스케줄러란 어떤 프로세스에게 자원을 할당할지를 결정하는 운영체제 커널의 코드를 의미합니다. 스케줄러는 역할과 목적에 따라서 단기 스케줄러, 중기 스케줄러, 장기 스케줄러로 구분됩니다.

 

 

단기 스케줄러

  • 단기 스케줄러는 미리 정한 스케줄링 알고리즘에 따라 실행할 프로세스를 선택합니다.
  • CPU와 메모리 사이의 스케줄링을 담당합니다.
  • ready queue에 존재하는 프로세스 중 어떤 프로세스를 실행할지 결정합니다.
  • 프로세스에게 CPU를 할당합니다.
    • ready -> running(dispatch)
  • 프로세스의 상태의 변화 ready -> running -> waiting(block) -> ready

 

장기 스케줄러

  • 메모리는 한정되어 있는데 많은 프로세스들이 한꺼번에 메모리에 올라올 경우, 대용량 메모리(디스크)에 임시로 저장됩니다. 대용량 메모리에 저장되어 있는 프로세스 중 어떤 프로세스에 메모리를 할당하여 ready queue로 보낼지 결정하는 역할을 합니다.
  • 메모리와 디스크 사이의 스케줄링을 담당합니다.
  • 하드 디스크에 있는 프로그램을 메모리에 로드하는 역할을 담당합니다.
  • 메모리에 몇개의 프로그램이 올라갈 것인지 제어합니다.
  • 프로세스의 상태의 변화 new -> ready

 

중기 스케줄러

  • 현 시스템에서 메모리에 너무 많은 프로그램이 동시에 올라가는 것을 조절하는 역할을 합니다.
  • 메모리의 여유 공간을 마련하기 위해서 프로세스를 통째로 디스크로 쫓아냅니다. (swapping)
  • 프로세스의 상태의 변화 Ready -> suspended

 

 

스케줄링 알고리즘

  • CPU는 하나의 프로세스 작업이 끝나면 다음 프로세스 작업을 수행해야 합니다. 이때 어떤 프로세스를 다음에 처리할 지 선택하는 알고리즘을 스케줄링 알고리즘이라고 합니다.
  • 스케줄링 알고리즘에는 비선점 스케줄링 방식과 선점 스케줄링 방식이 있습니다.

 

비선점 스케줄링

  • 프로세스가 CPU를 할당받으면 작업 종료 후 CPU 반환 시간까지 다른 프로세스는 CPU 점유가 불가능한 스케줄링 방식입니다.
  • 모든 프로세스에 대한 요구를 공정하게 처리할 수 있지만 짧은 작업을 수행하는 프로세스가 긴 작업을 하는 프로세스 종료 시까지 대기해야할 수도 있습니다.
  • 알고리즘 : FCFS, SJF, 우선순위 등

 

FCFS(First-Come-First-Served) 알고리즘

  • 먼저 들어온 프로세스가 먼저 처리되는 알고리즘입니다. 흔히 많이 보던 FIFO, Queue와 같습니다.
  • 장점
    • 가장 간단한 스케줄링 알고리즘으로 구현이 쉽습니다.
  • 단점
    • 비선점식 알고리즘이기 때문에 대화식 프로세스에 적합하지 않습니다.
    • 긴 작업을 해야하는 프로세스가 먼저 온 경우 짧은 작업으로 완료되는 프로세스의 대기시간이 길어지게 됩니다.

 

SJF(Shortest Job First)

  • 실행 시간이 가장 짧은 프로세스에게 자원을 할당하는 알고리즘입니다.
  • 장점
    • 실행 시간이 짧은 작업을 먼저 실행하기때문에 평균 대기시간이 다른 알고리즘에 비해 짧아집니다.
  • 단점
    • 짧은 작업들이 계속 생기는 경우 작업 시간이 긴 프로세스가 너무 오래 기다리는 현상이 발생합니다.
    • 짧은 작업에 최우선으로 CPU를 할당하기 때문에 공평하지않습니다.

 

우선순위(비선점)

  • 프로세스마다 우선순위를 부여하여 높은 우선순위를 가진 프로세스에게 먼저 자원을 할당하는 방법입니다.
  • 우선순위 알고리즘은 선점과 비선점이 있습니다.
  • 비선점 우선순위 스케줄링은 실행중인 것과 무관하게 우선순위가 높으면 큐의 가장 앞에 위치합니다.
  • 장점
    • 각 프로세스의 상대적 중요도를 명시할 수 있습니다.
    • 실시간 시스템에 유리합니다.
  • 단점
    • 높은 우선순위 프로세스가 계속 들어올 경우 우선순위가 낮은 프로세스가 너무 오래기다리는 현상이 발생합니다.

 

 

선점 스케줄링

  • 현재 CPU가 할당된 프로세스의 작업이 종료되지 않아도 다른 프로세스에게 CPU를 할당하는 스케줄링 방식입니다.
  • 알고리즘 : 우선순위(선점), 라운드로빈(RR)

 

 

우선순위(선점)

  • 하나의 프로세스가 CPU를 차지하고 있어도 우선순위가 높은 다른 프로세스가 대기하는 경우 현재 프로세스를 중단 시키고 우선순위가 높은 프로세스에게 CPU를 할당하는 스케줄링 방식입니다.
  • 장점
    • 응답이 빠릅니다.
  • 단점
    • 처리 시간을 예측하기 힘듭니다.
    • 우선순위 프로세스들이 계속해서 들어오는 경우 오버헤드가 발생합니다.

 

 

RR(Roung-Robin)

  • 현대적인 CPU 스케줄링 방식으로 각 프로세스는 동일한 할당 시간을 갖게되고 할당 시간이 지나고 나면 ready queue 맨 끝으로 가서 다시 CPU의 할당을 기다립니다.
  • 장점
    • 모든 프로세스가 공정하게 시간을 할당받습니다.
    • 프로세스 최악의 응답시간을 아는데 유리합니다.
  • 단점
    • 하드웨어 타이머가 필요합니다.
    • 작업 시간을 너무 짧게한 경우 Context Switching이 빈번하게 일어나 오버헤드가 발생합니다.

 

 

MLQ(Multilevel Queue)

  • 준비 상태 큐를 여러 종류별, 단계별로 분할해두고 자신만의 독자적인 스케줄링 구현이 가능합니다.
  • 각 큐는 절대적인 우선순위를 가지며 우선순위가 높은 큐가 모두 비어있기 전에는 낮은 우선순위 큐에 있는 프로세스를 실행할 수 없습니다.
  • 장점
    • 응답이 빠릅니다.
  • 단점
    • 여러 준비 큐와 스케줄링 알고리즘을 사용하기 때문에 추가 오버헤드가 발생합니다.
    • 우선순위가 낮은 큐의 프로세스가 오래 기다리는 현상이 발생합니다.

'운영체제' 카테고리의 다른 글

[OS] 가상 메모리와 페이지 폴트  (0) 2022.01.12
[OS] 프로세스 동기화 정리  (0) 2021.11.01
[OS] 프로세스와 스레드  (0) 2021.11.01

프로세스

  • 프로세스(process)란 일반적으로 CPU에 의해 처리되는 사용자 프로그램으로 실행중인 프로그램을 의미합니다.

 

 

프로세스와 프로그램

  • 프로그램은 일반적으로 하드 디스크 등에 저장되어 있는 실행 코드를 말합니다.
  • 프로세스는 프로그램을 구동하여 프로그램의 상태가 메모리 상에서 실행되는 작업 단위를 지칭합니다.
  • 프로그램은 보조 기억장치에 존재하며 실행되기를 기다리는 명령어와 정적인 데이터의 묶음입니다.
  • 이 프로그램의 명령어와 정적 데이터가 자원을 할당받고 메모리에 적재되면 프로세스가 됩니다.

 

프로세스의 특징

프로세스는 각각 독립된 영역(Code, Data, Stack, Heap)을 할당 받습니다.

  • Code 영역
    • 실행 명령을 포함하는 코드들이 들어가는 영역입니다.
    • 프로그램을 시작할 때 컴파일한 프로그램(기계어)이 저장되어 있고, 읽기 전용 영역이기때문에 프로세스가 함부로 변경할 수 없고 변경 시 오류를 발생시킵니다.
  • Data 영역
    • 프로그램이 실행될 때 생성되고 프로그램이 종료되면 시스템에 반환됩니다.
    • 전역변수, 정적변수, 배열, 구조체 등이 저장됩니다.
  • Heap 영역
    • 메모리를 동적으로 할당할 때 사용되는 메모리 영역입니다.
  • Stack 영역
    • 프로그램이 자동으로 사용하는 메모리 영역입니다.
    • 함수 호출과 관계되는 지역변수와 매개변수가 저장됩니다. 함수 호출 시 생성되고 함수가 끝나면 반환됩니다.

 

 

멀티 프로세스

  • 멀티 프로세스(Multi Process)란 하나의 응용 프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하도록 하는 것 입니다.
  • 장점
    • 멀티 프로세싱은 여러 개의 자식 프로세스 중 하나에 문제가 발생해도 다른 프로세스들에게 영향이 확산되지 않습니다.
  • 단점
    • Context Switching 과정에서 캐시 메모리 초기화 등 무거운 작업이 진행되고 많은 시간이 소모되는 등 오버헤드가 발생합니다.
    • 프로세스는 각각 독립된 영역을 가지고 있기 때문에 프로세스 사이에서 공유하는 메모리는 없습니다. 따라서 Context Switching이 발생하면 캐시에 있는 모든 데이터를 모두 리셋하고 다시 캐시 정보를 불러와야 합니다.
    • 독립된 영역 때문에 프로세스간의 통신이 필요합니다.

 

 

스레드

스레드(Thread)란 어떠한 프로세스 내에서 실행되는 흐름의 단위입니다.

 

 

스레드의 특징

  • 스레드는 프로세스 내에서 각각 Stack만 따로 할당받고 Code, Data, Heap 영역은 공유합니다.
  • 스레드는 한 프로세스 내에서 동작되는 여러 실행의 흐름으로 프로세스 내의 주소 공간이나 자원(Heap 공간 등)을 같은 프로세스 내에 스레드끼리 공유하면서 실행됩니다.
  • 같은 프로세스 안에 있는 여러 스레드들은 같은 Heap 영역을 공유합니다. 반면 프로세스는 다른 프로세스의 메모리에 직접 접근할 수 없습니다.

 

멀티 스레드

  • 멀티 스레드(Multi Thread)는 하나의 프로그램을 여러 개의 스레드로 구성하는 방식입니다.
  • 장점 
    • 멀티 스레드는 Stack을 제외한 자원들을 공유하고 있기 떄문에 Context Switching 시에 캐시 메모리를 비울 필요가 없고 이를 통해서 자원을 아낄 수 있습니다.
    • Stack 이외의 메모리를 공유하고 있기 때문에 통신의 부담도 멀티 프로세스에 비해 적습니다.
  • 단점
    • 내부의 메모리를 공유하고 있기때문에 한 프로세스의 스레드가 문제가 생길 시 해당 프로세스 안의 다른 스레드에도 문제가 생깁니다.
    • 같은 데이터를 공유하기에 데이터 동기화에 신경을 써야합니다.

 

 

멀티 프로세스와 멀티 스레드 비교

멀티 스레드는 멀티 프로세스보다 적은 메모리 공간을 차지하고 Context Switching이 빠르다는 장점이 있지만, 하나의 스레드가 문제가 생길 시 다른 스레드에 문제가 생길 수 있다는 치명적인 단점과 동기화의 문제를 가지고 있습니다. 반면 멀티 프로세스는 하나의 프로세스가 문제가 생기더라도 다른 프로세스에 영향을 끼치지 않는 장점이 있지만, 멀티 스레드보다 많은 메모리 공간과 CPU 시간을 차지한다는 단점이 있습니다.

 

동시에 여러 작업을 수행할 수 있다는 점에서 동일하지만 적용해야 하는 시스템에 따라 멀티 프로세스와 멀티 스레드 중 잘 선택해야하는 장, 단점이 존재합니다.

 

 

Context Switching

Context Switching이란 하나의 프로세스가 CPU를 사용중인 상태에서 다른 프로세스에게 CPU를 넘겨주기 위해, 이전의 프로세스 상태를 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말합니다. 여기서 한 프로세스의 상태는 프로세스 제어 블록인 PCB에 적재되어 있습니다.

 

 

P1 -> P2, P2 -> P1 의 Context Switching 과정은 다음과 같습니다.

  1. P1의 실행 중에 Interrupt 또는 System call 이 발생하면 P1은 idle 상태가 됩니다.
  2. P1이 executing 상태에서 idle 상태로 변할 때 프로세스와 실행에 대한 데이터는 레지스터에 존재하는데 이 데이터를 메모리에 저장합니다.(save state into PCB1)
  3. 그 다음 P2가 실행되는데 P2가 실행되기 위해서 메모리에 존재하는 P2에 대한 데이터를 레지스터에 올려야 합니다.(reload state from PCB2)
  4. P2가 실행되었다가 다시 idle 상태로 변경되면서 자신의 데이터를 메모리에 저장합니다.(save state into PCB2)
  5. 다음에 P1이 실행되는데 P1의 데이터를 메모리에 저장했었다. 이 데이터를 읽어서 레지스터에 올리고 P1을 이전에 멈추었던 시점에서 이어서 실행합니다.

 

 

 

+ Recent posts