오늘은 어제 프갤에 나왔던 면접 질문 답변해보기.


1. 유저레벨 스레드와 커널레벨 스레드를 비교하여 설명하세요.


유저레벨 스레드 :

- 커널영역 쓰레드와 하나의 프로세스가 엮이는 구조. (라이브러리 사용해서 쓰레드 생성하는 방식임 pcreate)

- OS는 프로세스 안에 많은 쓰레드가 있는지 모름. 그냥 한개의 쓰레드가 있겠지 하고 연결..


장점 : OS 시스템 콜 활용안해서 오버헤드 적음. 

단점 : 

- 한개의 쓰레드가 락걸면 전체 프로세스 스탑됨. 예를들어 join() 부르면 끝날때까지 머기타야됨.

- 멀티프로세서 버프 못받음.


커널레벨 쓰레드:


- 커널영역 쓰레드와 각각의 쓰레드가 엮이는 구조.


장점 : 멀티 프로세서 이점 받음. 스케줄링 독립적으로 수행되므로, 한개 락걸려서 멈춰도 다른 쓰레드 스케줄링됨.

단점 : 오버헤드 문제. OS는 쓰레드의 스케일까지 고려해야함.



2. 퀵소트가 왜 머지소트보다 빠를까요?

Locality의 이점을 활용하는 퀵소트가 더 빠름.

머지소트는 전체적으로 순회하면서 정렬하고

퀵소트는 국소범위를 정하고 순회하면서 정렬함.



3. 알고있는 GC와 작동 방식.


Java의 GC


1. GC Root이 있음. (GC root는 힙 영역 밖에서 접근 가능한것, 대표적으로 지역변수같은거) 

2. GC Root에서 참조 참조 해서 도달 가능한 객체를 찾아봄 -> 그래프 형태기때문에 DFS/BFS같은걸로 도달 가능한지 체크 가능.

3. 도달 가능하지 않는 객체들은 주기적으로 JVM에서 sweep시켜버림.


GC는 아닌데 IOS개발에서 사용하고 있는 메모리 관리 기법인 RC

ARC : automatic reference count 

레퍼런스 하고있는걸 카운팅해서 해제할지 말지 결정하는거임.


클래스의 새로운 인스턴스를 만들때마다 메모리에 그 인스턴스들의 정보를 할당함.

instance를 만들면, heap 영역의 에 instance mapping table에 객체 주소값, RC 등을 추가함. // new Person();

스택영역의 local variable에다가 주소값 넣어줌 입혀줌. // let a = new Person();

그리고 RC++; 함.


해제되거나, 사용하지 않으면  메모리에서 지워주고 reference count 는 -1이됨.


<<예제>>


ref = new A();

ref2 = ref; // 강한참조

ref3 = ref; // 강한참조


현재 refcount = 3;


ref2 = null; // refcount = 2

ref3 = nul; // refcount = 1


현재 refcount = 1; 

아직 살아있음.


ref = null

refcount = -1 => deinit이 불림.



+) RC와 GC의 비교

GC : 

- 런타임에서 돌면서 인스턴스 체크,

- 그래서 인스턴스 해제 잘 되고, 신경쓸거 많이없음.

- 대신에 추가 리소스 필요. 그리고 개발자가 언제 해제될지 알 수 없음.


RC : 

- 컴파일 타임에서 돌면서 인스턴스 체크.

- 컴파일 시에 메모리 해제 시점이 결정되니, 개발자가 직접 RC 카운팅 해보면 언제 해제되는지 알 수 있음.

- 추가 리소스 필요 없음.

- 대신 순환참조 신경써야함. 메모리에서 영원히 해제 안될 수 있음.


++) 강한참조문제


RC 마찬가지로 서로 강한참조로 물고있으면 영원히 해제 안됨.

그래서 Swift는 ref count 안늘리는 weak라는 약한참조를 명시할 수 있는것을 제공함.


let a = new Ref();

let b = new Ref();

a.you = b;

b.you = a;

서로 2씩 가지고있으므로, 

a = nil  //  2- 1 = 1;

b = nil // 2 -1 = 1;

해도 둘다 메모리에 남아있음.


GC 방식이라도 쓸데없는 참조때문에 메모리 릭이 날 수 있음.

예를들어 ArrayList의 각 셀에다가 중구난방으로 생성해놓고 사용하지 않고 방치해놓으면

객체는 사용안하지만 도달도 가능해서 GC가 해제 안시켜버림.



4. Lock의 종류

4-1) 락 구현 : 세마포어

현재 lock 상태를 정수로 보관


<동작방식>

처음 상태는 1.

만약 누가 락을 걸었으면 상태 -1

누가 락을 풀면 상태 +1


만약 락을 걸었는데 상태가 음수면 sleep()

락을 풀때, 한명 깨움.



4-2) 데이터베이스의 락

데이터베이스는 레코드 데이터가 일관성, 무결성을 유지하도록 레코드락을 걸어버림


1. shared lock: 읽기잠금. 

일관성, 무결성을 유지한 데이터를 "읽으려고" lock을 검

- 그래서 수정 허용안됨 (이 상태에서 exclusive lock 불가)

- 다른 shared lock은 허용됨. 어처피 수정하는게아니라 같은 데이터 읽는거라 상관 무.


2. exclusive lock: 쓰기잠금. 

일관성, 무결성을 유지한 채로 데이터를 "수정하려고" Lock을 검.

- 다른 exclusive lock, shared lock 불가. 아무도 읽지도 못함. 아무도 쓰지도 못함.


4-2-2) 트랜젝션 락.

RDMBS에서 트랜젝션의 무결성과 일관성을 보장하기 위해서 사용하는 락의 방식.


3. pessimistic lock: 

만약 바꾸면 exclusive lock을 걸어서 못읽게 막음.

트렌젝션 다 스탑하고 항상 최신값으로 정보를 받아옴.



4. optimistic lock: 

리코드를 읽을 때마다 버전정보가 바뀐적 없는지 체크.

만약 버전정보가 바뀐적이 있다면, 

누군가 바꿨다는 의미로 중간에 바꿨단 의미로 추측.

바꿔도 상관없다고 할지라도 새롭게 업데이트 된 레코드 데이터로 tx완료를 위해 다시 시작.


4-2-3) Optimistic vs. Pessimistic locking

optimistic : 

- 큰 단위/ 복잡한 시스템에서 유리 (중간에 락걸어서 멈출필요 없음)

- 데이터베이스와 직접 연결 세션도 필요없음. 버전정보 의지. (3티어 아키텍쳐에 적합, 클라이언트 + 비즈니스로직 + 데이터베이스)


Pessimistic

- 데드락 신경써야함.

- 락을 관리해야하기때문에 db세션 연결/유지 필수. (2티어 구조에 적합, 클라이언트 + 데이터)

- 혹은 db커넥션에 상관없이 txid를 공유할 수 있어야함. (two-phase commit의 프로토콜의 원리이기도 함 ,txid가지고 롤백)



No sql이지만, Redis도 Optimistic lock으로 구현. (근데 정확히모름 동작원리 알려주실분 있으면 ㄱㅅ)