해결된 질문
작성
·
275
·
수정됨
0
SpinLock 에 사용되는 특의 함수,
atomic 키워드와
compare_exchange_strong() 메소드를 쓰면서 구현을 하는 과정에 대해서 들었는데,
의문점이
void Add(), Sud() 메소드에
순서상의 lock_guard() 를 먼저 실행하면 되지 않을까 생각합니다.
thread1,
thread2
두 개의 쓰레드가 하나의 locked 부울 데이터를 공유한 상태에서
동시에 lock_guard() 를 할 수 있는 경우에서 locked 부울을 동시 접근하는 경우도 있겟지만,
=> 이러면 deadlock 이 발생하겠죠.
같은 말로,
thread t1(Add);
thread t2(Add);
같은 경우도 위에 deadlock 발생하는 같은 이치라고 보시면 될꺼 같습니다.
결국에는 locked 부울 데이터를 공통적으로 사용한다는 부분에서 생기는 문제니까요.
즉, thread 1 또는 thread 2 가 동시적으로 접근하지 않는 이상 deadlock 은 발생하지는 않을 것이며,
add 메소드에서 lock_guard() 를 했기에, 모든 작업을 다 끝내고 그 다음 add() 메소드를 실행하지 않을까 하고 생각합니다.
하지만 main 에서 Add(), Sub() 를 순차적으로 실행했기에 Add() 에 먼저 thread1, thread2 가 동시에 수행을 한다해도 결국에는 Add() 메소드에서 lock_guard 를 둘 중 하나가 먼저 수행하기 때문에
Add() 메소드를 먼저 접하는 thread 가 수행을 하고,
그 뒤의 Sub() 메소드 또한 실행을 하지만,
이 또한 thread 1 또는 thread 2 둘 중 하나가 lock_guard() 를 하고 있는 상태이기 때문에 다른 스레드가 _locked 부울에 접근을 하지 못하는 이유로 deadlock 이 발생하지 않고 원하는 값이 출력되지 않을까 생각했고, 실제로도 0 값이 나오기는 했습니다.
좀 더 정확하게는,
멀티스레드가 순차적으로 수행하지 않고 동시 다발적으로 실행이 된다면 종류는 아마,
1. A thread Add() 실행
B thread Sub() 실행
2. B thread Sub() 실행
A thread Add() 실행
3. A, B thread Add(), Sub() 동시 실행
같은 경우의 수가 더 있을 수도 있지만 현재는 여기 까지 밖에 볼 수 없었습니다.
만약 main() 함수에서 3번 경우에서 두 메소드에서 동시 다발적으로 처리를 수행한다면 deadlock 이 테스트를 하면서 운이 좋게 안 뜬걸로 볼 수있지만,
순차적으로 Add() -> Sub() 식이라면, 즉 1번 경우라면 SpinLock 을 처음 방식처럼 구현하는 상태에서,
lock_guard() 메소드를 순서를 바꿔서 처리하는 방법도 옳은 방법으로도 되는지 알고 싶습니다.
int32 sum;
class SpinLock
{
public:
void lock(){
while (_locked){
}
_locked = true;
}
void unlock()
{
_locked = false;
}
private:
bool _locked = false;
};
void Add()
{
lock_guard<SpinLock> guard(spinLock);
for (int32 i = 0; i < 10'000; ++i)
{
sum++;
}
}
void Sub()
{
lock_guard<SpinLock> guard(spinLock);
for (int32 i = 0; i < 10'000; ++i)
{
sum--;
}
}
void main()
{
thread t1(Add);
thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
}
.. 생략
답변 1
1
동시에 lock_guard() 를 할 수 있는 경우에서 locked 부울을 동시 접근하는 경우도 있겟지만,
=> 이러면 deadlock 이 발생하겠죠.
=> DeadLock을 잘못 이해하신 것 같네요. 경합 상황이 꼭 데드락이 아닙니다.
데드락은 정말 피치못할 사정으로 프로그램이 크래시 날 정도로 심각하게
락을 놔주지 않는 경우이고, 잠시 대기하는 것은 데드락이라고 하지 않습니다.
그리고 lock_guard도 단순하게 Lock을 Acquire하는 헬퍼 클래스이고
Acquire를 구현하려면 결국 CAS나 atomic 류의 변수가 필요합니다.
lock_guard<SpinLock> guard(spinLock);
락의 범위를 for문 밖으로 빼도 안 될 것은 없지만
그럴 경우 for문이 다 끝날 때까지 다른 쓰레드는 전혀 진행을 못하게 됩니다.
이게 의도한 바라면 상관없지만
보통 일감은 쪼개서 하나씩 실행하는 편이 여러모로 최적화할 방법이 많고
나중에 Job 방식의 일 분할 방법을 배우면 특히나 그렇습니다.
마지막으로 deadLock 이라고 불릴 수 있는 상황은 스레드들이 다른 스레드를 기달리는 일반적인 상황이 아닌 프로그램이 제 기능을 수행하지 못할 정도로 프로세서가 거의 정지 되어있는 듯한 상태를 지칭하는 개념으로 제가 봐도 될까요?
-> 네 그렇습니다.
한 가지 더 궁금한 점은, atomic 키워드는 강사님이 얘기하신 대로 하나의 작업 과정을 시작해서 끝이 날 때 까지 그 변수를 가지고 있는 상태로 유지하기 위한 변수 타입형이라고 볼 수도 있을까요?
-> 비슷한데 사실 정확히는 CPU단에서 제공하는 명령어이고, 하드웨어 레벨에서 제어를 하는 것입니다. 데이터 버스를 일시적으로 막아서 정말 동시다발적으로 실행되는 것을 막는 등의 행동을 하지만 이거까지 우리가 알 필욘 없습니다.
답변 주셔서 감사합니다 :),
for 문 도중에 다른 스레드가 작업을 할 수 없다는 것다는 것을 고려하지 않았습니다. 가장 간단한 예제로서 스레드 2개 를 소유한 상태에서 하나의 공통 데이터를 접근하는 경우였지만, 만약 수 많은 스레드, 데이터 인 경우에 강사님이 말씀하신 존버 메타는 더 이상 효율적이지 않게 되는 점에 동의합니다.
그렇다면 spinLock 의 경우에는
for 문 실행 도중 a 스레드가 작업이 끝나고 unlock 이 된 뒤, 다른 스레드 b가 같은 데이터 변수에 접근해서 수정이 가능한 원활한 작업과정을 의한 방법 중 하나로 spinLock 이 있습니다의 의도로서의 주제였다면 제가 미숙했던 점 같습니다.
마지막으로 deadLock 이라고 불릴 수 있는 상황은 스레드들이 다른 스레드를 기달리는 일반적인 상황이 아닌 프로그램이 제 기능을 수행하지 못할 정도로 프로세서가 거의 정지 되어있는 듯한 상태를 지칭하는 개념으로 제가 봐도 될까요?
한 가지 더 궁금한 점은, atomic 키워드는 강사님이 얘기하신 대로 하나의 작업 과정을 시작해서 끝이 날 때 까지 그 변수를 가지고 있는 상태로 유지하기 위한 변수 타입형이라고 볼 수도 있을까요?
감사합니다