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:
Using a virtual environment
First, create a virtual environment using tools likevenvorvirtualenvusing this command in your terminal:
python -m venv myenv
Now, activate the virtual environment using these commands:
source myenv/bin/activate # for Linux/Mac
myenv\Scripts\activate.bat # for Windows
Finally, install packages within the virtual environment using this command in your terminal:
pip install package-name
This way, you will have write access to the virtual environment's site-packages directory without requiring system-wide permissions.
Specifying a user installation
You can install the package in the user’s local site-packages directory using this command:
pip install package-name--user
Use the--userflag when installing packages to specify a user installation instead of a system-wide installation.
Using a package manager
If you're using Linux, you can use system-wide package management likeaptoryumto install Python packages. Typically, administrative rights are needed for this.
sudo apt install python-package-name# for apt package manager
sudo yum install python-package-name# for yum package manager
Hence, these approaches provide you with the necessary write permissions to install packages in a controlled and isolated manner.
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.
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);
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 값에 의해 정렬 되도록 처리한 것이다
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 버전에서 문제가 발생 할 수 있어서(불안전해서) 안쓰는게 좋다
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();
}
}
}
C++20 이전의 C++에서는 필요한 함수 또는 클래스를 불러오기 위해 #include 전처리문을 이용해 왔다. 이런 헤더 파일 방식의 문제는..많지만 그 중에 필자가 가장 크리티컬하게 생각하는 부분은 #include 전처리문을 가리키고 있던 파일의 내용 그대로 치환해버려 헤더에 있는 필요하든 필요하지 않든 상관 없이 정의 되어있는 모든 기능을 포함하게 된다는 것이다. 예를 들어 cmath 헤더 파일에서 정작 내가 필요한 기능은 acos함수 하나 뿐이지만, acos를 사용하기 위해서 나는 헤더에 정의된 모든 함수들을 인클루드하고 컴파일 해야만 한다.
이미 현재 다른 언어들에서는 필요한 기능만을 가져 올 수 있는 기능을 제공하고 있지만 C++은 이번 C++20 스펙의 module을 통해 필요한 것만 가져올 수 있는 기능을 제공한다.
기존 C++시스템과 module 도입 이후의 차이와 이점에 대해서 [여기]에 정리 되어 있으니 살펴 보도록 하자.
모듈 코드 작성하기
앞에서 알아 본바와 같이 모듈의 의도와 개념은 간단 명료하다. 이제 모듈을 사용하기 위해 필요한 것들을 알아 보자. 모듈을 사용하기 위해 우리가 알아야 할 것도 간단 명료하다.
module, import, export 이 세가지 키워드를 기억하자
module : 모듈의 이름을 지정 eg)moduleMath : 모듈의 이름은 'Math'이다.
import : 가져올 모듈의 이름을 지정 eg)importMath : 가져올 대상 모듈의 이름은 'Math'이다.
export : 모듈에서 내보낼 기능(함수)의 인터페이스를 지정 eg)exportint 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 키워드를 사용한다
필자의 경우 비주얼 스튜디오에서는 #include, import 순서에 상관 없이 정상적으로 컴파일 되었지만, gcc에선 헤더와 모듈의 순서에 따라 컴파일 에러가 발생했었다(내가 뭔가를 잘못해서 그럴 수도 있지만..). 모듈을 임포트 후 shared_ptr을 위해 memory 헤더를 인클루드하면 컴파일 오류가 발생하고, 헤더를 먼저 인클루드 후 모듈을 임포트하면 정상적으로 컴파일 되었다.
According to the following test, it seems that astd::vector<int>increases its capacity in this way:
it happens when wepush_back()andthe 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 capacityincreases 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 doesnotsupport areallocfunction:
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 isalwaysrelocated 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 areallocapproach in std::vector would help wherever the factor 1.5 has the advantage over 2.0andwould save the data copy.
멀티 스레드 환경에서 각 스레드별로 하나의 공통 데이터에 접근하여 처리 될때 스레드 마다 원자성을 보장하면서 데이터를 다뤄야 하는 상황이 발생할 수 있습니다.
이때 제공 되는것이 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>부분만 바꿔서 실행 시켜 보겠습니다.
결과를 보니 위 결과와는 다르게 사용된 스레드가 스레드풀에 반환될때는 값이 초기화가 되는 걸 볼 수 있습니다.
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!
C#은 가비지 컬렉터(GC)가 메모리를 자동으로 관리한다. 필요 없는 클래스의 인스턴스를 메모리에서 바로 지우는 게 아니라, 조건이 될 때까지 기다렸다가 지우기 때문에 클래스를 지웠다고 해도 그게 실제로 바로 삭제되는 것은 아니다. 일반적인 메모리라면 GC에 맡겨도 상관이 없지만, 관리되지 않는(Unmanaged, Native) 리소스는 즉각 해제해야 하는 경우가 생기는데, 그럴 때 필요한 것이 Dispose이다.
그래서, C++의 경우 소멸자에 각종 변수의 메모리를 해제하는 것으로 간단하게 구현이 될 만한 내용이지만, C#에서는 바로 삭제가 필요한 리소스를 해제하기 위해서 Dispose 함수가 필요하다.
ReaderWriterLock 은 EnterWriterLock 호출 되기 전까진 EnterReadLock과 ExitReadLock 구간을 lock 없이 읽을 수 있는데 EnterWriterLock 이 호출 되면 이때 lock 이 점유되어 놓아지기 전까지 EnterReadLock ~ ExitReadLock 구간을 다른 스레드가 접근하지 못하게 된다
// 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();
}
});
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 홈페이지 설명글은 항상 느끼는 거지만 글자만 있지 내용을 파악하기엔 내용이 부실하게 빠져 있거나 부족하거나 이상한 부분들이 많다
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;
메모리 베리어는 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 을 실행시키기 전에 모두 실행되어야 한다는 것이다. 메모리 배리어의 종류는 아래와 같다.
Full Memory Barrier : read/write 를 둘 다 막는다.
Store Memory Barrier : write만 막는다.
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이 되었습니다. ");
}
}
}
시작점에서 끝점까지 이동 할때 노란색 화살표가 가리키는 곳으로 가게 되면 더 먼 경로로 가게 된다는 것을 알 수 있는데
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 와 유사한데 길을 찾을때 대상 목표지점과 플레이어 사이에 벽같은게 있다면 벽면의 외각 부분에 우선순위가 높아 그쪽을 먼저 향해 가는 방식으로 주변 모든 타일을 게속 탐색하면서 가는 방식보다 성능이 빠르다
출발 -> 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);
}
}
}
실행 결과
벨만 포드 알고리즘과 다익스트라 알고리즘의 가장 큰 차이점은 벨만 포드 알고리즘은 방향 그래프에서음의 가중치를 지닌 간선이 존재해도 사용할 수 있다라는 점입니다.따라서 음의 가중치를 가진 방향 그래프를 주면서 최단 거리를 구하라고 한다면 다익스트라 알고리즘이 아닌 벨만 포드 알고리즘을 사용해야 합니다.
보드에 출구(목적지)를 표시해야 한다. 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++;
}
}
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)
};
};
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 문돌리듯이 템플릿을 만들어 미리 그 결과를 저장해 놓을수 있는데
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 usingmake_sharedincorrectly. You dont need to usenewinmake_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.
std에 존재하는 lock 방법들은 운영체제 단에서 지원하는크리티컬 섹션을 래핑한 mutex를 기반으로 개발자가 쉽게 락을 걸 수 있도록 도와줍니다. 앞서 배운 lock_guard 또한 mutex를 쉽게 사용할 수 있도록 도와 줍니다.
여기서 중요한 점은 mutex와 lock은 분명히 다른 역할을 하는 것입니다. mutex는 다른 쓰레드가 공유자원에 접근을 하지 못하게 만드는 동기화 객체입니다. 공유 자원에 접근하기 위해서는 mutex에 대한 잠금을 획득하고, 접근 후에는 잠금을 해제 해야 합니다.
lock은 이러한 mutex를 기반으로 잠글수 있는 기능을 캡슐화 한 객체 입니다. 쉽게 생각하면 자물쇠가 lock 이고, 자물쇠의 열쇠 구멍을 mutex라고 생각 할 수 있습니다. 이러한 객체들은 개발자가 쉽고 간편하게 공유자원의 동시접근을 막을 수 있도록 도와줍니다.
기본적인 lock의 종류
std::mutex
앞서 정리했던 mutex 역시 가장 기본적이고 핵심이 되는 부분입니다. c++의 lock은 이러한 mutex를 베이스로 개발자가 더 쉽고 간편하게 다양한 기능들을 쓸 수 있도록 도와줍니다.
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해줄수 있습니다.)
위의 예제는unique_lock의 사용 예제 입니다. 각transfer함수를 쓰레드로 실행하며 그때 인자로mutex를 가진 Box 객체를 넘겨줍니다. 각 쓰레드에서는 lock을 걸고 계좌의 돈을 옮기는 로직을 수행합니다.
하지만 이와 같은 상황에서는 주석 친 부분과 같이 unique_lock을 2줄에 걸쳐 잠그는 코드는 사용해서는 안됩니다. 이러한 코드는 문제를 발생 시 킬 수 있는 여지가 존재합니다. 만약에 2줄에 걸쳐unique_lock을 이용해 락을 건다면 이때는 양쪽 다 락이 걸리는 데드락이 발생할 수 있기 때문입니다. 여기서는std::defer_lock을 이용해 생성자에서 잠그는 것이 아니라 잠금 구조만 생성을 하고 후에 잠그도록 하였습니다.
위와 같은 상황이라면쓰레드 1(from = acc1, to = acc2)이from.m(acc1)을 락 걸고 그 다음줄인to.m(acc2)를 수행하기 전에쓰레드 2(from = acc2, to = acc1)가 첫줄에서from.m(acc2)를 건 상황이 될 수 있습니다. 이때 두 쓰레드가 그 다음줄을 수행하려고 하지만 둘다 락이 걸려있는 상태이므로 대기를 하게 됩니다. 하지만 둘다 락을 풀어 주지 않기 때문에 데드락 현상이 발생하게 됩니다.
그래서 이런상환에서는std:lock을 이용해 동시에 락을 걸어 주어야 타이밍 이슈로 데드락이 발생하지 않습니다.
I have a base classMediaand several derived classes, namelyDVD,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 warningsclass 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:
이 경고는 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.
};