Tôi đã nghe những từ này liên quan đến lập trình đồng thời, nhưng sự khác biệt giữa chúng là gì?
Tôi đã nghe những từ này liên quan đến lập trình đồng thời, nhưng sự khác biệt giữa chúng là gì?
Câu trả lời:
Khóa chỉ cho phép một luồng vào phần bị khóa và khóa không được chia sẻ với bất kỳ quy trình nào khác.
Một mutex giống như một khóa nhưng nó có thể là toàn hệ thống (được chia sẻ bởi nhiều quá trình).
Một semaphore thực hiện giống như một mutex nhưng cho phép x số luồng để nhập, ví dụ, điều này có thể được sử dụng để giới hạn số lượng cpu, io hoặc ram các tác vụ chuyên sâu chạy cùng lúc.
Đối với một bài viết chi tiết hơn về sự khác biệt giữa mutex và semaphore đọc tại đây .
Bạn cũng có khóa đọc / ghi cho phép không giới hạn số lượng người đọc hoặc 1 người viết tại bất kỳ thời điểm nào.
Có rất nhiều quan niệm sai lầm liên quan đến những từ này.
Đây là từ một bài viết trước ( https://stackoverflow.com/a/24582076/3163691 ) phù hợp tuyệt vời ở đây:
1) Phần quan trọng = Đối tượng người dùng được sử dụng để cho phép thực thi chỉ một luồng hoạt động từ nhiều luồng khác trong một quy trình . Các chủ đề không được chọn khác (@ có được đối tượng này) được đưa vào chế độ ngủ .
[Không có khả năng liên tiến trình, đối tượng rất nguyên thủy].
2) Mutex Semaphore (còn gọi là Mutex) = Đối tượng Kernel được sử dụng để cho phép thực thi chỉ một luồng hoạt động từ nhiều luồng khác, trong số các quy trình khác nhau . Các chủ đề không được chọn khác (@ có được đối tượng này) được đưa vào chế độ ngủ . Đối tượng này hỗ trợ quyền sở hữu luồng, thông báo chấm dứt luồng, đệ quy (nhiều cuộc gọi 'nhận' từ cùng một luồng) và 'tránh đảo ngược ưu tiên'.
[Khả năng xử lý, rất an toàn để sử dụng, một loại đối tượng đồng bộ hóa 'mức cao'].
3) Đếm Semaphore (còn gọi là Semaphore) = Đối tượng Kernel được sử dụng để cho phép thực thi một nhóm các luồng hoạt động từ nhiều người khác. Các chủ đề không được chọn khác (@ có được đối tượng này) được đưa vào chế độ ngủ .
[Tuy nhiên, khả năng xử lý của bộ xử lý không an toàn lắm vì nó thiếu các thuộc tính 'mutex': thông báo chấm dứt luồng, đệ quy?, 'Tránh đảo ngược ưu tiên'?, V.v.].
4) Và bây giờ, nói về 'spinlocks', đầu tiên một số định nghĩa:
Vùng quan trọng = Vùng nhớ được chia sẻ bởi 2 hoặc nhiều quá trình.
Khóa = Một biến có giá trị cho phép hoặc từ chối lối vào 'vùng quan trọng'. (Nó có thể được thực hiện như một 'cờ boolean' đơn giản).
Bận rộn chờ đợi = Kiểm tra liên tục một biến cho đến khi một số giá trị xuất hiện.
Cuối cùng:
Spin-lock (hay còn gọi là Spinlock) = Một khóa sử dụng chờ bận . (Việc mua khóa được thực hiện bởi xchg hoặc các hoạt động nguyên tử tương tự ).
[Không có luồng ngủ, chủ yếu chỉ được sử dụng ở cấp độ kernel. Không hiệu quả đối với mã cấp độ người dùng].
Như một bình luận cuối cùng, tôi không chắc nhưng tôi có thể đặt cược cho bạn một số tiền lớn mà 3 đối tượng đồng bộ hóa đầu tiên ở trên (# 1, # 2 và # 3) sử dụng con thú đơn giản này (# 4) như một phần của việc thực hiện chúng.
Chúc bạn ngày mới tốt lành!.
Người giới thiệu:
Khái niệm thời gian thực cho các hệ thống nhúng của Qing Li với Caroline Yao (Sách CMP).
-Hodern Operations Systems (thứ 3) của Andrew Tanenbaum (Pearson Education International).
-Các ứng dụng lập trình cho Microsoft Windows (thứ 4) của Jeffrey Richter (Dòng lập trình Microsoft).
Ngoài ra, bạn có thể xem qua: https://stackoverflow.com/a/24586804/3163691
Hầu hết các vấn đề có thể được giải quyết bằng cách sử dụng (i) chỉ khóa, (ii) chỉ là ngữ nghĩa, ... hoặc (iii) kết hợp cả hai! Như bạn có thể đã phát hiện ra, chúng rất giống nhau: cả hai đều ngăn chặn các điều kiện chủng tộc , cả hai hoạt động acquire()
/ release()
hoạt động, cả hai đều khiến 0 hoặc nhiều luồng bị chặn / nghi ngờ ... Thực sự, sự khác biệt quan trọng chỉ nằm ở cách chúng khóa và mở khóa .
Đối với cả hai khóa / semaphores, cố gắng gọi acquire()
trong khi nguyên thủy ở trạng thái 0 làm cho luồng gọi bị treo. Đối với khóa - các nỗ lực để có được khóa ở trạng thái 1 là thành công. Đối với semaphores - các nỗ lực để có được khóa ở các trạng thái {1, 2, 3, ...} đều thành công.
Đối với các khóa ở trạng thái 0, nếu cùng một luồng đã gọi trước đó acquire()
, bây giờ gọi phát hành, thì việc phát hành thành công. Nếu một luồng khác đã thử điều này - đó là tùy thuộc vào việc triển khai / thư viện như những gì xảy ra (thường là bỏ qua nỗ lực hoặc lỗi được ném). Đối với semaphores ở trạng thái 0, bất kỳ luồng cũng có thể gọi phát hành và nó sẽ thành công (bất kể luồng nào được sử dụng trước đó để đặt semaphore ở trạng thái 0).
Từ các cuộc thảo luận trước, chúng ta có thể thấy rằng các khóa có khái niệm về chủ sở hữu (chủ đề duy nhất có thể gọi phát hành là chủ sở hữu), trong khi semaphores không có chủ sở hữu (bất kỳ chủ đề nào cũng có thể gọi phát hành trên semaphore).
Điều gây ra nhiều nhầm lẫn là, trong thực tế, chúng có nhiều biến thể của định nghĩa cấp cao này.
Các biến thể quan trọng cần xem xét :
acquire()
/ release()
được gọi là? - [Khác nhau ồ ạt ]Những điều này phụ thuộc vào cuốn sách / giảng viên / ngôn ngữ / thư viện / môi trường của bạn.
Đây là một chuyến tham quan nhanh lưu ý cách một số ngôn ngữ trả lời các chi tiết này.
pthread_mutex_t
. Theo mặc định, chúng không thể được chia sẻ với bất kỳ quy trình nào khác ( PTHREAD_PROCESS_PRIVATE
), tuy nhiên, mutex có một thuộc tính được gọi là chia sẻ . Khi được đặt, vì vậy mutex được chia sẻ giữa các process ( PTHREAD_PROCESS_SHARED
).sem_t
. Tương tự như mutexes, semaphores có thể được chia sẻ giữa các nhóm của nhiều tiến trình hoặc giữ riêng tư cho các luồng của một tiến trình đơn lẻ. Điều này phụ thuộc vào đối số chia sẻ được cung cấp cho sem_init
.threading.RLock
) là chủ yếu giống như C / C ++ pthread_mutex_t
s. Cả hai đều là reentrant . Điều này có nghĩa là chúng chỉ có thể được mở khóa bởi cùng một chủ đề đã khóa nó. Đó là trường hợp sem_t
semaphores, threading.Semaphore
semaphores và theading.Lock
lock không được reentrant - vì đó là trường hợp bất kỳ chủ đề nào có thể thực hiện mở khóa / xuống semaphore.threading.Semaphore
) hầu hết giống như sem_t
. Mặc dù với sem_t
, một hàng đợi id id được sử dụng để ghi nhớ thứ tự các luồng bị chặn khi cố khóa nó trong khi nó bị khóa. Khi một luồng mở ra một semaphore, luồng đầu tiên trong hàng đợi (nếu có) được chọn làm chủ sở hữu mới. Mã định danh luồng được đưa ra khỏi hàng đợi và semaphore sẽ bị khóa lại. Tuy nhiên, với threading.Semaphore
, một tập hợp được sử dụng thay cho hàng đợi, vì vậy thứ tự các luồng bị chặn không được lưu trữ - bất kỳ luồng nào trong tập hợp có thể được chọn làm chủ sở hữu tiếp theo.java.util.concurrent.ReentrantLock
) là chủ yếu giống như C / C ++ pthread_mutex_t
's, và Python threading.RLock
ở chỗ nó cũng thực hiện một khóa reentrant. Chia sẻ khóa giữa các quy trình khó hơn trong Java do JVM đóng vai trò trung gian. Nếu một chủ đề cố gắng mở khóa một khóa mà nó không sở hữu, một chủ đề IllegalMonitorStateException
sẽ bị ném.java.util.concurrent.Semaphore
) chủ yếu giống như sem_t
và threading.Semaphore
. Hàm tạo cho các semaphores Java chấp nhận một tham số boolean công bằng kiểm soát việc sử dụng một tập hợp (sai) hay hàng đợi (true) để lưu trữ các luồng chờ. Về lý thuyết, semaphores thường được thảo luận, nhưng trong thực tế, semaphores không được sử dụng nhiều. Một semaphore chỉ giữ trạng thái của một số nguyên, do đó, nó thường không linh hoạt và nhiều thứ cần thiết cùng một lúc - gây khó khăn trong việc hiểu mã. Ngoài ra, thực tế là bất kỳ chủ đề có thể phát hành một semaphore đôi khi không mong muốn. Thay vào đó, nhiều nguyên tắc / trừu tượng đồng bộ hóa hướng đối tượng / mức độ cao hơn như "biến điều kiện" và "màn hình" được sử dụng thay thế.
Hãy xem Hướng dẫn đa luồng của John Kopplin.
Trong phần Đồng bộ hóa giữa các Chủ đề , anh giải thích sự khác biệt giữa các sự kiện, khóa, mutex, semaphore, bộ đếm thời gian chờ
Một mutex chỉ có thể được sở hữu bởi một luồng tại một thời điểm, cho phép các luồng phối hợp truy cập loại trừ lẫn nhau vào một tài nguyên được chia sẻ
Các đối tượng phần quan trọng cung cấp đồng bộ hóa tương tự như đối tượng được cung cấp bởi các đối tượng mutex, ngoại trừ các đối tượng phần quan trọng chỉ có thể được sử dụng bởi các luồng của một quy trình duy nhất
Một sự khác biệt khác giữa một mutex và một phần quan trọng là nếu đối tượng của phần quan trọng hiện đang được sở hữu bởi một luồng khác,
EnterCriticalSection()
chờ đợi vô thời hạn quyền sở hữu trong khiWaitForSingleObject()
, được sử dụng với mutex, cho phép bạn chỉ định thời gian chờMột semaphore duy trì số đếm giữa 0 và một số giá trị tối đa, giới hạn số lượng luồng đang truy cập đồng thời một tài nguyên được chia sẻ.
Tôi sẽ cố gắng che nó bằng các ví dụ:
Khóa: Một ví dụ mà bạn sẽ sử dụng lock
sẽ là một từ điển được chia sẻ trong đó các mục (phải có khóa duy nhất) được thêm vào.
Khóa sẽ đảm bảo rằng một luồng không nhập vào cơ chế mã đang kiểm tra mục có trong từ điển trong khi một luồng khác (nằm trong phần quan trọng) đã vượt qua kiểm tra này và đang thêm mục. Nếu một luồng khác cố gắng nhập mã bị khóa, nó sẽ đợi (bị chặn) cho đến khi đối tượng được giải phóng.
private static readonly Object obj = new Object();
lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
if (!sharedDict.ContainsKey(key))
{
sharedDict.Add(item);
}
}
Semaphore: Giả sử bạn có một nhóm kết nối, sau đó một luồng có thể dự trữ một phần tử trong nhóm bằng cách đợi semaphore nhận được kết nối. Sau đó, nó sử dụng kết nối và khi công việc hoàn thành sẽ giải phóng kết nối bằng cách phát hành semaphore.
Ví dụ mã mà tôi yêu thích là một trong những bouncer được đưa ra bởi @Patric - đây là:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TheNightclub
{
public class Program
{
public static Semaphore Bouncer { get; set; }
public static void Main(string[] args)
{
// Create the semaphore with 3 slots, where 3 are available.
Bouncer = new Semaphore(3, 3);
// Open the nightclub.
OpenNightclub();
}
public static void OpenNightclub()
{
for (int i = 1; i <= 50; i++)
{
// Let each guest enter on an own thread.
Thread thread = new Thread(new ParameterizedThreadStart(Guest));
thread.Start(i);
}
}
public static void Guest(object args)
{
// Wait to enter the nightclub (a semaphore to be released).
Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
Bouncer.WaitOne();
// Do some dancing.
Console.WriteLine("Guest {0} is doing some dancing.", args);
Thread.Sleep(500);
// Let one guest out (release one semaphore).
Console.WriteLine("Guest {0} is leaving the nightclub.", args);
Bouncer.Release(1);
}
}
}
Mutex Nó là khá nhiều Semaphore(1,1)
và thường được sử dụng trên toàn cầu (ứng dụng rộng rãi nếu không được cho lock
là phù hợp hơn). Người ta sẽ sử dụng toàn cục Mutex
khi xóa nút khỏi danh sách có thể truy cập toàn cầu (điều cuối cùng bạn muốn một luồng khác thực hiện điều gì đó trong khi bạn đang xóa nút). Khi bạn có được Mutex
nếu các luồng khác nhau cố gắng thu được giống nhau, Mutex
nó sẽ được chuyển sang chế độ ngủ cho đến khi chủ đề CÙNG thu được các Mutex
bản phát hành.
Ví dụ điển hình về việc tạo mutex toàn cầu là bởi @deepee
class SingleGlobalInstance : IDisposable
{
public bool hasHandle = false;
Mutex mutex;
private void InitMutex()
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
string mutexId = string.Format("Global\\{{{0}}}", appGuid);
mutex = new Mutex(false, mutexId);
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
mutex.SetAccessControl(securitySettings);
}
public SingleGlobalInstance(int timeOut)
{
InitMutex();
try
{
if(timeOut < 0)
hasHandle = mutex.WaitOne(Timeout.Infinite, false);
else
hasHandle = mutex.WaitOne(timeOut, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
}
public void Dispose()
{
if (mutex != null)
{
if (hasHandle)
mutex.ReleaseMutex();
mutex.Dispose();
}
}
}
sau đó sử dụng như:
using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
//Only 1 of these runs at a time
GlobalNodeList.Remove(node)
}
Hy vọng điều này sẽ giúp bạn tiết kiệm thời gian.
Wikipedia có một phần tuyệt vời về sự khác biệt giữa Semaphores và Mutexes :
Một mutex về cơ bản là giống như một semaphore nhị phân và đôi khi sử dụng cùng một triển khai cơ bản. Sự khác biệt giữa chúng là:
Mutexes có một khái niệm về một chủ sở hữu, đó là quá trình khóa mutex. Chỉ có quá trình khóa mutex mới có thể mở khóa nó. Ngược lại, một semaphore không có khái niệm về chủ sở hữu. Bất kỳ quá trình có thể mở khóa một semaphore.
Không giống như semaphores, mutexes cung cấp an toàn đảo ngược ưu tiên. Vì mutex biết chủ sở hữu hiện tại của nó, có thể thúc đẩy mức độ ưu tiên của chủ sở hữu bất cứ khi nào một nhiệm vụ ưu tiên cao hơn bắt đầu chờ đợi trên mutex.
Mutexes cũng cung cấp an toàn xóa, trong đó quá trình giữ mutex có thể vô tình bị xóa. Semaphores không cung cấp điều này.
Tôi hiểu rằng một mutex chỉ được sử dụng trong một quy trình duy nhất, nhưng trên nhiều luồng của nó, trong khi một semaphore có thể được sử dụng trên nhiều quy trình và trên các chuỗi chủ đề tương ứng của chúng.
Ngoài ra, một mutex là nhị phân (nó bị khóa hoặc mở khóa), trong khi một semaphore có khái niệm đếm hoặc một hàng đợi nhiều hơn một yêu cầu khóa và mở khóa.
Ai đó có thể xác minh lời giải thích của tôi? Tôi đang nói về ngữ cảnh của Linux, cụ thể là Red Hat Enterprise Linux phiên bản 6 (RHEL), sử dụng kernel 2.6.32.
Sử dụng lập trình C trên một biến thể Linux làm ví dụ cơ bản.
Khóa:
• Thường là một nhị phân cấu trúc rất đơn giản trong hoạt động hoặc bị khóa hoặc mở khóa
• Không có khái niệm về quyền sở hữu chủ đề, ưu tiên, trình tự, vv
• Thường là khóa xoay trong đó chỉ liên tục kiểm tra tính khả dụng của khóa.
• Thường dựa vào các hoạt động nguyên tử, ví dụ: Thử và đặt, so sánh và trao đổi, tìm nạp và thêm, v.v.
• Thường yêu cầu hỗ trợ phần cứng cho hoạt động nguyên tử.
Khóa tệp:
• Thường được sử dụng để phối hợp truy cập vào một tệp thông qua nhiều quy trình.
• Nhiều quy trình có thể giữ khóa đọc tuy nhiên khi bất kỳ quy trình nào giữ khóa ghi thì không có quy trình nào khác được phép có được khóa đọc hoặc ghi.
• Ví dụ: đàn, fcntl, v.v.
Đột biến:
• Các lệnh gọi hàm Mutex thường hoạt động trong không gian kernel và dẫn đến các cuộc gọi hệ thống.
• Nó sử dụng khái niệm quyền sở hữu. Chỉ có chủ đề hiện đang giữ mutex có thể mở khóa nó.
• Mutex không được đệ quy (Ngoại lệ: PTHREAD_MUTEX_RECURSIVE).
• Thường được sử dụng trong Hiệp hội với Biến điều kiện và được chuyển làm đối số cho ví dụ: pthread_cond_signal, pthread_cond_wait, v.v.
• Một số hệ thống UNIX cho phép mutex được sử dụng bởi nhiều quy trình mặc dù điều này có thể không được thi hành trên tất cả các hệ thống.
Semaphore:
• Đây là một số nguyên duy trì hạt nhân có giá trị không được phép giảm xuống dưới 0.
• Nó có thể được sử dụng để đồng bộ hóa các quy trình.
• Giá trị của semaphore có thể được đặt thành giá trị lớn hơn 1 trong trường hợp đó giá trị thường cho biết số lượng tài nguyên có sẵn.
• Một semaphore có giá trị được giới hạn ở 1 và 0 được gọi là một semaphore nhị phân.
Supporting ownership
, maximum number of processes share lock
Và maximum number of allowed processes/threads in critical section
ba yếu tố chính mà xác định tên / loại của đối tượng đồng thời với tên chung của lock
. Vì giá trị của các yếu tố này là nhị phân (có hai trạng thái), chúng tôi có thể tóm tắt chúng trong bảng giống như sự thật 3 * 8.
X Y Z Name
--- --- --- ------------------------
0 ∞ ∞ Semaphore
0 ∞ 1 Binary Semaphore
0 1 ∞ SemaphoreSlim
0 1 1 Binary SemaphoreSlim(?)
1 ∞ ∞ Recursive-Mutex(?)
1 ∞ 1 Mutex
1 1 ∞ N/A(?)
1 1 1 Lock/Monitor
Vui lòng chỉnh sửa hoặc mở rộng bảng này, tôi đã đăng nó dưới dạng bảng ascii để có thể chỉnh sửa :)