Hàm chấp nhận một số giá trị và trả về một số giá trị khác và không làm phiền bất cứ thứ gì bên ngoài hàm, không có tác dụng phụ và do đó an toàn cho luồng. Nếu bạn muốn xem xét những thứ như cách thức hoạt động của chức năng ảnh hưởng đến mức tiêu thụ điện năng, thì đó là một vấn đề khác.
Tôi giả sử rằng bạn đang đề cập đến một máy hoàn chỉnh Turing đang thực thi một số loại ngôn ngữ lập trình được xác định rõ, trong đó các chi tiết triển khai không liên quan. Nói cách khác, không quan trọng ngăn xếp đang làm gì, nếu chức năng tôi viết bằng ngôn ngữ lập trình mà tôi chọn có thể đảm bảo tính bất biến trong giới hạn của ngôn ngữ. Tôi không nghĩ về ngăn xếp khi tôi lập trình bằng ngôn ngữ cấp cao, tôi cũng không cần phải làm vậy.
Để minh họa cách thức hoạt động của nó, tôi sẽ cung cấp một vài ví dụ đơn giản trong C #. Để những ví dụ này là đúng, chúng ta phải đưa ra một vài giả định. Đầu tiên, trình biên dịch tuân theo đặc tả C # không có lỗi và thứ hai là nó tạo ra các chương trình chính xác.
Giả sử tôi muốn một hàm đơn giản chấp nhận bộ sưu tập chuỗi và trả về một chuỗi là nối của tất cả các chuỗi trong bộ sưu tập được phân tách bằng dấu phẩy. Một triển khai đơn giản, ngây thơ trong C # có thể trông như thế này:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
string result = string.Empty;
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result += s;
else
result += ", " + s;
}
return result;
}
Ví dụ này là bất biến, prima facie. Làm thế nào để tôi biết điều đó? Vì string
đối tượng là bất biến. Tuy nhiên, việc thực hiện không lý tưởng. Bởi vì result
không thay đổi, một đối tượng chuỗi mới phải được tạo ra mỗi lần thông qua vòng lặp, thay thế đối tượng ban đầu result
trỏ đến. Điều này có thể ảnh hưởng tiêu cực đến tốc độ và gây áp lực lên bộ thu gom rác, vì nó phải dọn sạch tất cả các chuỗi bổ sung đó.
Bây giờ, hãy nói tôi làm điều này:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
var result = new StringBuilder();
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
Lưu ý rằng tôi đã thay thế string
result
bằng một đối tượng có thể thay đổi , StringBuilder
. Đây là nhiều nhanh hơn so với ví dụ đầu tiên, bởi vì một chuỗi mới không được tạo ra mỗi lần thông qua các vòng lặp. Thay vào đó, đối tượng StringBuilder chỉ thêm các ký tự từ mỗi chuỗi vào một tập hợp các ký tự và xuất ra toàn bộ ở cuối.
Hàm này có bất biến không, mặc dù StringBuilder có thể thay đổi?
Vâng, nó là. Tại sao? Bởi vì mỗi lần hàm này được gọi, một StringBuilder mới được tạo, chỉ dành cho cuộc gọi đó. Vì vậy, bây giờ chúng ta có một hàm thuần túy là an toàn luồng, nhưng chứa các thành phần có thể thay đổi.
Nhưng nếu tôi làm điều này thì sao?
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
public string ConcatenateWithCommas(ImmutableList<string> list)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
Phương pháp này có an toàn không? Không, không phải vậy. Tại sao? Bởi vì lớp hiện đang giữ trạng thái mà phương thức của tôi phụ thuộc vào. Một điều kiện cuộc đua hiện có trong phương thức: một luồng có thể sửa đổi IsFirst
, nhưng một luồng khác có thể thực hiện lần đầu tiên Append()
, trong trường hợp đó bây giờ tôi có dấu phẩy ở đầu chuỗi không được phép ở đó.
Tại sao tôi có thể muốn làm điều đó như thế này? Chà, tôi có thể muốn các chủ đề tích lũy các chuỗi vào của tôi result
mà không liên quan đến thứ tự, hoặc theo thứ tự các chủ đề đến. Có lẽ đó là một logger, ai biết?
Dù sao, để khắc phục nó, tôi đặt một lock
tuyên bố xung quanh các bộ phận của phương thức.
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
private static object locker = new object();
public string AppendWithCommas(ImmutableList<string> list)
{
lock (locker)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
}
Bây giờ nó lại an toàn cho chủ đề.
Cách duy nhất mà các phương thức bất biến của tôi có thể không thể an toàn cho luồng là nếu phương thức này bằng cách nào đó rò rỉ một phần của việc thực hiện. Điều này có thể xảy ra không? Không phải nếu trình biên dịch là chính xác và chương trình là chính xác. Tôi sẽ bao giờ cần khóa trên các phương pháp như vậy? Không.
Để biết ví dụ về cách triển khai có thể bị rò rỉ trong kịch bản tương tranh, xem tại đây .
but everything has a side effect
- Uh, không nó không. Hàm chấp nhận một số giá trị và trả về một số giá trị khác và không làm phiền bất cứ thứ gì bên ngoài hàm, không có tác dụng phụ và do đó an toàn cho luồng. Không quan trọng là máy tính sử dụng điện. Chúng ta cũng có thể nói về các tia vũ trụ đánh vào các tế bào bộ nhớ, nếu bạn muốn, nhưng hãy giữ cho cuộc tranh luận thực tế. Nếu bạn muốn xem xét những thứ như cách thức hoạt động của chức năng ảnh hưởng đến mức tiêu thụ điện năng, thì đó là một vấn đề khác với lập trình luồng an toàn.