In Python, the error message "Defaulting to user installation because normal site-packages is not writeable" usually appears when you try to install packages without having the necessary permissions to write to the system-wide site-packages directory.

Occurrence

This commonly occurs in virtual environments or when you lack administrative access.

Error-resolving approaches

To resolve this error, you can use the following approaches:

  1. Using a virtual environment

First, create a virtual environment using tools like venv or virtualenv using this command in your terminal:

 
 

Now, activate the virtual environment using these commands:

 
 

Finally, install packages within the virtual environment using this command in your terminal:

 
 

This way, you will have write access to the virtual environment's site-packages directory without requiring system-wide permissions.

  1. Specifying a user installation

You can install the package in the user’s local site-packages directory using this command:

 
 

Use the --user flag when installing packages to specify a user installation instead of a system-wide installation.

  1. Using a package manager

If you're using Linux, you can use system-wide package management like apt or yum to install Python packages. Typically, administrative rights are needed for this.

 
 

Hence, these approaches provide you with the necessary write permissions to install packages in a controlled and isolated manner.

 

 

 

ref : https://www.educative.io/answers/how-to-resolve-normal-site-packages-is-not-writable-in-python

반응형
Console.WriteLine(default(int));  // output: 0
Console.WriteLine(default(object) is null);  // output: True

void DisplayDefaultOf<T>()
{
    var val = default(T);
    Console.WriteLine($"Default value of {typeof(T)} is {(val == null ? "null" : val.ToString())}.");
}

DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
// Default value of System.Nullable`1[System.Int32] is null.
// Default value of System.Numerics.Complex is (0, 0).
// Default value of System.Collections.Generic.List`1[System.Int32] is null.

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/default

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

linq (3)  (0) 2023.04.10
linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //join
            {

                List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            //player와 levels를 조인하는데
                    on p.Level equals l     //레벨 i 와 player.level 이 같은것만 조인한다
                    select p;

                foreach(var p in playerLevels)
                {
                    Console.WriteLine(p.Level);
                }
                int test = 0;
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

player와 levels를 조인하는데 레벨 i 와 player.level 이 같은것만 조인하게 한다

 

                 List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            
                    on p.Level equals l    

                    select p;

 

 

 

 

 

표준 Linq 방식

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }


            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);


                int iii = 0;
            }

두개의 결과가 같은 것을 볼 수 있다

 

            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);

 

결과 : 

세부적인 기능을 사용하고 싶다면 함수형 linq 를 사용하면 된다

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

default 연산자  (0) 2023.04.25
linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }
            


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

palyer 에 

 public List<int> Items { get; set; } = new List<int>();

를 추가 한다음

 

from from 구문으로 item 의 모든 목록을 출력하는 구문이다

 

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

 

 

 

 

Group 처리

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
                int jj = 0;
            }


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

코드 중

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
               
            }

이 부분이 플레이어들을 같은 level 로 그룹화 한다음 각 그룹끼리는 key 값에 의해 정렬 되도록 처리한 것이다

into 는 그룹화 한것을 g 라는 집합으로 넣겠다는 것

 

결과 : 

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

default 연산자  (0) 2023.04.25
linq (3)  (0) 2023.04.10
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                _players.Add(player);
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

 

linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
from 은 foreach 로 생각해도 괜찮다
실행 순서는 from where orderby select 순으로실행된다 생각하면 된다

=> 이것이 원래 db 의 쿼리 구문을 이해하는 순서인데 c# 의 linq 에서는 select를 아예 밑으로 내려놨음


            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

 

 

 

유니티에선 linq 가 ios 버전에서 문제가 발생 할 수 있어서(불안전해서) 안쓰는게 좋다

(현재 버전 확인 필요)

 

서버에선 linq 활용을 하기에 DB 와의 명령어등의 호환성 좋아서 사용하는것은 괜찮다

 

 

db 관련 참고글 https://3dmpengines.tistory.com/category/%EC%84%9C%EB%B2%84%28Server%29/DB

 

'서버(Server)/DB' 카테고리의 글 목록

[3D] [M]athematic [P]hysics Engines

3dmpengines.tistory.com

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

linq (3)  (0) 2023.04.10
linq (2)  (0) 2023.04.09
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        //async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

        //task 는 일감을 말함
        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

 

Task 를 생성하고 delay 에 시간을 넣으면 생성과 동시에 시간이 흘러간다는 것을 알 수 있다

 

start test 문자가 뜨고 시간 지연 없이 바로 while start 가 호출되는데

        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            //처리 지점
              
            t.Wait();
            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }

wait 이 들어가면 start test 가 찍힌 후 3초 이후에 while start 가 호출 되는것을 알 수 있다

 

그래서 //처리 지점    에서 task 가 완료 되는 동안 다른 동작을 할 수 있게 된다

 

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);

            await t;
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

이 코드는 async/await 인데 결과는 다음과 같다

async 함수가 호출되고 task 가 완료 될때까지 await 으로 기다려지긴 하지만 제어권이 main 으로 넘어가서 처리할 것을 먼저 처리 할수 있는 형태가 된다 typescript 에 이와 유사한 문법들이 있다

 

또는 다음 처럼 간략한 버전으로 사용 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

 

main 에서 testAsync 함수가 완료 될때까지 대기 시킬 수도 있다 다음 처럼 대기 할수 있고

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            await testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

testAsync 함수가 모두 완료 될때까지 awiat testAsync(); 구문에서 대기하다가 완료 된 이후 main 의 나머지 구문을 이어 나간다

결과는 다음과 같다

 

 

값을 리턴 받고 싶다면 Task<T> 를 리턴하면 된다

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await testAsync();

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

다음처럼 main 에서 다른일을 하기위해 task 를 별도의 변수로 두어 위 async 함수를 분리하여 동시에 처리할 일을 처리 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            Task<int> t = testAsync();
            Console.WriteLine("다른 일");
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await t;

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

예시 다음 처럼 조식을 만들때

다음 처럼 일반적인 방식인 순차적으로 생각 할 수도 있지만

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

 

async 함수를 적절히 await 하여 처리 하여 대기 시간을 줄일 수 있다

그림은 위에서 아래로 내려가는 방향의 시간의 흐름이고 옆은 비동기로 실행 되는 개수라 보면 된다

 

 

 

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

 

 

최종 방식은 List 로 Task 를 담아 대기 하는 방식으로 최종 15 분 안에 끝나는 예시이다

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);

            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                await finishedTask;
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

ref : https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04

#include 를통해 오래 걸리던 시간을 줄일 수 있는 대안이 된다

다른 랭귀지 C#, Java 등에서 사용 되었던 기법들인데 이제 c++ 에 들어왔다

사실 들어온진 좀 됐는데 실험 기능이였고 계속 발전 되었던 형태이긴 하다

 

 


 

모듈(module) 이란?

C++20 이전의 C++에서는 필요한 함수 또는 클래스를 불러오기 위해 #include 전처리문을 이용해 왔다. 이런 헤더 파일 방식의 문제는..많지만 그 중에 필자가 가장 크리티컬하게 생각하는 부분은 #include 전처리문을 가리키고 있던 파일의 내용 그대로 치환해버려 헤더에 있는 필요하든 필요하지 않든 상관 없이 정의 되어있는 모든 기능을 포함하게 된다는 것이다. 예를 들어 cmath 헤더 파일에서 정작 내가 필요한 기능은 acos함수 하나 뿐이지만, acos를 사용하기 위해서 나는 헤더에 정의된 모든 함수들을 인클루드하고 컴파일 해야만 한다.

이미 현재 다른 언어들에서는 필요한 기능만을 가져 올 수 있는 기능을 제공하고 있지만 C++은 이번 C++20 스펙의 module을 통해 필요한 것만 가져올 수 있는 기능을 제공한다.

기존 C++시스템과 module 도입 이후의 차이와 이점에 대해서 [여기]에 정리 되어 있으니 살펴 보도록 하자.

모듈 코드 작성하기

앞에서 알아 본바와 같이 모듈의 의도와 개념은 간단 명료하다. 이제 모듈을 사용하기 위해 필요한 것들을 알아 보자. 모듈을 사용하기 위해 우리가 알아야 할 것도 간단 명료하다.

module, import, export 이 세가지 키워드를 기억하자
  • module : 모듈의 이름을 지정
    eg) module Math : 모듈의 이름은 'Math'이다.
  • import : 가져올 모듈의 이름을 지정
    eg) import Math : 가져올 대상 모듈의 이름은 'Math'이다.
  • export : 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
    eg) export int sum(int, int) : 내보낼 함수의 이름은 sum이고 리턴 타입은 int, 인자는 int, int다.

모듈 선언(declaration)

기존 #include 전처리 방식은 컴파일러와 상관 없이 선언은 헤더 파일에 위치하고 구현은 .cpp 파일 적성하는 방식이었다면 module은 각 컴파일러마다 각각 다른 방식으로 작성된다.

cl.exe(Microsoft 비주얼 스튜디오)

cl.exe는 비주얼 스튜디오의 컴파일러 이름이다. cl.exe는 모듈을 선언하기 위해 확장자가 ixx인 모듈 인터페이스 파일을 사용한다. 모듈을 만들기 위해 가장 먼저 할 일은 프로젝트에서 아래와 같이 'C++ 모듈 인터페이스 단위(.ixx)'를 선택하여 파일을 생성한다.

확장자가 .ixx인 모듈 인터페이스 파일을 만들고 내보낼 모듈의 이름과 인터페이스를 작성한다.

 
// ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc()  // 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​export double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()   // 모듈 내부에서만 사용하는 함수
​​​​{
​​​​}
}
  • 3라인의 'export module ModuleA'는 우리가 함수나 변수를 선언하는 것과 같이 모듈을 만들겠다는 선언이다.
  • 7라인과 12라인의 export가 선언되어 있는 MyIntFunc()와 MyDoubleFunc()는 다른 파일에서 이 모듈을 임포트(import)했을 때 사용할 수 있는 함수라는 것을 의미한다.
  • 17라인의 InternalMyFunc() 함수는 모듈 내부에서만 사용되고 이 모듀을 임포트하는 곳에서는 사용하지 못한다.
  • 위 예제와 같이 선언과 구현을 ixx파일에 모두 작성할 수도 있고 다음 예제와 같이 기존 .h, .cpp 구조 처럼 나눠서 작성할 수도 있다.
 
// 선언만 있는 ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc();
​​​​
​​​​export double MyDoubleFunc();
​​​​
​​​​void InternalMyFunc();
}
 
// ModuleA.cpp 모듈 구현 파일

module ModuleA;         // 시작 부분에 모듈 선언을 배치하여 파일 내용이 명명된 모듈(ModuleA)에 속하도록 지정

namespace Foo
{
​​​​int MyIntFunc()     // 구현에서는 export 키워드가 빠진다.
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()
​​​​{
​​​​}
}
Visual Studio 2019 버전 16.2에서는 모듈이 컴파일러에서 완전히 구현 되지 않았기 때문에 모듈을 사용하기 위해서는 /experimental:module 스위치를 활성화 해야 한다. 하지만 이 글을 적는 시점에서 최신버전 컴파일러는 모든 스펙을 다 구현하였으므로 모듈을 사용기 위해서는 최신 버전으로 업데이트할 필요가 있다.

module, import 및 export 선언은 C++20에서 사용할 수 있으며 C++20 컴파일러를 사용한다는 스위치 활성화가 필요하다(/std:c++latest ). 보다 자세한 사항은 [C++20] 컴파일 항목을 참고 하도록 하자.

참고로 필자의 경우 x86 32-bit 프로젝트를 생성해서 모듈을 사용하려고 했을 때 제대로 컴파일 되지 않았다. 비주얼 스튜디오에서 모듈을 사용하기 위해서는 64-bit 모드로 프로젝트를 설정해야 한다.

gcc

gcc의 경우 모듈을 선언하기 위해 .cpp 파일을 사용하지만 컴파일 시 -fmodules-ts 옵션을 이용해 컴파일 해야 한다. gcc에서 모듈을 사용하기 위해서는 gcc 버전 11이상이 필요하다. gcc 11의 설치 방법에 대해서는 [여기]를 참고 하도록 한다. gcc 컴파일에 대한 자세한 방법은 [여기]를 참고하자.

Global Module Fragment

적절하게 번역할 단어를 찾지 못해서 그대로 글로벌 모듈 프래그먼트(Global Module Fragment)라고 부르도록 하겠다. 글로벌 모듈 프래그먼트는 #include와 같은 전처리 지시자를 적어 주는 곳이라 생각하면 된다. 글로벌 모듈 프래그먼트는 익스포트(export)되지 않는다. 여기에 #include를 이용해 헤더를 불러왔다고 하더라도 모듈을 가져다 쓰는 곳에서 include의 내용를 볼 수 없다는 뜻이다.

 
// math1.ixx
module;                   // global module fragment (1)

#include <numeric>        // #include는 여기 들어간다
#include <vector>

export module math;       // module declaration (2)

export int add(int fir, int sec){
​​​​return fir + sec;
}

export int getProduct(const std::vector<int>& vec) {
​​​​return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
}

모듈 불러오기(import)

모듈을 불러와 사용하는 방법은 기존의 include와 매우 비슷하다. #include 대신 import 키워드를 사용한다

 
import ModuleA; // 모듈 임포트

#include <iostream>

int main()
{
​​​​std::cout << Foo::MyIntFunc() << std::endl;
​​​​return 0;
}

마치며

필자의 경우 비주얼 스튜디오에서는 #include, import 순서에 상관 없이 정상적으로 컴파일 되었지만, gcc에선 헤더와 모듈의 순서에 따라 컴파일 에러가 발생했었다(내가 뭔가를 잘못해서 그럴 수도 있지만..). 모듈을 임포트 후 shared_ptr을 위해 memory 헤더를 인클루드하면 컴파일 오류가 발생하고, 헤더를 먼저 인클루드 후 모듈을 임포트하면 정상적으로 컴파일 되었다. 

 
// compile : g++ -std=c++20 -fmodules-ts hello.cc main.cc

// import hello;      // Compile Error!!
// #include <memory>

#include <memory>     // Compile Success
import hello;

int main()
{
​​​​std::shared_ptr<int> i = std::shared_ptr<int>(new int);
​​​​greeter("world");
​​​​return 0;
}

위 예제의 전체 프로젝트 코드는 [여기]에서 확인 할 수 있다.

 

 

ref : https://kukuta.tistory.com/389

반응형

According to the following test, it seems that a std::vector<int> increases its capacity in this way:

  • it happens when we push_back() and the capacity is already full (i.e. v.size() == v.capacity()), it has to be noted that it doesn't happen a little bit before
  • the capacity increases to 1.5 times the previous capacity

Question: why this 1.5 factor? Is it implementation-dependent? Is it optimal?

Also, is there a way to analyze, in this code, when exactly a reallocation happens? (sometimes maybe the capacity can be increased without moving the first part of the array)

 

Note: I'm using VC++ 2013

 

 

 

I think an important aspect to the answer of why the factor is 1.5 is preferred over 2.0, is that the standard c++ allocator model does not support a realloc function:

Realloc- in Standard allocator

If it would exist, and it would be used in std::vector, the mentioned memory fragmentation problem would be minimized in all cases where the factor 1.5 is better compared to e.g. factor 2.0 capacity increase.

The factor of 1.5 (or more precisely the golden ratio) is just optimum when the buffer is always relocated on every buffer size increase (as the other answers explain).

With realloc and if there is space after the current memory block, it would just be enlarged without copying. If there is no space, realloc call would of course move the datablock. However then the free'd blocks (if this happens multiple times) would not be contiguous anyway. Then it doesnt matter whether 1.5 or 2 is the factor for buffer enlargement... Memory is fragmented anyways.

So a realloc approach in std::vector would help wherever the factor 1.5 has the advantage over 2.0 and would save the data copy.

 

 

 

ref : https://stackoverflow.com/questions/45403052/when-does-a-stdvector-enlarge-itself-when-we-push-back-elements

반응형
  • 배열(예를들면 네트워크에서 받은 데이터를 저장한 버퍼 byte[] buffer)의 특정 위치에서 특정 크기만큼 참고하고 싶을 때 보통 새로 배열을 만든 후 복사해야 원하는 데이터만을 참조할 수 있다.
  • 그러나 ArraySegment를 사용하면 새로 배열을 만들지 않으면서 버퍼의 특정 데이터를 참조할 수 있다.
  • ArraySegment를 사용하여 1차원 배열의 랩퍼로 배열 내의 요소를 범위(시작 위치와 길이)를 지정하여 구별한다.
int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 배열을 나눈다. ary1의 범위만큼의 참조를 만든다.
ArraySegment<int> arySeg = new ArraySegment<int>(ary1, 2, 5);

// 나누어진 범위의 요소를 출력한다.
for (int i = arySeg.Offset; i < arySeg.Offset + arySeg.Count; i++)
{
	Console.WriteLine("{0}:{1}", i, arySeg.Array[i]);
}

아래와 같이 표시된다

2:2
3:3
4:4
5:5
6:6

 

ref : https://jacking75.github.io/csharp_ArraySegment/

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
C# 에서 Dispose  (0) 2022.12.04

멀티 스레드 환경에서 각 스레드별로 하나의 공통 데이터에 접근하여 처리 될때 스레드 마다 원자성을 보장하면서
데이터를 다뤄야 하는 상황이 발생할 수 있습니다.

이때 제공 되는것이 Thread Local Storage(TLS) 변수라는 것이 있습니다.

쉽게 말해 tls변수는 스레드별 고유한 데이터를 저장(?)할 수 있는 공간이라고 이해 하면 됩니다. 또한 이 tls변수는(이하 거론되는 ThreadLocal<T> AsyncLocal<T> 동일) 자체적으로 데이터 원자성이 보장되기에 lock없이 접근하여 사용이 가능합니다.

닷넷 C#에서는 이것을 System.ThreadStaticAttribute 어트리뷰트 클래스로 제공 하고 있으며
System.Threading.ThreadLocal<T> 그리고 System.Threading.AsyncLocal<T> 클래스로 제공하고 있고, C++에서는 __declspec(thread)로 사용 가능합니다.

System.Threading.ThreadLocal<T> 같은 경우 System.ThreadStaticAttribute 보다 변수 초기 방법을 제공하고 있어 [ThreadStatic] 어트리뷰트 보다 좀 더 최신 기능을 제공합니다.

이번 포스트는 System.Threading.ThreadLocal<T>  System.Threading.AsyncLocal<T> 가 서로 어떤 부분에 차이점이 있는지에 대한 내용 입니다.

자 그럼 어떻게 다른지 살펴보겠습니다.

ThreadLocal<T> AsyncLocal<T> 차이점

위 tls역할을 하는 두 클래스의 큰 차이점은 스레드풀을 사용하는 스레드 환경에서 차이점이 있습니다.

우선 System.Threading.ThreadLocal<T> 은 해당 스레드가 스레드풀에 반환되어도 해당 데이터는 항상 유지 되어 집니다.

private static ThreadLocal<int> _tl = new ThreadLocal<int>();

private static async Task WorkAsync(int i)
{
  if(_tl.Value == 0)
  {
    _tl.Value = i;
    Console.WriteLine($"[새로운 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
  }
  else
  {
    Console.WriteLine($"[기존 값] Thread Id : {Environment.CurrentManagedThreadId} - tl Value : {_tl.Value}");
  }
  Console.WriteLine("-----------------------------------------");
}

위 처럼 System.Threading.ThreadLocal<T> 을 사용하는 메서드를 스레드로 호출해 봅니다. 그리고 차이점을 확실히 보기 위해 스레드풀의 스레드 개수 제한을 4개로 설정해 보았습니다.

// ThreadPool의 개수를 4개로 임의 제한
ThreadPool.SetMinThreads(4, 4);
ThreadPool.SetMaxThreads(4, 4);

for (int i = 1; i < 11; i++)
{
  await Task.Run(() => WorkAsync(i));
}

스레드를 사용하여 WorkAsync() 메서드를 10번 호출하였고 파라메터로 tls값을 동시에 넘겨 주었습니다.

결과는 어떻게 나올까요?

위 처럼 처음 스레드가 사용되어서 tls값이 설정 되고 해당 스레드가 반환된 이후 다시 같은 스레드가 사용 되었을때
tls값이 처음 설정 된 값으로 유지 되고 있는 걸 확인 할 수 있습니다.

반면,

AsyncLocal<T> 는 어떻게 처리 되는지 위 코드에서 System.Threading.ThreadLocal<T> 부분만 바꿔서 실행 시켜 보겠습니다.

결과를 보니 위 결과와는 다르게 사용된 스레드가 스레드풀에 반환될때는 값이 초기화가 되는 걸 볼 수 있습니다.

 

ref :https://blog.arong.info/c%23/2022/01/14/C-ThreadLocal-T-%EC%99%80-AsyncLocal-T-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90.html

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
Common Memory Leaks In C#  (0) 2022.12.04
C# 에서 Dispose  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27

Introduction

We all know that the .NET framework is an object-oriented, memory-managed, and type-safe framework. C# is the main development language in the .Net framework that has the same qualities. When we talk about memory management, we offer to refer to the garbage collector (GC) which will reclaim any unused objects and hence release them from memory. This potentially means we do not need to worry about memory leaks. However, there might be situations where we might create memory leaks which would be difficult for the GC to collect or run out of memory. Let us look at some of these situations and how to avoid them. So, let us begin.

Memory Leaks in C#
 

Implementing the IDisposable pattern

Always remember to implement the Dispose method on a class that implements the IDisposable interface. Otherwise, a memory leak can be caused. The best way to do this is to use the “using” statement which will call the disposal method for every condition. If you cannot use the “using” statement, remember to do this manually and also suppress the Finalize method as this would not be required.

Very Long Running Threads

If you have implemented a very long-running or infinite running thread that is not doing anything and it holds on to objects, you can cause a memory leak as these objects will never be collected. The fix for this is to be very careful with long-running threads and not hold on to objects not needed.

Over Caching

It has been seen that in some applications there is too much caching. This might help to speed up your application in the beginning but if you continue to do this you will eventually run out of memory. The best solution is to only cache objects very frequently used and not add unnecessary objects to the cache. Also, keeping the size of the cache objects to a reasonable one is important.

Using Static Objects

The use of static objects can also cause memory leaks. The reason for this is that static objects and everything they reference are not collected by the Garbage collector. Hence, use them to a bare minimum.

Using Unmanaged Objects

Whenever we are working with objects that are not collected by the garbage collector, we need to be extra careful to dispose of them after use. Otherwise, these will cause a memory leak as they are not cleaned up by the GC.

Summary

In this article, we looked at some potential situations that could lead to memory leaks or we can say inefficient use of the memory in our C# code, and how this can be avoided. There might be other similar situations as well. Hence, it is always a good idea to ensure memory is not being held for longer than required. Happy coding!

 

 

ref : https://www.c-sharpcorner.com/article/common-memory-leaks-in-c-sharp/

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
C# 에서 Dispose  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27
SpinLock  (0) 2022.11.27

C#은 가비지 컬렉터(GC)가 메모리를 자동으로 관리한다. 필요 없는 클래스의 인스턴스를 메모리에서 바로 지우는 게 아니라, 조건이 될 때까지 기다렸다가 지우기 때문에 클래스를 지웠다고 해도 그게 실제로 바로 삭제되는 것은 아니다. 일반적인 메모리라면 GC에 맡겨도 상관이 없지만, 관리되지 않는(Unmanaged, Native) 리소스는 즉각 해제해야 하는 경우가 생기는데, 그럴 때 필요한 것이 Dispose이다.

 

그래서, C++의 경우 소멸자에 각종 변수의 메모리를 해제하는 것으로 간단하게 구현이 될 만한 내용이지만, C#에서는 바로 삭제가 필요한 리소스를 해제하기 위해서 Dispose 함수가 필요하다.

 

소멸자와 Dispose의 차이 : stackoverflow.com/questions/339063/what-is-the-difference-between-using-idisposable-vs-a-destructor-in-c?newreg=eff4d2b40e844d5581272cc65983c418

 

What is the difference between using IDisposable vs a destructor in C#?

When would I implement IDispose on a class as opposed to a destructor? I read this article, but I'm still missing the point. My assumption is that if I implement IDispose on an object, I can

stackoverflow.com

그런데, Dispose를 호출하는 걸 깜박하면 어떻게 될까?

그럴 경우에는 GC에서 해당 인스턴스를 제거할 때, (소멸자에서) Dispose를 호출해준다.

큰 문제가 없으면 그냥 냅 둬도 상관없다는 얘기인데, 다만 다음과 같은 부작용이 생길 수 있다고 한다.

 

1.GC가 해당 인스턴스를 언제 지울 지 알 수 없다.

 즉, Dispose가 언제 호출될지 알 수 없다. 애플리케이션이 종료될 때까지 호출이 안될 수도 있다. 중요한 리소스를 사용하거나, 메모리를 많이 먹는 클래스라면 위험할 수도 있겠다.

 

2.GC가 해당 인스터스를 지울 때 Dispose를 호출하게 되면 그 순간 부하가 걸린다.

 즉, 랜덤하게 랙을 유발할 수도 있다. 성능에 예민한 앱이라면 주의가 필요하다.

 

3.GC가 해당 인스턴스를 지울 때 Dispose에서 예외가 발생하면 어플 자체가 크래시 할 수도 있다.

 수동으로 Dispose를 할 때에는 try/catch로 보호할 수 있지만, 자동제거일 경우 예외를 잡을 수 없기 때문에 매우 위험할 수도 있다.

 

참고 링크 : stackoverflow.com/questions/32397248/what-happens-if-i-dont-call-dispose

 

 

매번 Dispose를 호출하기 귀찮을 때는 using 블록을 사용하면 된다. using을 사용하면 자동으로 Dispose를 해준다.

using과 try/catch를 같이 사용해야 할 때에는 using을 try 안쪽에 넣으면 된다.

 

참고 링크 : theeye.pe.kr/archives/2736

 

Unity C# – IDisposable 알아보기 | 아이군의 블로그

이번에는 IDisposable 인터페이스에 대해서 알아보겠습니다. C#은 가비지콜랙터(Garbage Collector)를 가지고 있습니다. 이 GC는 기본적으로 관리되는 모든 객체들의 참조 링크를 관리하며 더이상 참조되

theeye.pe.kr

 

Dispose 기능이 필요한 커스텀 클래스를 만들어야 할 때에는 IDisposal을 상속 받아서 만들면 된다.

 

참고 링크  : medium.com/dotnetdev/idisposable-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95-4fa0fcf0e67a

 

IDisposable 패턴의 올바른 구현 방법

.NET Framework에서 새로운 클래스를 만들 때 여러가지 메모리 관리 디자인 패턴과 기법을 적용할 수 있지만, 한시적으로 사용해야 할 필요가 있는 자원들을 묶어서 관리할 때에는 IDisposable 패턴을

medium.com

참고 링크 2 : codeless.tistory.com/entry/C-Disposable-%ED%8C%A8%ED%84%B4

 

C# Disposable 패턴

public class MyResourceHog : IDisposable { private bool alreadyDisposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool isDisposing)..

codeless.tistory.com

 

 

ref : https://chomdoo.tistory.com/15

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
ReaderWriterLockSlim  (0) 2022.11.27
SpinLock  (0) 2022.11.27
c# AutoResetEvent, ManualResetEvent (커널모드)  (0) 2022.11.27

 

 

ReaderWriterLock 은 EnterWriterLock 호출 되기 전까진 EnterReadLock과 ExitReadLock  구간을 lock 없이 읽을 수 있는데 EnterWriterLock  이 호출 되면 이때 lock 이 점유되어 놓아지기 전까지 EnterReadLock ~ ExitReadLock   구간을 다른 스레드가 접근하지 못하게 된다

 

아이템 업데이트를 서버에서 잠깐 처리해야 할때 흔히자읂은 업데이트에서 사용 될 수도 있다

 

 

 

반응형

SpinLock 사용 예시

 

static SpinLock _lock

 

Enter 에서 현재 lock 을 제대로 잡았는지에 대한 상태를 lockTaken 변수를 통해서 알 수 있게 된다

 

Enter(Boolean) 메서드 호출에서 예외가 발생하는 경우에도 안정적인 방식으로 잠금을 얻으며 잠금을 얻었는지 확인하기 위해 lockTaken을 안정적으로 검사할 수 있습니다.
Exit() 잠금을 해제합니다.
Exit(Boolean) 잠금을 해제합니다.

 

 

//      SpinLock.IsThreadOwnerTrackingEnabled
    static void SpinLockSample2()
    {
        // Instantiate a SpinLock
        SpinLock sl = new SpinLock();

        // These MRESs help to sequence the two jobs below
        ManualResetEventSlim mre1 = new ManualResetEventSlim(false);
        ManualResetEventSlim mre2 = new ManualResetEventSlim(false);
        bool lockTaken = false;

        Task taskA = Task.Factory.StartNew(() =>
        {
            try
            {
                sl.Enter(ref lockTaken);
                Console.WriteLine("Task A: entered SpinLock");
                mre1.Set(); // Signal Task B to commence with its logic

                // Wait for Task B to complete its logic
                // (Normally, you would not want to perform such a potentially
                // heavyweight operation while holding a SpinLock, but we do it
                // here to more effectively show off SpinLock properties in
                // taskB.)
                mre2.Wait();
            }
            finally
            {
                if (lockTaken) sl.Exit();
            }
        });

 

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.spinlock?view=net-7.0

반응형

lock 클래스는 임의로 만든 lock 클래스이고 실제 중요한것은 AutoResetEvent 이다

 

초기 AutoResetEvent 값은 true 였고, 초기 값을 false 로 설정 하는것도 가능하다

 

waitOne() 함수를 만나 상태가 false 가 되면서 락을 소유하게 된다, 그래서 다른 스레드는 더이상 접근하게 못하게 되다가

 

.Set() 을 하는 순간 AutoResetEvent  가 다시 true 상태가 됨으로 이후 부턴 다른 스레드도

waitOne 에서 락을 획득하여 들어올 수 있게 된다

 

 

AutoResetEvent 는 waitOne 에 진입함과 동시에 자신의 상태를 false 로 만든다는 특징이 있다

 

 

 

 

이렇게 코드를 돌릴때 한쪽에서만 공유변수 _num 을 증가 또는 감소 할 수 있게 된다

 

 

 

 

 

ManualResetEvent

이건 문을 자동으로 닫진 않는다 즉 waitOne 에서 lock 을 획득하면 자도으로 ManualResetEvent 상태를 false 로 만들지 않는다는 것이고 이것을 수기로 해줘야 한다

 

ManualResetEvent.Reset();  false 로 바꾸는 함수

 

 

 

이렇게 하면 문제가 발생하는데

reset 과 watiOne 사이에 여러 스레드가 동시에 들어올 수 있기 때문에 정확한 처리가 되지 않는다

 

하지만 의도가 여러 스레드의 입장이 동시에 가능하게 할 것이라면 이 처리는 맞게 된다

 

 

 

ex ManualResetEvet 를 댐 같은것이라 생각하면 예시가 될 수 있는데

 

사용자가 게임 진입 버튼을 누르기 전까지 ManualResetEvet  에 의해 모든 스레드가 대기 하고 있다가

 

버튼 을 누르면 인터넷으로부터 데이터를 받아오거나 로컬 파일을 동시에 읽는다던가 맵을 이동하는 등

여러 스레드들을 버튼 누른 시점 이후부터 동작하게 하기위한 스레드 대기의 용도로 사용 할 수 도 있다

 

 

spinlock 은 유저모드에서 실행되는거라 cpu 타임만 갉아 먹는데

AutoResetEvent, ManualResetEvent 은 커널모드에서 돌아가는 것이기 때문에 context switch 가 일어나게 되어 성능이 유저모드에 비해 떨어 질 수가 있다

 

 

 

 

 

 

커널모드 동기화 중에 다른 것이 Mutex 가 있다

위의  WiatOne(문을 잠금) 으로 lock 을 소유하고 ReleaseMutex(문을 다시 연다) 를 통해서 mutex 의 lock 을 놓아준다

 

 

 

mutex 와 autoResetEvent 의 차이는

 

Mutex 는 lock을  잡을떄 카운팅을 할 수 있고 mutex 는 스레드 id 또한 갖고 있어서 

문제가 있을때 유용한데 추가 비용이 좀 더 들어간다

 

 

 

 

반응형

스레드 컨텍스트 스위칭을 일으킬 수 있는 명령들

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

SpinLock  (0) 2022.11.27
c# AutoResetEvent, ManualResetEvent (커널모드)  (0) 2022.11.27
Interlocked.CompareExchange(ref _lockecd, desired, expected)  (0) 2022.11.27
Deadlock 현상  (0) 2022.11.27
lock 과 Monitor 임계영역  (0) 2022.11.26

 

int ddd = Interlocked.CompareExchange(ref _lockecd, desired, expected);

 

 

CompareExchange 이걸 풀어보면

 

if( _lockecd == expected )

{

   _lockecd  = desired;

  return expected;

}else{

 return _locked;

}

 

같으면 expected 를 리턴하고

다르면 _locked 를 리턴한다

 

그리고 같으면 _locked 가 desired 값으로 바뀐다

 

 

그래서 아래 코드는 값이 바뀔때까지 계속 실행된다

 

 

 

    // AddToTotal safely adds a value to the running total.
    public float AddToTotal(float addend)
    {
        float initialValue, computedValue;
        do
        {
            // Save the current running total in a local variable.
            initialValue = totalValue;

            // Add the new value to the running total.
            computedValue = initialValue + addend;

            // CompareExchange compares totalValue to initialValue. If
            // they are not equal, then another thread has updated the
            // running total since this loop started. CompareExchange
            // does not update totalValue. CompareExchange returns the
            // contents of totalValue, which do not equal initialValue,
            // so the loop executes again.
        }
        while (initialValue != Interlocked.CompareExchange(ref totalValue, 
            computedValue, initialValue));
        // If no other thread updated the running total, then 
        // totalValue and initialValue are equal when CompareExchange
        // compares them, and computedValue is stored in totalValue.
        // CompareExchange returns the value that was in totalValue
        // before the update, which is equal to initialValue, so the 
        // loop ends.

        // The function returns computedValue, not totalValue, because
        // totalValue could be changed by another thread between
        // the time the loop ends and the function returns.
        return computedValue;
    }

 

주석 설명들에 어떤것과 어떤것이 비교가 되는지 빠져 있어 헷갈린다

ms 홈페이지 설명글은 항상 느끼는 거지만 글자만 있지 내용을 파악하기엔 내용이 부실하게 빠져 있거나 부족하거나 이상한 부분들이 많다

 

 

ref : https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.interlocked.compareexchange?view=net-7.0

반응형

 

    class SessionManager
    {
        static object _lock_session = new object(); 
        public static void TestSession()
        {
            lock(_lock_session)
            {}
        }
        public static void Test()
        {
            lock (_lock_session)
            {
                UserManager.TestUser(); 
            }
        }
    }
    class UserManager
    {
        static object _lock_user = new object();
        public static void TestUser()
        {
            lock(_lock_user)
            {}
        }
        public static void Test()
        {
            lock(_lock_user)
            {
                SessionManager.TestSession(); 
            }
        }
    }

    class Program
    {
            

        static int number = 0;
        static object _obj = new object(); 

        static void Thread_1()
        {
            for(int i=0; i<100; i++)
            {
                SessionManager.Test(); 
                
            }
        }

        static void Thread_2()
        {
            for(int i=0; i<100; i++)
            {
                UserManager.Test(); 
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine("데드락에 걸리지 않았다!");
        }
    }

 

 

Thread_1 에서 SessionManger.Test를 호출할 때 _lock_session을 사용해서 Lock을 걸고

이때 동시에 Thread_2 는 UserManger.Test를 호출해서 _lock_user를 사용해서 Lock을 건다.

 

Thread_1은 SessionManger.Test를 통해 UserManger.TestUser를 호출할 때 _lock_user로 Lock을 걸어야 한다.

근데 Thread_2에서 _lock_user를 사용하고 있다보니까 Lock을 걸 수 없고

 

마찬가지로, Thread_2 역시 UserManger.Test를 통해 SessionManager.TestSession을 호출할 때
_lock_session으로 Lock을 걸어야 하는데

Thread_1에서 이미 사용하고 있다보니 Lock을 걸 수 없다 보니

 

데드락에 빠지게 된다.

 

 

 

 

 

번외로, 데드락이 나면 예외처리로 고치기 보다는 차라리 크래쉬를 내는 것이 더 좋다.

Lock 구조에 문제기 있어 데드락이 발생한거기 때문에 그 상황에서 예외처리를 하는 것은 장기적인 관점에서 좋지 않다. 훗날 어떤 예외가 튀어 나올지 모르기 대문이다.

 

또 데드락이 무서운 이유는 조금의 시간차가 있으면 발생하지 않을 수 있기 때문이다.

위 코드에서

t1.Start();

Thread.Sleep(100); 

t2.Start();

t1과 t2의 시작 시간을 다르게 해주면 코드가 정상적으로 작동하는 것을 볼 수 있다.

 

이것은 임시적인 방인이다

 

 

ref :https://velog.io/@kkuoo7/DeadLock

반응형

Monitor

이를 위해 Monitor 라는 기능이 존재한다.
사용하기 전 변수를 선언해주자.

static object _obj=new object();

Monitor는 다음과 같이 사용하면 된다.

Monitor.Enter(_obj);
//소스코드
Monitor.Exit(_obj);
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
    class Lock_Basic
    {
        //1. 전역변수 메모리 값을 레지스터에 갖고오고, 레지스터 값을 증가시키고, 그 값을 메모리 값에 넣는다
        static object _obj = new object();
        static int number = 0;
        static void Thread_1()
        {

            for (int i = 0; i < 10000; i++)
            {
                Monitor.Enter(_obj);
                number++;
                Monitor.Exit(_obj);
                
            }
        }
        static void Thread_2()
        {
            for (int i = 0; i < 10000; i++)
            {

                Monitor.Enter(_obj);
                number--;
                Monitor.Exit(_obj);

            }
        }
        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2);
            Console.WriteLine(number);
        }
    }
}

 

이와 같이 실행한다면 증감연산자는 원자적으로 동작하지 않음에도 불구하고 잘 작동하는 점을 확인할 수 있을 것이다.

아시다시피 동시 다발적으로 쓰레드가 공유 변수에 접근시 문제가 발생했고, 이를 우리는Critical Section(임계영역)이라고 첫 시간때 이야기를 하였다.

그러나 Monitor.Enter, Monitor.Exit을 통해 이를 방지하여 이 사이의 코드는 사실상 싱글 쓰레드와 같은 상태가 되어버렸다. 이러한 조치를 Mutual Exclusion(상호배제)라고도 설명하였다.

Enter, Exit은 쉽게 이야기하면 1인용 화장실과 같다.
Enter(화장실 들어가서 문잠금)를 통해 한 사람이 화장실을 이용하고 있으면 Exit(화장실을 다 쓰고 문 열기)하기 전까지 바깥의 사람들은 화장실을 사용할 수 없다.

적어도 그 순간만큼은 소스코드에 다른 쓰레드가 끼어들 껀덕지가 없다는 뜻이다.

하지만, Monitor의 경우에도 단점이 있는데, 만약 Exit를 선언하지 않게 된다면 어떻게 될까? 화장실은 계속 이용하고 있고, 영영 화장실을 나가지 않게 되면 바깥의 사람들은 주구장창 기다려야 할 것이다.

이러한 상황을 데드락이라고 하는데, 이는 다음 시간에 자세히 살펴볼 것이다.

따라서 Monitor를 쓰는 경우 모든 경우(예외 처리)까지 Exit을 고려해야 하는 골치아픈 단점이 있어 거의 대부분 쓰이지 않는다.

 

 

 

처럼 Enter, Exit으로 샌드위치처럼 코드를 감쌌지만

lock(_obj){
//소스코드
}

Lock에선 이와 같이 소스코드를 넣어주면, lock 구절이 끝난 이후엔 자동으로 Monitor.Exit을 하게 되는 효과를 가지게 된다.

 

 


lock은 thread-unsafe(안전하지 않은 스레드)코드를 안전하게 사용하고자 할때 사용한다. 즉, 동기화 처리를 할때 사용하는 구문이다.

그렇다면, thread-unsafe한 코드는 어떤 코드인가 다음 예제를 보자.

       static public class Division
        {
            static int num1 = 100, num2 = 5;

            static public void Divide()
            {
                try
                {
                    if (num2 != 0) Console.WriteLine(num1 / num2);
                    num2 = 0;
                }
                catch (Exception e)
                {
                    Debug.WriteLine(string.Format("Err : {0}", e));
                }
            }
        }

 

 

Divide 함수가 스레드 하나의 함수에 의해 호출되면 안전하게 동작하겠지만, 두개 이상의 스레드에서 동시에 실행되면 thread-unsafe한 상태가 된다.

첫번째 실행된 스레드가 num2 = 0으로 할당하는 시점에 두번째 실행된 스레드가  num1/num2를 수행하게되면 DivideByZeroException("0으로 나누려 했습니다.")을 발생시키면서 정상 동작을 못하는 상황이 발생될 수 있기때문이다.

thread-safe(스레드에 안전한 코드)로 만들려면 아래 클래스와 같이 수정해주면된다.

    static public class Division
    {
        static int num1 = 100, num2 = 5;
        static readonly object divisionlocker = new object();

        static public void Divide()
        {
            try
            {
                lock (divisionlocker)
                {
                   if (num2 != 0) Console.WriteLine(num1 / num2);
                    num2 = 0;
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(string.Format("Err : {0}",e));
            }
        }
    }

 

lock 구문에 의해 첫번째로 실행된 스레드의 코드 처리 위치가 lock 구문안에 있다면, 두번째로 실행된 스레드는 첫번째 실행된 스레드가 lock구문 밖으로 나올때까지 기다렸다가 실행하여 thread-safe하게 동작한다.

 

 

 

ref : https://velog.io/@tunadolphin/C%EA%B3%BC-%EC%9C%A0%EB%8B%88%ED%8B%B0%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-MMORPG-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C-%EC%8B%9C%EB%A6%AC%EC%A6%88-Part4-lock-%EA%B8%B0%EC%B4%88

ref : https://sixthman23.tistory.com/entry/lock-%EA%B7%B8%EB%A6%AC%EA%B3%A0-MonitorEnterMonitorExit

반응형
class Program
    {

        static int number = 0; 

        static void Thread_1()
        {
            for(int i=0; i<100000; i++)
            {
                number++; 
            }
        }

        static void Thread_2()
        {
            for(int i=0; i<100000; i++)
            {
                number--; 
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(number);
        }
   }

number에 1을 더하는 연산을 10만번, number에 1을 빼는 연산을 10만번 수행한다.

우리는 당연히 number에 0이 할당되었을 것이라 생각 할수 있지만 그렇지 않다

 

하지만 결과는 전혀 엉뚱한 값이 나온다.

이런 현상이 발생하는 이유는 Race Condition(경합 조건) 때문이다.

 

 

 

코드를 자세히 살펴 보자.

number++;

number--; 

두 코드는 어셈블리 언어의 관점에서 아래와 같이 해석할 수 있다.

다른 스레드
int temp = number; 

temp += 1; 

number = temp; 



다른 스레드
int temp = number; 

temp -= 1;

number = temp;

 


전역변수의 값을 다른 스레드에서 각각 서로 변경하려다보니 이전 값으로 덮어 쓰게 되어 문제가 발생함

 

이것은 가시성 문제가 아니다

 

 

이러한 문제를 해결하려면 원자성(Atomicity)을 보장해주면 된다.

원자성이란? 더 이상 쪼개질 수 없는 성질로써, 어떠한 작업이 실행될때 언제나 완전하게 진행되어 종료되거나, 그럴 수 없는 경우 실행을 하지 않는 경우를 말한다

즉, number++와 number-- 연산이 원자성을 보장하여 한꺼번에 실행되거나 아예 실행하지 않도록 해주면 된다.

 

 

static void Thread_1()
{
	for(int i=0; i<100000; i++)
    	{
    		Interlocked.Increment(ref number);
    	}
}

static void Thread_2()
{
	for(int i=0; i<100000; i++)
	{
    		Interlocked.Decrement(ref number); 
	}

Interlocked 계열은 All or Nothing. 즉, 실행을 하거나 안되거나 이다.

Interlocked 하나가 실행되면 다른 Interlocked은 대기하고 있는다.

이렇게 함으로써 동시다발적으로 경합을 하면 최종승자만이 작업을 할 수 있고 결과를 보장받을 수 있다.

하지만 작업 속도는 당연히 원래 연산보다 느려질 수 밖에 없다. 한 작업이 끝날 때 까지 다른 작업이 대기해야 하기 때문

 

 

번외로, 만약에 우리가 number란 값을 추출하고 싶다면 어떻게 해야할까?

for(int i=0; i<100000; i++)
{
	prev = number; 
	Interlocked.Increment(ref number);
	next = number; 
}
int afterValue = Interlocked.Increment(ref number);

위 코드와 같이 Interlocked 함수의 반환 값을 통해 number의 값을 추출해야 한다.

 

이렇게 바로 받아오는건 스레드 세이프한데

 

아래 처럼 처리하면 next = number; 에서 스레드 세이프 하지 않게 된다

이 순간다른 스레드가 값을 바꿀 수있다

for(int i=0; i<100000; i++)
{
	prev = number; 
	Interlocked.Increment(ref number);
	next = number; 
}
반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

Deadlock 현상  (0) 2022.11.27
lock 과 Monitor 임계영역  (0) 2022.11.26
MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27

 

명령의 순서와 가시성에 도움을 주는 명령어 

최적화를 막아준다

 


Memory barrier

메모리 베리어는 membar, memory fence, fence instruction으로 알려져 있다. 메모리 배리어는 CPU나 컴파일러에게 barrier 명령문 전 후의 메모리 연산을 순서에 맞게 실행하도록 강제하는 기능이다. 즉 barrier 이전에 나온 연산들이 barrier 이후에 나온 연산보다 먼저 실행이 되는게 보장되어야 하는 것이다.

Memory barrier는 현대 CPU가 성능을 좋게 하기 위해 최적화를 거쳐 순서에 맞지 않게 실행시키는 결과를 초래할 수 있기 때문에 필수적이다. 이런 메모리 연산(load/store)의 재배치는 single 쓰레드로 실행할 때는 알아차리기 어렵지만, 제대로 관리되지 않는 한 병행적으로 작동되는 프로그램과 디바이스 드라이버에서 예상할 수 없는 결과를 초래하기도 한다. 싱글 쓰레드에서는 하드웨어가 명령어 통합성을 강제하기 때문에 싱글 쓰레드에서는 문제가 되지 않는다.

즉 정리하자면 성능을 좋게 만드려고 코드의 순서를 바꿔서 실행시킬 수 있는데, 이런 것을 막고 순서대로 코드가 실행되게 하도록 강제하는 것이다. 아래 간략화 된 코드를 보자. 실제로 동작하는 코드는 아니고, 핵심적인 부분만 남겨놓은 코드다.

static void Thread1() {
  y = 1; // Store y
  r1 = x; // Load x
}

static void Thread2() {
  x = 1; // Store x
  r2 = y; // Load y
}

static void Main(string[] args) {
  while(true) {
    x = y = r1 = r2 = 0; // 반복문의 초기부분마다 모든 변수의 값을 0으로 할당한다.
    
    Task t1 = new Task(Thread1);
    Task t2 = new Task(Thread2);
    
    t1.Start();
    t2.Start();
    
    Task.WaitAll(t1, t2);
    
    if (r1 == 0 & r2 == 0) 
      break;
  }
  System.out.println("While문이 break되었다.");
}

메모리 연산인 store과 load를 하는 Thread1, Thread2가 있고, 이를 Main 함수 안에서 실행시킨다. 위의 동작을 봤을 때 우리가 기대하는 것은 초기에 0으로 설정되어 있는 x, y 값이 1로 바뀌고 r1, r2에 이어서 1이 할당되면서 while 문이 계속 반복되어 무한 루프를 도는 것이다. 하지만 실제로 실행해 보면(위의 코드가 실행되는 건 아니다) While문이 break되었다. 로그가 찍히는 경우가 생긴다.

어떻게 이런 일이 가능한 것일까? 분명 while문을 빠져 나오는 조건은 r1과 r2가 모두 0일 때였다. 이 반복문을 빠져나왔다는 것은 원래 아래의 순서대로 실행되어야 할 코드가

x = 0 // 반복문 안
y = 0

x = 1 // 쓰레드 안
y = 1

r1 = x // 쓰레드 안
r2 = y

실제로는 아래와 같이 실행되는 것이다.

x = 0 // 반복문 안
y = 0

r1 = x // 쓰레드 안
r2 = y

// 여기에서 반복문 빠져나옴!

x = 1 // 쓰레드 안
y = 1

성능의 최적화를 위해서 코드의 순서를 이렇게 뒤집어서 실행시킬 수 있는데, 싱글 쓰레드에서는 문제가 되지 않지만 멀티 쓰레드에서는 문제가 된다. 따라서 이를 방지하기 위해, 명령어를 순서대로 실행시키기 위해 메모리 배리어를 사용한다.

static void Thread1() {
  y = 1; // Store y
  
  Thread.MemoryBarrier(); // 메모리 배리어
  
  r1 = x; // Load x
}

static void Thread2() {
  x = 1; // Store x
  
  Thread.MemoryBarrier(); // 메모리 배리어
  
  r2 = y; // Load y
}

static void Main(string[] args) {
  while(true) {
    x = y = r1 = r2 = 0; // 반복문의 초기부분마다 모든 변수의 값을 0으로 할당한다.
    
    Task t1 = new Task(Thread1);
    Task t2 = new Task(Thread2);
    
    t1.Start();
    t2.Start();
    
    Task.WaitAll(t1, t2);
    
    if (r1 == 0 & r2 == 0) 
      break;
  }
  System.out.println("While문이 break되었다.");
}

위의 코드와 같이 메모리 배리어를 설정하면 배리어 위의 코드가 배리어 아래의 코드와 순서가 바뀌어서 실행될 수 없게 된다. 이렇게 코드의 실행 순서를 보장해 주는 것이 메모리 배리어의 역할이다.

종류

메모리 배리어는 메모리 read/write가 내가 예상한 대로 실행되게 하는 명령어의 클래스다. 예를 들어 full fence(barrier)는 fence 전의 모든 read/write 는 fence 이후의 read/write 을 실행시키기 전에 모두 실행되어야 한다는 것이다. 메모리 배리어의 종류는 아래와 같다.

  1. Full Memory Barrier : read/write 를 둘 다 막는다.
  2. Store Memory Barrier : write만 막는다.
  3. Load Memory Barrier : read만 막는다.

메모리 배리어는 하드웨어 개념임을 기억해야 한다. 높은 레벨의 언어에서는 mutex와 세마포어를 이용해서 이를 해결한다. 낮은 레벨에서 메모리 배리어를 사용하고 메모리 배리어를 명시적으로 사용하는 것을 구현하는 것은 불필요할 수 있다. 메모리 배리어의 사용은 하드웨어 구조를 자세히 공부한 것이 전제가 되어야 하고, 애플리케이션 코드보다는 디바이스 드라이버에서 많이 사용된다.

 

 

 

 

 

 

 


메모리 배리어(Memory Barrier)

이 코드를 읽고 실행해보자.

 

 

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{

    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;


        static void Thread_1()
        {
            y = 1; // Store y
            r1 = x; // Load x 
        }

        static void Thread_2()
        {
            x = 1; // Store x 
            r2 = y; // Load y 
        }

        static void Main(string[] args)
        {
            int count = 0;
            while(true)
            {
                count++;
                x = y = r1 = r2 = 0;

                // t1, t2야 일해!
                Task t1 = new Task(Thread_1); 
                Task t2 = new Task(Thread_2);

                t1.Start();
                t2.Start();


                // t1 t2가 일이 끝날 때까지 Main Thread는 대기 
                Task.WaitAll(t1, t2);
                // t1,t2가 x,y,r1,r2를 모두 1로 바꾸어 주었으므로
                // 이론상으로는 무한반복 되어야 한다. 
                if (r1 == 0 && r2 == 0)
                    break;
            }

            Console.WriteLine($"{count}번 만에 r1, r2가 0이 되었습니다. ");

        }
    }
}

 

 

싱글쓰레드에서는 절대로 마지막 반복문을 빠져나올 수 없다.

하지만 생각보다 반복문을 잘 빠져나오는 것을 알 수 있다.


이는, 멀티쓰레드에서는 하드웨어 최적화가 적용되기 때문이다.

즉, 하드웨어가 쓰레드(Thread_1,Thread_2)에 준 연산들이 서로 상관이 없는 연산이라고 생각하면 의 연산 순서를 임의로 바꾸어서 연산하는 경우도 있기 때문이다.

이 때 메모리 배리어(Memory Barrier)를 사용한다.


메모리 배리어를 사용한 를 실행해보면 반복문을 빠져나오지 못한다.

 

메모리 베리어를 통해 코드 재배치 가시성을 확보 할 수 있다.

 

 

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{

    class Program
    {
        static int x = 0;
        static int y = 0;
        static int r1 = 0;
        static int r2 = 0;


        static void Thread_1()
        {
            y = 1; // Store y

            Thread.MemoryBarrier(); // notify other Thread stored 'y'
            r1 = x; // Load x 
        }

        static void Thread_2()
        {
            x = 1; // Store x 
            Thread.MemoryBarrier(); // notify other Thread stored 'x'
            r2 = y; // Load y 
        }

        static void Main(string[] args)
        {
            int count = 0;
            while(true)
            {
                count++;
                x = y = r1 = r2 = 0;

                // t1, t2야 일해!
                Task t1 = new Task(Thread_1); 
                Task t2 = new Task(Thread_2);

                t1.Start();
                t2.Start();


                // t1 t2가 일이 끝날 때까지 Main Thread는 대기 
                Task.WaitAll(t1, t2);

                // Memory Barrier로 코드 재배치와 가시성이 확보된 상태 
                if (r1 == 0 && r2 == 0)
                    break;
            }

            Console.WriteLine($"{count}번 만에 r1, r2가 0이 되었습니다. ");

        }
    }
}

 

 

 

 

 

 


 

 

ref : https://yoojin99.github.io/cs/Memory-Barrier/

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

lock 과 Monitor 임계영역  (0) 2022.11.26
Interlocked.Increment 과 Race Condition  (0) 2022.11.26
A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27

 

아래와 같은 길이 있을때

녹색 : 시작점

빨간색 : 도착점

노란색 화살표가 가르키는 곳 : 또 다른 길

 

시작점에서 끝점까지 이동 할때 노란색 화살표가 가리키는 곳으로 가게 되면 더 먼 경로로 가게 된다는 것을 알 수 있는데

Astar 에선 거리 측정을 두가지로 한다

1 현재 플레이어 위치와 목표 지점

2 현재 플레이어 위치 기준 이동 누적 거리가 얼마나 되는가

 

1,2 이것을 합산하여 거리 비교 판단을 하게 되어 더 가까운 쪽을 우선적으로 거리를 선택하여 도착점 까지 가게 되어 최단 경로를 구하게 됩니다
(최단 경로는 종점에서 역으로 시작점으로 가면서 현재 점의 부모 위치인 역으로 올라가는 방식입니다)

즉 여기선 아래쪽의 최단 경로를 찾게 된다는 애기가 됩니다

 

 

알고리즘 처리는 우선순위 큐를 활용해 현재 후보 노드들 중에서 합산 거리 가장 가까운 노드 기준으로 계속 탐색을 하는 방식입니다

 

 

 

 

 

코드는 4방향과 8방향이 있는데

4방향은 거리 10, 14를 모두 1로 처리해놓으면 됩니다

using System;
using System.Collections.Generic;
using System.Text;

namespace Algorithm
{
	class Pos
	{
		public Pos(int y, int x) { Y = y; X = x; }
		public int Y;
		public int X;
	}

	class Player
	{
		public int PosY { get; private set; }
		public int PosX { get; private set; }
		Random _random = new Random();
		Board _board;

		enum Dir
		{
			Up = 0,
			Left = 1,
			Down = 2,
			Right = 3 
		}

		int _dir = (int)Dir.Up;
		List<Pos> _points = new List<Pos>();

		struct Node : IComparable<Node>
        {
			public int F;
			public int G;
			public int X;
			public int Y;

            int IComparable<Node>.CompareTo(Node other)
            {
				return F == other.F ?  0 : (F < other.F ? 1 : -1);
            }
        }

		public void Initialize(int posY, int posX, Board board)
		{
			PosY = posY;
			PosX = posX;
			_board = board;

			AStar();

		}

		void AStar()
        {
            //점수 매기기
            //F = G + H
            //F = 최종 점수 (작을 수록 좋음, 경로에 따라 달라짐)

            //G = 시작점에서 해당 좌표까지 이동하는데 드는 비용 ( 작을 수록 좋음, 경로에 따라 달라짐 ), 현재 노드에서 열린 다음 노드까지
            //H = 목적지에서 얼마나 가까운지 (작을 수록 좋음, 고정)

            //open (거리 계산용) 배열과
            //closed 이미 방문한 노드인지 파악하기 위한배열
            //이 둘의 배열이 각각 존재한다

            // open 한 x,y 위치에 목적지까지 거리가 얼마나 되는지 거리 계산 후 저장한다 그렇지 않은것들은 max distance,  H 와 유사
            //오픈 리스트에 있는 정보들 중에서, 가장 좋은 후보를 뽑기 위해 => 우선순위 큐를 쓴다 => 저장 되는 기준은 거리가 짭을 수록 순위가 높은 것

            //방문 하게 되면 closed 가 된다, 방문한 곳은 더이상 조사하지 않는다

            //이동할수 있는 노드라면 예약을 해 놓는다, 상 하 좌우 좌표 확인해서 예약 한다(open)

            //F = G(현재 노드에서 다음 노드까지 거리) + H(현재 노드에서 목적지 까지의 거리H)

            //open 된 것들 중에서  계산한 F 가 open 되어진 즉 이전에 계산했던 open 거리보다 보다 작다면 더이상 계산 안함
            //=> open 된것과 현재 노드 기준 사방향을 보면서 가장 적은 비용의 F 를 구해서 다시 open[nextX, nextY] = F 에 넣어 준다
            //그리고 이것을 우선순위 Q 에 넣어 준다


			//4방향일때 
            // 			int[] deltaX = new int[] { 0, -1, 0, 1 };
            // 			int[] deltaY = new int[] { -1, 0, 1, 0 };
            // 			int[] cost = new int[] { 1, 1, 1, 1 };

			//8방향 일때
            int[] deltaX = new int[] { -1, 0,  1, 0,		-1,  1,  1, -1 };
            int[] deltaY = new int[] {  0, -1, 0, 1,		-1, -1 , 1,  1 };
            int[] cost =   new int[] { 10, 10, 10, 10,	14, 14, 14, 14 };

            bool[,] closed = new bool[_board.Size, _board.Size];
			int[,] open = new int[_board.Size, _board.Size];

			Pos[,] parent = new Pos[_board.Size, _board.Size];

			for(int i=0;i<_board.Size;++i )
            {
                for (int j = 0; j < _board.Size; ++j)
                {
					open[i, j] = int.MaxValue;
                }
            }


			PriorityQueue<Node> pq = new PriorityQueue<Node>();

			//초기 H 
			open[PosY, PosX] =  10 *  Math.Abs(_board.DestX - PosX) + Math.Abs(_board.DestY - PosY);
			pq.Push(new Node() { F=  open[PosY, PosX], G=0, X = PosX, Y = PosY });
			parent[PosY, PosX] = new Pos(PosY, PosX);

			while(pq.Count > 0)
            {
				//현재 후보군들 중에서 목표와 가장 근접한(계산식상) 후보를 하나 뽑는다
				Node node = pq.Pop();

				//방문했던 노드는 건너띔
				if(closed[node.Y, node.X])
                {
					continue;
                }
				closed[node.Y, node.X] = true;
				if (node.X == _board.DestX && node.Y == _board.DestY)
                {
					break;
                }

				//4방향으로 이동 할수 있는 노드인지 확인해서 가능하다면 예약(open) 에 거리 계산 한다
				for (int i = 0; i < deltaY.Length; ++i)
				{
					int nextX = node.X + deltaX[i];
					int nextY = node.Y + deltaY[i];

					//테두리를 벗어나지 않은 경우 and  벽이 아니며 and 방문을 안했었다면 새로운 open 노드를 찾는다
					if ((nextX < 0 || nextX >= _board.Size || nextY < 0 || nextY >= _board.Size) ||	
						(_board.Tile[nextY, nextX] == Board.TileType.Wall) ||
						closed[nextY, nextX])
					{
						continue;
					}


					int g = node.G + cost[i];
					//예약 가능 다음 노드 후보들에 대한 거리 계산을 하고
					//목적지와 다음 노드 같의 거리 임으로 이 차이가 더 작은 곳으로 
					int h =  10 *  Math.Abs(_board.DestX - nextX) + Math.Abs(_board.DestY - nextY);		
					//이전에 했었던 거리 계산이 더 작다면 skip
					if(open[nextY, nextX] < g + h)
                    {
						continue;
                    }
					 
					//거리를 갱신한다 더 짧은 거리로
					open[nextY, nextX] = g + h;
					pq.Push(new Node() { F = g + h, G = g, X = nextX, Y = nextY });

					parent[nextY, nextX] = new Pos(node.Y, node.X);

				}



			}

			calculatePath(parent);
		}

		void calculatePath(Pos[,] parent)
        {
			int x = _board.DestX;
			int y = _board.DestY;
			while(parent[y,x].X != x || parent[y, x].Y != y)
            {
				_points.Add(new Pos(y, x));
				Pos pos = parent[y, x];
				x = pos.X;
				y = pos.Y;
			}
			_points.Add(new Pos(y, x));
			_points.Reverse();
		}
			

		

		const int MOVE_TICK = 10;
		int _sumTick = 0;
		int _lastIndex = 0;
		public void Update(int deltaTick)
		{
			if (_lastIndex >= _points.Count)
				return;

			_sumTick += deltaTick;
			if (_sumTick >= MOVE_TICK)
			{
				_sumTick = 0;

				PosY = _points[_lastIndex].Y;
				PosX = _points[_lastIndex].X;
				_lastIndex++;
			}
		}
	}
}

 

 

 

 

노란 블럭 : 이동 경로

녹색 : 플레이어

붉은색 : 종점

왼쪽 상단 : 시작점

 

 

 

단점 : 목표하는대상과 거리가 멀 수록 성능이 떨어진다 => 조사 해야 하는 주변 블락 들이 많아지게 됨으로

장점 : BFS 보다 주변 환경이 반영된 좀 더 빠른 길 찾기가 가능하다, BFS 는 조사하는 범위가 계속 커진다

astar 는 BFS 보다는 조사 범위가  타일 하나의  누적 이동 거리+  플레이어의 위치에서 목표지점까지의 거리

를 고려하게 됨으로 좀 덜 커질 수 있다

 

 

거리 찾는데 Astar 도 느린건 마찬가지다 그렇다면?

JPS : 대상지점까지 거리를 찾는건 Astar 와 유사한데 길을 찾을때 대상 목표지점과 플레이어 사이에 벽같은게 있다면 벽면의 외각 부분에 우선순위가 높아 그쪽을 먼저 향해 가는 방식으로 주변 모든 타일을 게속 탐색하면서 가는 방식보다 성능이 빠르다

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

Interlocked.Increment 과 Race Condition  (0) 2022.11.26
MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
using System;
using System.Collections.Generic;

namespace TreeHeight
{

    class TreeNode<T>
    {
        public T data { get; set; }
        public List<TreeNode<T>> children { get; set; } = new List<TreeNode<T>>();
    }

    class Program
    {

        static TreeNode<string> makeTree()
        {
            TreeNode<string> root = new TreeNode<string>() { data = "Root" };
            {
                TreeNode<string> node = new TreeNode<string>() { data = "left" };
                node.children.Add(new TreeNode<string>() { data = "cc" });
                root.children.Add(node);
            }
            {
                TreeNode<string> node = new TreeNode<string>() { data = "design" };
                node.children.Add(new TreeNode<string>() { data = "combat" });
                node.children.Add(new TreeNode<string>() { data = "econo" });
                node.children.Add(new TreeNode<string>() { data = "abc" });
                root.children.Add(node);
            }
            {
                TreeNode<string> node = new TreeNode<string>() { data = "right" };
                node.children.Add(new TreeNode<string>() { data = "bb" });
                root.children.Add(node);
            }
            return root;
        }

        static int getTreeheight(TreeNode<string> root)
        {
            int height = 0;
            foreach(var elem in root.children)
            {
                int treeHeight = getTreeheight(elem) + 1;
                if(treeHeight > height)
                {
                    height = treeHeight;
                }
            }

            return height;
        }

        static void Main(string[] args)
        {
            var tree = makeTree();
            int treeHeight = getTreeheight(tree);
            Console.WriteLine(treeHeight);

        }
    }
}

 

결과는 2

 

 

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

MemoryBarrier 와 연산 순서/가시성  (0) 2022.11.25
A* (Astar) , JPS  (0) 2022.10.28
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
Comparer<T> Class  (0) 2018.10.15

Dijkstra 

 

다음과 같은 노드가 있다 가정하에 접근해봅니다

 

 

 

 

출발에서 A 로 갈때 미리 8을 a 까지드는 비용을 누적으로 구해놓고

출발 -> B -> A 로 가는 경로가 누적적으로 구해지면서 A에 도달했을때 기존 누적값과 누적해서 공통인 노드에 도달했을때 즉 A 에 도달했을때 누적된 것 중 더 빠른 거리를 경로로 선택하는 것

 

 

using System;

namespace GraphTraversal
{
    class Graph
    {
        // -1 은 연결 안된 상태를 표시
        int[,] adj = new int[6, 6]
        {
            { -1, 15, -1, 35, -1, -1 },
            { 15, -1, 5, 10, -1, -1 },
            { -1, 5, -1, -1, -1, -1 },
            { 35, 10, -1, -1, 5, -1 },
            { -1, -1, -1, 5, -1, 5 },
            { -1, -1, -1, -1, 5, -1 },
        };

        public void Dijikstra(int start)
        {
            bool[] visited = new bool[6];
            int[] distance = new int[6];
            Array.Fill(distance, Int32.MaxValue);
            int[] parent = new int[6];

            distance[start] = 0;        //자기자신은 0 거리로 만든다
            parent[start] = start;      //자기자신의 부모는 자기자신으로

            while (true)
            {
                // 제일 좋은 후보를 찾는다. 

                // 가장 유력한 정점의 거리와 번호를 저장한다.
                int closet = Int32.MaxValue;
                int now = -1;

                for (int i = 0; i < 6; i++)
                {
                    // 이미 방문한 정점은 스킵
                    if (visited[i])
                        continue;
                    // 아직 발견(예약)된 적이 없거나, 아직 방문하지 않고 거리만 계산한 노드 중에서 가장 짧은 노드를 찾아내기 위한 과정
                    if (distance[i] == Int32.MaxValue || distance[i] >= closet)
                        continue;

                    closet = distance[i];
                    now = i;
                }

                //now : 아직 방문하지 않은 노드 중 가장 짧은 거리가 짧은 노드
                if (now == -1) 
                    break;

                visited[now] = true;

                for (int next = 0; next < 6; next++)
                {
                    // 연결되지 않은 정점은 스킵한다
                    if (adj[now, next] == -1)
                        continue;

                    // 이미 방문한 정점은 스킵
                    if (visited[next])
                        continue;

                    //now : 1  ,
                    //next : 3
                    // 새로 조사된 정점의 최단 거리를 계산한다.
                    //                      15  +  10
                    int nextDist = distance[now] + adj[now, next];
                    // 이전까지 누적해서 계산 했던 노드 길이distance[next]보다,
                    // 현재 계산하고있는 누적 길이(nextDist)가 더 작다면 next Dist 즉 더 짧은 경로로 변경한다
                    if (nextDist < distance[next])
                    {
                        distance[next] = nextDist;      //해당 노드의 현재 까지 짧은 거리
                        parent[next] = now;             //해당 노드의 현재까지 짧은 거리의 부모

                        //각 노드에 누적된 거리가 계속 갱신 됨으로 
                    }
                }
            }
        }
    }
    class Program
    {

        static void Main(string[] args)
        {

            Graph dijik = new Graph();

            dijik.Dijikstra(0);

        }
    }
}

 

실행 결과

 

 

 벨만 포드 알고리즘과 다익스트라 알고리즘의 가장 큰 차이점은 벨만 포드 알고리즘은 방향 그래프에서 음의 가중치를 지닌 간선이 존재해도 사용할 수 있다 라는 점입니다. 따라서 음의 가중치를 가진 방향 그래프를 주면서 최단 거리를 구하라고 한다면 다익스트라 알고리즘이 아닌 벨만 포드 알고리즘을 사용 해야 합니다. 

 

 

 

 

ref : 한곳 더 있는데 url 을 잊어버림

ref : https://velog.io/@changhee09/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8B%A8%EA%B2%BD%EB%A1%9C-%EB%B2%A8%EB%A7%8C-%ED%8F%AC%EB%93%9C

https://www.youtube.com/watch?v=F-tkqjUiik0

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

A* (Astar) , JPS  (0) 2022.10.28
Tree height  (0) 2022.10.27
BFS 길찾기  (0) 2022.10.26
Comparer<T> Class  (0) 2018.10.15
out var  (0) 2018.09.09

출구 만들기

보드에 출구(목적지)를 표시해야 한다. Board 클래스에 DestY DestX 프로퍼티를 정의해서 렌더링 할 때 목적지를 노란색으로 표시하도록 코드를 수정한다.

 

class Board
{
    ...
    
    public int DestY { get; private set; }
    public int DestX { get; private set; }
    
    ...
    
    public void InitializeBoard(int size, Player player)
    {
        ...
        
        DestY = Size - 2;
        DestX = Size - 2;
        
        ...
    }
    
    public void Render()
    {
        ...
 
        for (int y = 0; y < Size; y++)
        {
            for (int x = 0; x < Size; x++)
            {
                ...
                
                else if (y == DestY && x == DestX)
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    
                ...
            } 
        }
    }
}

이제는 Board에 목적지 정보가 있으니 Player 쪽에서 목적지 정보를 받아올 필요가 없어졌다.

public void InitializePlayer(int posY, int posX, Board board)
{
    PosY = posY;
    PosX = posX;
 
    _board = board;
}

 


BFS 길찾기 알고리즘

인접 행렬 또는 인접 리스트 없이 타일 정보만 이용해서 가상의 그래프를 그려낼 수 있고 BFS 길 찾기 알고리즘을 적용할 수 있다. 하나의 타일을 노드라고 생각하고 상하좌우 바닥(노드)이 Empty라면 연결된 노드, Wall이라면 연결되지 않은 노드라고 판단할 수 있다.

 

BFS 코드를 작성하기 전에 미로 상의 X, Y좌표를 담을 Pos클래스를 정의했다.

class Pos
{
    public int Y;
    public int X;
 
    public Pos(int y, int x) { Y = y; X = x; }
}

 

※ BFS 코드 ※

여느 BFS 코드와 유사하다. 주목할 포인트는 parent path를 정의해서 찾아왔던 길을 기록하는 코드이다.

List<Pos> path = new List<Pos>();
 
...
 

   BFS();


...
 
private void BFS()
{
    int[] dirY = new int[] { -1, 0, 1, 0 };
    int[] dirX = new int[] { 0, -1, 0, 1 };
 
    bool[,] found = new bool[_board.Size, _board.Size];
    Pos[,] parent = new Pos[_board.Size, _board.Size];
 
    Queue<Pos> q = new Queue<Pos>();
    q.Enqueue(new Pos(PosY, PosX));
 
 
    found[PosY, PosX] = true;
    parent[PosY, PosX] = new Pos(PosY, PosX);
 
    while(q.Count > 0)
    {
        Pos pos = q.Dequeue();
 
        int nowY = pos.Y;
        int nowX = pos.X;
 
        for (int i = 0; i < 4; i++)
        {
            int nextY = nowY + dirY[i];
            int nextX = nowX + dirX[i];
 
            if (nextX <= 0 || nextX >= _board.Size || nextY <= 0 ||  nextY >= _board.Size)
                continue;
            if (_board.Tile[nextY, nextX] == Board.TileType.Wall)
                continue;
            if (found[nextY, nextX])
                continue;
 
            q.Enqueue(new Pos(nextY, nextX));
            found[nextY, nextX] = true;
            parent[nextY, nextX] = new Pos(nowY, nowX);
        }
    }
    
    CalcPathFromParent(parent);
}
 
private void CalcPathFromParent(Pos[,] parent)
{
    int y = _board.DestY;
    int x = _board.DestX;
 
    while (parent[y, x].Y != y || parent[y, x].X != x)
    {
        path.Add(new Pos(y, x));
 
        Pos pos = parent[y, x];
        y = pos.Y;
        x = pos.X;
    }
    path.Add(new Pos(y, x));
    path.Reverse();
}

 

 

BFS를 통해 path 정보를 채웠으니 Player가 자신의 위치를 갱신할 수 있도록 코드를 수정한다.

const int MOVE_TICK = 100;
private int _sumTick = 0;
int _index = 0;
 
public void Update(int deltaTick)
{
    if (_index >= path.Count)
        return;
 
    _sumTick += deltaTick;
 
    if (_sumTick >= MOVE_TICK)
    {
        _sumTick = 0;
 
        PosY = path[_index].Y;
        PosX = path[_index].X;
 
        _index++;
    }
}

 

똑똑한 Player로 진화했다.

 

 

ref : https://kangworld.tistory.com/57

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

Tree height  (0) 2022.10.27
다익스트라(Dijkstra) 최단경로  (0) 2022.10.27
Comparer<T> Class  (0) 2018.10.15
out var  (0) 2018.09.09
dynamic 형식 사용  (0) 2018.09.08

 

template<typename T>
struct Length;

template<>
struct Length<TypeList<>>
{
	enum { value = 0 };
};

template<typename T, typename... U>
struct Length<TypeList<T, U...>>
{
	enum { value = 1 + Length<TypeList<U...>>::value };
};

using TL = TypeList<player, mage,="" knight,="" archer="">;</player,>

이건 TL 들의 사이즈를 구할 수 있는 템플릿예시입니다

 

 

 

 

다음과 같은 상속 구조가 있을 경우

class Player
{
public:

	virtual ~Player() { }


};

class Knight : public Player
{
public:
	Knight() {  }
};

class Mage : public Player
{

public:
	Mage() {  }
};

class Archer : public Player
{

public:
	Archer() {  }
};

class Dog
{
};

 

 

template<typename From, typename To>
class Conversion
{
private:
	using Small = __int8;
	using Big = __int32;

	static Small Test(const To&) { return 0; }
	static Big Test(...) { return 0; }
	static From MakeFrom() { return 0; }

public:
	enum
	{
		exists = sizeof(Test(MakeFrom())) == sizeof(Small)
	};
};

 

Conversion은 아래 처럼 사용 할수 있는데 

 

bool canConvert1 = Conversion<Player, Knight>::exists;   0
bool canConvert2 = Conversion<Knight, Player>::exists;   1
bool canConvert3 = Conversion<Knight, Dog>::exists;       0

 

이렇게 호출 할 경우 

아래 두 함수 중에서 

 

static Small Test(const To&) { return 0; }


static Big Test(...) { return 0; }

 

 

Test 함수가 호출 될때 From 타입을 To 타입으로 캐스팅 할수 있다면 즉

 

모든 타입을 다 받는 Test(...)  가 아닌 특정 지정한 To 타입으로 

Test 함수가 불릴 수 있다면 상속관계이 있는 것임으로

 

이를 이용해서

 

exists = sizeof(Test(MakeFrom())) == sizeof(Small)

 

캐스팅이 가능한지 불가능한 관계인지를 컴파일 타임때 미리 계산 할 수 있다

 

 

 

 

 

 

아래는  타입에 대한 인덱스를 구할수 있는 코드

template<typename TL, typename T>
struct IndexOf;

template<typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T>
{
	enum { value = 0 };
};

template<typename T>
struct IndexOf<TypeList<>, T>
{
	enum { value = -1 };
};

template<typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T>
{
private:
	enum { temp = IndexOf<TypeList<Tail...>, T>::value };

public:
	enum { value = (temp == -1) ? -1 : temp + 1 };
};

 

 

define 좋진 않은데 그냥 예시라 보면 될듯합니다

 

[플레이어 코드 TL 예시]

using TL = TypeList<class Player, class Mage, class Knight, class Archer>;

#define DECLARE_TL	using TL = TL; int32 _typeId;
#define INIT_TL(Type)	_typeId = IndexOf<TL, Type>::value;



class Player
{
public:
	Player()
	{
		INIT_TL(Player);
	}
	virtual ~Player() { }

	DECLARE_TL
};

class Knight : public Player
{
public:
	Knight() { INIT_TL(Knight); }
};

class Mage : public Player
{

public:
	Mage() { INIT_TL(Mage); }
};

class Archer : public Player
{

public:
	Archer() { INIT_TL(Archer) }
};

class Dog
{

};

 

컴파일타임때 해당 타입이 몇번의 인덱스인지 구할수 있게 됩니다

tempalte 가변인자 특징을 활용

 

 

 

template<typename TL, int32 index>
struct TypeAt;

template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0>
{
	using Result = Head;
};

template<typename Head, typename... Tail, int32 index>
struct TypeAt<TypeList<Head, Tail...>, index>
{
	using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};

이것은 컴파일타임에 타입들중에서 지정한 인덱스에 해당하는 타입을 알수 있는 코드입니다

 

 

 

 

 

 

타입 변환을 위한 클래스들간의 타입 변환이 일어날수 있는가 2중 for 문돌리듯이 템플릿을 만들어 미리 그 결과를 저장해 놓을수 있는데 

 

 

 

template<int32 v>
struct Int2Type
{
	enum { value = v };
};

template<typename TL>
class TypeConversion
{
public:
	enum
	{
		length = Length<TL>::value
	};

	TypeConversion()
	{
		MakeTable(Int2Type<0>(), Int2Type<0>());
	}

	template<int32 i, int32 j>
	static void MakeTable(Int2Type<i>, Int2Type<j>)
	{
		using FromType = typename TypeAt<TL, i>::Result;
		using ToType = typename TypeAt<TL, j>::Result;

		if (Conversion<const FromType*, const ToType*>::exists)
			s_convert[i][j] = true;
		else
			s_convert[i][j] = false;

		MakeTable(Int2Type<i>(), Int2Type<j + 1>());
	}

	template<int32 i>
	static void MakeTable(Int2Type<i>, Int2Type<length>)
	{
		MakeTable(Int2Type<i + 1>(), Int2Type<0>());
	}

	template<int j>
	static void MakeTable(Int2Type<length>, Int2Type<j>)
	{
	}

	static inline bool CanConvert(int32 from, int32 to)
	{
		static TypeConversion conversion;
		return s_convert[from][to];
	}

public:
	static bool s_convert[length][length];
};

template<typename TL>
bool TypeConversion<TL>::s_convert[length][length];

 

이런 식으로 작성 하면 됩니다

 

s_convert 이곳에는 컴파일 타임때 타입간의 변환이 가능한지를 미리 계산해 놓은 곳이 되며

 

using TL = TypeList<class Player, class Mage, class Knight, class Archer>;

 

 

static inline bool CanConvert(int32 from, int32 to)
{
static TypeConversion conversion;
return s_convert[from][to];
}

 

이 함수로 타입간의 결과를 리턴 받을수 있습니다 : 이건 런타임이겠지요

 

 

 

 

이제 [플레이어 코드 TL 예시] 를 기준으로

 

 

아래 처럼 작성 할수 있는데

 

타입 캐스팅이 가능하다면 static_cast 로 캐스팅 해서 반환, 그렇지 않으면 nullptr 반환(dynamic_cast 와 유사)을 하는 것입니다

 

 


//타입 캐스팅이 가능하다면 static_cast 로 캐스팅 해서 반환, 그렇지 않으면 nullptr 반환(dynamic_cast 와 유사)
template<typename To, typename From>
To TypeCast(From* ptr)
{
	if (ptr == nullptr)
		return nullptr;

	using TL = typename From::TL;

	if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
		return static_cast<To>(ptr);

	return nullptr;
}


template<typename To, typename From>
shared_ptr<To> TypeCast(shared_ptr<From> ptr)
{
	if (ptr == nullptr)
		return nullptr;

	using TL = typename From::TL;

	if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
		return static_pointer_cast<To>(ptr);

	return nullptr;
}

//이건 캐스팅이 가능하냐 그렇지 않냐 Is, As 캐스팅 같은 것..
template<typename To, typename From>
bool CanCast(From* ptr)
{
	if (ptr == nullptr)
		return false;

	using TL = typename From::TL;
	return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}


template<typename To, typename From>
bool CanCast(shared_ptr<From> ptr)
{
	if (ptr == nullptr)
		return false;

	using TL = typename From::TL;
	return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}

 

remove_pointer_t 는 들어온 타입의 포인터를 제거 하는 것

 

 

 

예시 Player를 Knight 로 캐스팅

 

dynamic_cast 처럼 null 이 나오는것을 알수 있습니다

 

 

예시 본체(인스턴스)가 Knight 였고 이것을 player에 담은 후 player를 Knight 로 캐스팅하면 성공

 

Knight 가 초기화 되면서 player index 가 Knight 의 index로 바뀐 상태가 되고 

멤버변수 index에 Knight의 index 가 들어가게 됨

런타입에서 Knight 에 맞는 index를 찾기 때문에가능

 

 

반응형
This code does not compile, but i couldnot find what is wrong with the code. I think shared_ptr matters.

#include <memory>
#include <iostream>
using namespace std;

class A {
public:
  virtual const void test() const = 0;
  ~A() {  }
};
class AImpl : public A {
public:
  const void test() const {
    std::cout << "AImpl.test" << std::endl;
  }
};
class B {
public:
  B() {  }
  ~B() { 
  }

  shared_ptr<A> CreateA() {
    a_ = make_shared<AImpl>(new AImpl());
    return a_;
  }
private:
  shared_ptr<A> a_;
};

int main() {

  B *b = new B();
  shared_ptr<A> p = b->CreateA();
  if (b) {
    delete b;
    b = NULL;
  }
}

 

 

ou are using make_shared incorrectly. You dont need to use new in make_shared, it defeats the whole purpose of this function template.

This function is typically used to replace the construction std::shared_ptr(new T(args...)) of a shared pointer from the raw pointer returned by a call to new. In contrast to that expression, std::make_shared typically allocates memory for the T object and for the std::shared_ptr's control block with a single memory allocation (this is a non-binding requirement in the Standard), where std::shared_ptr(new T(args...)) performs at least two memory allocations.

a_ = make_shared<AImpl>(); // correct
//a_ = make_shared<AImpl>(new AImpl()); // not correct

 

 

ref : https://stackoverflow.com/questions/39651748/whats-wrong-with-the-code-using-shared-ptr-with-pure-virtual-base-class

반응형

Intro


std에 존재하는 lock 방법들은 운영체제 단에서 지원하는 크리티컬 섹션을 래핑한 mutex를 기반으로 개발자가 쉽게 락을 걸 수 있도록 도와줍니다. 앞서 배운 lock_guard 또한 mutex를 쉽게 사용할 수 있도록 도와 줍니다.

여기서 중요한 점은 mutex와 lock은 분명히 다른 역할을 하는 것입니다.
mutex는 다른 쓰레드가 공유자원에 접근을 하지 못하게 만드는 동기화 객체입니다. 공유 자원에 접근하기 위해서는 mutex에 대한 잠금을 획득하고, 접근 후에는 잠금을 해제 해야 합니다.

lock은 이러한 mutex를 기반으로 잠글수 있는 기능을 캡슐화 한 객체 입니다. 쉽게 생각하면 자물쇠가 lock 이고, 자물쇠의 열쇠 구멍을 mutex라고 생각 할 수 있습니다. 이러한 객체들은 개발자가 쉽고 간편하게 공유자원의 동시접근을 막을 수 있도록 도와줍니다.

 

 

기본적인 lock의 종류


std::mutex

앞서 정리했던 mutex 역시 가장 기본적이고 핵심이 되는 부분입니다. c++의 lock은 이러한 mutex를 베이스로 개발자가 더 쉽고 간편하게 다양한 기능들을 쓸 수 있도록 도와줍니다.

std::lock

C++11에서 추가된 std::lock은 기본적인 lock 클래스입니다.

#include <iostream>
#include <mutex>

std::mutex m1, m2;
int main() {
   std::thread th([&]() {
   std::lock(m1, m2);
   std::cout << "th1" << std::endl;
   m1.unlock();
   m2.unlock();
   });
   std::thread th2([&]() {
   std::lock(m1, m2);
   std::cout << "th2" << std::endl;
   m1.unlock();
   m2.unlock();
   });

   std::cout << "hello" << std::endl;
   th.join();
   th2.join();
}
C++

위의 코드처럼 std::lock은 여러개의 mutex를 한번에 잠글 수 있도록 도와 줍니다.

 


std::lock_guard

std::lock_guard는 많이 쓰이는 락 종류로써 다음처럼 객체 생성 시에 lock되며 객체가 소멸시에 unlock 되는 특성을 가지고 있습니다.

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m1;
int main() {
   std::thread th([&]() {
   std::lock_guard<std::mutex> lock_guard(m1);
   for (int i = 0; i < 100; i++) {
   std::cout << "th1" << std::endl;
   }
   });
   std::thread th2([&]() {
   std::lock_guard<std::mutex> lock_guard(m1);
   for (int i = 0; i < 100; i++) {
   std::cout << "th2" << std::endl;
   }
   });

   std::cout << "hello" << std::endl;
   th.join();
   th2.join();
}
C++
...
mutex.lock()

if(n == 10)
   return false;

mutex.unlock()
...
C++

또한 위와 같이 중간에 리턴되어 unlock이 되지 않는 문제를 해결 할 수 있습니다.

 


std::unique_lock

std::unique_lock은 기본적으로 lock_guard와 비슷한 특징을 가지고 있습니다. 둘 다 자신의 생명주기를 가지며 소멸 될 때 unlock 됩니다.
std::unique_lock은 기본적으로 생성과 동시에 lock이 걸리고 소멸시에 unlock되지만 그밖에도 옵션을 통해 생성시 lock을 안 시키기고 따로 특정 시점에 lock을 걸 수도 있습니다.

생성자의 인자로 mutex만 넘겨 준다면 생성 시에 lock이 걸리게 됩니다. 생성자의 인자로 mutex와 함께 std::defer_lock, std::try_to_lock, std::adopt_lock을 넘겨 줄 수 있습니다.
세가지 모두 컴파일 타임 상수 입니다.

  • std::defer_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. lock() 함수를 호출 될 때 잠금이 됩니다. 둘 이상의 뮤텍스를 사용하는 상황에서 데드락이 발생 할 수 있습니다.(std::lock을 사용한다면 해결 가능합니다.)
  • std::try_to_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. 내부적으로 try_lock()을 호출해 소유권을 가져오며 실패하더라도 바로 false를 반환 합니다. lock.owns_lock() 등의 코드로 자신이 락을 걸 수 있는 지 확인이 필요합니다.
  • std::adopt_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. 현재 호출 된 쓰레드가 뮤텍스의 소유권을 가지고 있다고 가정합니다. 즉, 사용하려는 mutex 객체가 이미 lock 되어 있는 상태여야 합니다.(이미 lock 된 후 unlock을 하지않더라도 unique_lock 으로 생성 해 unlock해줄수 있습니다.)
#include <mutex>
#include <thread>
#include <chrono>

struct Box {
   explicit Box(int num) : num_things{num} {}

   int num_things;
   std::mutex m;
};

void transfer(Box &from, Box &to, int num) {
   std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
   std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);

   // 2개의 뮤텍스를 동시에 lock
   std::lock(lock1, lock2);

   from.num_things -= num;
   to.num_things += num;
}

int main() {
   Box acc1(100);
   Box acc2(50);

   std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
   std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

   t1.join();
   t2.join();
}

 

C++

위의 예제는 unique_lock의 사용 예제 입니다. 각 transfer 함수를 쓰레드로 실행하며 그때 인자로 mutex를 가진 Box 객체를 넘겨줍니다.
각 쓰레드에서는 lock을 걸고 계좌의 돈을 옮기는 로직을 수행합니다.

하지만 이와 같은 상황에서는 주석 친 부분과 같이 unique_lock을 2줄에 걸쳐 잠그는 코드는 사용해서는 안됩니다. 이러한 코드는 문제를 발생 시 킬 수 있는 여지가 존재합니다.
만약에 2줄에 걸쳐 unique_lock을 이용해 락을 건다면 이때는 양쪽 다 락이 걸리는 데드락이 발생할 수 있기 때문입니다. 여기서는 std::defer_lock을 이용해 생성자에서 잠그는 것이 아니라 잠금 구조만 생성을 하고 후에 잠그도록 하였습니다.

하지만 transfer 함수가 다음과 같은 코드로 되어 있다면 어떻게 될까요?

void transfer(Box &from, Box &to, int num) {
   //객체 생성과 동시에 lock
   std::unique_lock<std::mutex> lock1(from.m);
   std::unique_lock<std::mutex> lock2(to.m);

   from.num_things -= num;
   to.num_things += num;
}
C++

위와 같은 상황이라면 쓰레드 1(from = acc1, to = acc2)이 from.m(acc1)을 락 걸고 그 다음줄인 to.m(acc2)를 수행하기 전에 쓰레드 2(from = acc2, to = acc1)가 첫줄에서 from.m(acc2)를 건 상황이 될 수 있습니다.
이때 두 쓰레드가 그 다음줄을 수행하려고 하지만 둘다 락이 걸려있는 상태이므로 대기를 하게 됩니다. 하지만 둘다 락을 풀어 주지 않기 때문에 데드락 현상이 발생하게 됩니다.

 

 

 

그래서 이런상환에서는 std:lock을 이용해 동시에 락을 걸어 주어야 타이밍 이슈로 데드락이 발생하지 않습니다.

Reference

https://en.cppreference.com/w/cpp/thread/mutex
https://en.cppreference.com/w/cpp/thread/unique_lock/unique_lock
https://docs.microsoft.com/ko-kr/cpp/standard-library/unique-lock-class?view=vs-2019

 

 

 

 

ref : https://dydtjr1128.github.io/cpp/2020/04/05/Cpp-lock.html

반응형
I have a base class Media and several derived classes, namely DVD, Book, etc... The base class is written as:
class Media{
    private:
        int id;
        string title;
        int year;
    public:
        Media(){ id = year = 0; title = ""; }
        Media(int _id, string _title, int _year): id(_id), title(_title), year(_year) {}
//      virtual ~Media() = 0;
        void changeID(int newID){ id = newID; }
        virtual void print(ostream &out);
};

The thing is: without the destructor, GCC gives me a bunch of warnings class has virtual functions but non-virtual destructor, but still compiles and my program works fine. Now I want to get rid of those annoying warnings so I satisfy the compiler by adding a virtual destructor, the result is: it doesn't compile, with the error:

undefined reference to `Media::~Media()`

Making the destructor pure virtual doesn't solve the problem. So what has gone wrong?

 

 

 

 

 

 

What you have commented out is a pure-virtual declaration for a destructor. That means the function must be overridden in a derived class to be able to instantiate an object of that class.

What you want is just a definition of the destructor as a virtual function:

virtual ~Media() {}

In C++ 11 or newer, it's generally preferable to define this as defaulted instead of using an empty body:

virtual ~Media() = default;
answered Apr 5, 2012 at 8:05

 

Jerry Coffin
453k76 gold badges597 silver badges

 

 

 

ref : https://stackoverflow.com/questions/10024796/c-virtual-functions-but-no-virtual-destructors

반응형

멤버 선언에 예기치 않은 한정이 있습니다. 이 경고를 해결하려면 식별자에서 한정자를 제거합니다.

기본적으로 이 경고는 해제되어 있습니다. /Wall 또는 /wN4596을 사용하여 명령줄에서 수준 N 경고로 사용하도록 설정할 수 있습니다. 또는 소스 파일에서 #pragma 경고(N:4596)를 사용합니다. 자세한 내용은 기본적으로 해제되어 있는 컴파일러 경고를참조하세요. 일부 버전의 컴파일러는 /permissive-아래에만 이 경고를 생성합니다.

이 경고는 Visual Studio 2015 업데이트 3부터 사용할 수 있습니다. 이전 버전의 컴파일러에서 경고 없이 컴파일된 코드는 이제 C4596을 생성할 수 있습니다. 특정 컴파일러 버전 이상에 도입된 경고를 사용하지 않도록 설정하는 방법에 대한 자세한 내용은 컴파일러 버전별 컴파일러 경고를참조하세요.

 

 

 

이 샘플에서는 C4596을 생성하고 이를 해결하는 방법을 보여줍니다.

 

// C4596.cpp
// compile with: /w14596 /c

struct A {
    void A::f() { } // error C4596: illegal qualified name in member
                    // declaration.
                    // Remove redundant 'A::' to fix.
};

 

 

ref : https://docs.microsoft.com/ko-kr/cpp/error-messages/compiler-warnings/c4596?view=msvc-170

반응형

+ Recent posts