동시성 프로그래밍: 단일 코어에서 여러 개의 thread를 활용하여 시간을 잘게 쪼개서 일을 진행하는 것이기에 동시에 실행되는 것 처럼 보인다. 앱 같은 경우 여러개의 작업(데이터 압축, 처리, 테이블뷰 등등)을 실행하기 위해
병렬 프로그래밍: 여러개의 프로세스를 같은 작업을 여러 프로세스에서 나눠서 하는 것. 유사하거나 같은 작업을 여러개로 나눠서 실행하는 것. 코어를 여러개를 가지고 있다고 해서 무조건 병렬 프로그래밍은 아니다. 물론 멀티코어 환경에서만 병렬처리가 가능하다.
멀티코어가 물리적 환경인 것은 맞다. 하지만 동시성 프로그래밍 환경에서 멀티코어인 환경에서는 특정 코어에서 코어를 잘 활용할 수 있도록 일을 잘 분배 해 준다. (GCD의 역할) GCD는 여러 개의 동작이 동시에 일어나게 하는 것 처럼 보이게 하기 위해 여러 개의 코어를 이용할 수 있다.
여러 개의 thread가 하나의 공유된 자원을 접근할 때 공간을 잠구고 그것을 사용한 다음에 해당 공간을 해제시켜줘야 thread-safe하다. 우리가 어떤 타입 프로퍼티 값을 사용해야 하는 상황일 때 여러 개의 thread가 있는 상황에서 특정 프로퍼티에 접근하려 할 때 A작업에서 해당 프로퍼티를 가져가려 하는데 B에서 해당 프로퍼티를 수정하고 있다면? 동시에 일어나게 된다면 문제가 발생할 수 있다[앱이 죽어버린다.]
Thread-safe == atomic:
Atomic: 모든 프로퍼티는 atomic하다 == 여러 thread에서 특정 프로퍼티를 가져가면 안된다는 개념. 다른 thread에서 특정 프로퍼티에 접근할 때 상관 없거나 아니면 해당 프로퍼티가 사용 중이라면 해당 프로퍼티를 접근하지 못하게 방지되어 있다. 외부로부터 방해 받지 않을 수 있다.
Non-atomic: 여러 thread 에서 특정 프로퍼티를 동시에 가져가도 상관 없다는 개념
두 개 이상의 프로세스나 스레드가 하나의 데이터를 공유할 때 데이터가 동기화되지 않는 상황을 뜻하는 용어로 알맞은 것은?
Race Condition: 경쟁 상태란 둘 이상의 입력 또는 조작의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태를 말한다. 따라서 하나의 데이터를 공유할 때 이런 동시작업이 이뤄지면 각 작업의 결과가 해당 데이터에게 종합적으로 영향을 미치는 것이 아니라 동작 중 하나는 데이터에 정상적으로 동기화 되지 않을 수 있다.
var globalVaribale = 100 // A
struct Person {
static let koreanWord = "사람" // B (B-1 : koreanWord / B-2 : "사람")
var name: String // C
}
var yagom = Person(name: "야곰") // D (D-1 : yagom / D-2 : "야곰")
func printName(_ person: Person) { // E - person
let name = person.name // F - name
print(name)
}
class Academy {
static let koreanWord = "아카데미" // G (G-1 : koreanWord / G-2 : "아카데미")
var name: String // H
init(name: String) {
self.name = name
}
}
var academy = Academy(name: "야곰 아카데미") // I (I-1 : academy / I-2 : "야곰 아카데미")
Q2 : ARC는 무엇인가?
참고) https://sujinnaljin.medium.com/ios-arc-뿌시기-9b3e5dc23814
strong, weak, unowned
Q3 : ARC 이전의 메모리 관리는 어땠을까?
retain
: retain count(= reference count) 증가를 통해 현재 Scope에서 객체가 유지되는것을 보장release
: retain count(= reference count)를 감소시킴. retain
후에 필요 없을 때 release
함Q4 : ARC를 이해해야 하는 이유는 무엇무엇이 있을까?
매번 전달할 때마다 값을 복사해 전달하는 값 타입과는 달리 참조 타입은 하나의 인스턴스가 참조를 통해 여러 곳에서 접근하기 때문에 언제 메모리에서 해제되는지가 중요한 문제이다.
인스턴스가 적절한 시점에 메모리에서 해제되지 않으면 한정적인 메모리 자원을 낭비하게 되며, 이는 성능의 저하로 이어지게 된다.ㅇ
작성해야하는 코드양이 줄어들고, 프로그램의 안정성 증가한다.
속도 향상을 위한 리팩토링 과정에서 ARC에 대한 이해 없이는 ...
Q5 : 언제 구조체를 선택하고 언제 클래스를 선택해야할까?
A: 데이터 영역에 저장된다. , 프로그램이 시작될 때 결정된다.
B-1: 데이터 영역에 저장된다 , (이 프로퍼티는 Person객체가 생성될 때 이 프로퍼티의 위치와 크기가 결정된다.)
B-2: 데이터 영역에 저장? (koreanWord의 초기값, koreanWord가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다??!?!.)
C: 스택 영역에 저장?, (yagom 객체가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
D-1: 스택 영역에 저장된다, (스택 포인터로 Person의 프로퍼티를 복사한 뒤)
D-2: 스택영역에 저장[파라미터], (yagom 객체가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
E: 스택 영역에 저장[파라미터], (printName이 호출될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
F: 스택 영역[지역변수], (printName이 호출될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
G-1: 데이터 영역 (이 프로퍼티는 Academy객체가 생성될 때 이 프로퍼티의 위치와 크기가 결정된다.)
G-2: 데이터 영역 (이 프로퍼티는 Academy객체가 생성될 때 이 프로퍼티의 위치와 크기가 결정된다.)
H: 스택 영역? Heap 영역?(academy 객체가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
I-1: 스택영역[heap을 가리키는 포인터가 저장], (academy 객체가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
I-2: 스택 영역[파라미터], (academy 객체가 생성될 때 해당 프로퍼티의 위치와 크기가 결정된다.)
. 자동으로 참조 횟수를 관리하기 때문에 사용하지 않는 인스턴스 를 메모리에서 해제해줍니다.
개발자가 직접 매번 메모리를 해제해줘야해서 불편함이 있었을 것 같다
메모리 누수를 막기 위해 강한참조와 약한 참조를 적절히 사용해야 하는데 이를 이해하기 위해서는 ARCㄹ를 잘 이해해야 합니다.
데이터의 크기를 모르거나 스택에 저장하기에는 큰 데이터의 경우에는 class에 데이터를 할당하는 것이 낫다.
그 외엔 스택에 할당하면 된다. 스텍에 너무 큰 데이터를 저장하게 되면 stackoverflow!!!!
https://babbab2.tistory.com/25
http://tcpschool.com/c/c_memory_structure
https://zeddios.tistory.com/1213#recentComments
https://jusung.gitbook.io/the-swift-language-guide/language-guide/10-properties
https://developer.apple.com/videos/play/wwdc2016/416/
비동기적인 작업을 모두 끝낸 뒤 해당 작업의 요약본을 print함수를 통해 출력해야 하는데 이 경우 main thread를 차단(block)한 뒤 모든 작업이 끝난 뒤 main thread의 차단을 풀어 print 메서드를 실행 시킬 수 있다.
func serveClient() {
while true {
bankWindow.maxConcurrentOperationCount = 3
var workTime = Double.zero
displayMenu()
let menuNumber = inputMenuNumber()
switch menuNumber {
case 1:
var clientWaitLine: [Client] = []
let totalCustomer = customerNumber()
for waitNumber in 1...totalCustomer {
let randomClientAttribute = acceeptRandomClient()
let client = Client()
client.waitingNumber = waitNumber
client.creditRate = creditRatings[randomClientAttribute.0]
client.typeOfWork = typeOfWorks[randomClientAttribute.1]
assignPriority(client)
clientWaitLine.append(client)
client.completionBlock = {
guard let typeOfTask = client.typeOfWork else { return }
workTime += typeOfTask.duration
}
}
bankWindow.addOperations(clientWaitLine, waitUntilFinished: true)
// 위 작업이 끝나기 전까지 현재 쓰레드(main thread)는 차단된다.
print("업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 \(totalCustomer)명이며, 총 업무시간은\(String(format: "%.2f", workTime))초 입니다.")
continue
case 2:
exit(0)
default:
print("잘못된 입력")
continue
}
}
}
물론 현재는 콘솔앱을 통해 작업 중이라서 큰 문제는 생기지 않지만 앱이라면 얘기가 달라진다.
iOS 앱에서 "메인 쓰레드는 1/60초에 한번씩 화면을 다시 그리는 역할을 하고 있기 때문에 오래걸리는 작업(네트워크 통신)과 같은 일을 시키면 안된다(버벅임의 원인이 된다.)" - Allen
UI업데이트 하는 작업을 메인쓰레드에서 하기 때문에 waitUntilFinished를 통해 아무런 while true문(메인 쓰레드에서 진행하는 작업)에서 waitUntilFinished를 사용하게 되면 메인쓰레드를 잠시 차단시키게 된다.
let serialQueue = DispatchQueue(label: "serial") // 시리얼큐 만들기
func serveClient() {
bankManager.maxConcurrentOperationCount = 1
serialQueue.async { // ⭐️ 시리얼큐에서 while true문 실행
while true {
// 고객 업무
}
}
}
메인 thread를 멈추지 않으려면 간단하다 serialQueue를 하나 만들어서 비동기적으로 while true문을 보내주면 된다. 그럼 이 while true문에서 실행되는 waitUntilFinished는 메인쓰레드가 아닌 다른 쓰레드를 차단하게 된다.
그런데 이렇게 보내고 나면 아무것도 실행되지 않고 프로그램이 종료되어 버립니다.
시리얼큐로 보내는 순간 main() 메인함수에서 작업이 끝나버리기 때문이다. 비동기적으로 작업을 실행하기 때문에 메인쓰레드에서는 main 함수의 모든 작업이 끝났다고 인식 해 버리는 것입니다. 😥
그렇다고 serialQueue.sync {}
로 동기적으로 비동기적 작업을 보낸다 하더라도 메인쓰레드에서 시리얼 큐로 보내는 일을 기다리기 때문에 결론적으로는 mainQueue가 해당 작업이 끝날 때까지 block상태가 되는 것이다.
그래서 내린 결론은....일단 콘솔앱에서는 이렇게 실행해도 되지만 앱을 구현할 때는 로직을 바꿔줘야 할 것 같습니다....그래도 waitUntilFinished의 개념을 조금 더 이해한 것 같아서 기쁩니다; ㅎㅎ
갑 캡쳐인가 뭔가 때문에 약한 참조가 필요한 것 같은데...다시 공부 해 봐야겠어요!
둘 다 operation의 우선순위를 정할 수 있게 해주는데 어떤게 다른지 잘 모르겠어요.. 찾아봐야 하는데 프로젝트의 늪에...
https://developer.apple.com/documentation/foundation/nsoperation/1411204-queuepriority
https://developer.apple.com/documentation/foundation/nsoperation/1413553-qualityofservice