it-swarm-vi.tech

Mutex ví dụ/hướng dẫn?

Tôi chưa quen với đa luồng và đang cố gắng hiểu cách thức hoạt động của mutexes. Đã làm rất nhiều Googling và Tôi đã tìm thấy một hướng dẫn tốt , nhưng nó vẫn còn một số nghi ngờ về cách thức hoạt động vì tôi đã tạo chương trình của riêng mình trong đó khóa không hoạt động.

Một cú pháp hoàn toàn không trực quan của mutex là pthread_mutex_lock( &mutex1 );, trong đó có vẻ như mutex đang bị khóa, khi điều tôi thực sự muốn khóa là một số biến khác. Liệu cú pháp này có nghĩa là khóa một mutex khóa một vùng mã cho đến khi mutex được mở khóa? Sau đó, làm thế nào để chủ đề biết rằng khu vực bị khóa? [CẬP NHẬT: Chủ đề biết rằng khu vực bị khóa, bởiĐấu kiếm bộ nhớ ]. Và không phải là một hiện tượng như vậy được gọi là phần quan trọng? [CẬP NHẬT: Các đối tượng phần quan trọng chỉ có sẵn trong Windows, trong đó các đối tượng nhanh hơn mutexes và chỉ hiển thị với luồng thực hiện nó. Mặt khác, phần quan trọng chỉ đề cập đến vùng mã được bảo vệ bởi một mutex]

Nói tóm lại, bạn có thể vui lòng trợ giúp với mutex đơn giản nhất có thể chương trình ví dụ và đơn giản nhất có thể giải thích về logic hoạt động của nó không? Tôi chắc chắn rằng điều này sẽ giúp nhiều của những người mới khác.

151
Nav

Đây là nỗ lực khiêm tốn của tôi để giải thích khái niệm này cho người mới trên khắp thế giới: (a phiên bản được mã hóa màu trên blog của tôi nữa) 

Rất nhiều người chạy đến một bốt điện thoại đơn độc (không có điện thoại di động) để nói chuyện với người thân của họ. Người đầu tiên bắt được tay nắm cửa của gian hàng, là người được phép sử dụng điện thoại. Anh ta phải giữ chặt tay nắm cửa miễn là anh ta sử dụng điện thoại, nếu không người khác sẽ nắm lấy tay cầm, ném anh ta ra và nói chuyện với vợ anh ta :) Không có hệ thống xếp hàng như vậy. Khi người đó kết thúc cuộc gọi, ra khỏi buồng và rời khỏi tay nắm cửa, người tiếp theo nắm tay nắm cửa sẽ được phép sử dụng điện thoại. 

Một chủ đề là: Mỗi người
Mutex là: Tay nắm cửa
Khóa là: Tay của người đó
Resource là: Điện thoại 

Bất kỳ luồng nào phải thực thi một số dòng mã không được sửa đổi bởi các luồng khác cùng một lúc (sử dụng điện thoại để nói chuyện với vợ), trước tiên phải có được một khóa trên mutex (nắm chặt tay nắm cửa của gian hàng ). Chỉ sau đó, một chủ đề mới có thể chạy các dòng mã đó (thực hiện cuộc gọi điện thoại). 

Khi luồng đã thực thi mã đó, nó sẽ giải phóng khóa trên mutex để một luồng khác có thể có được khóa trên mutex (những người khác có thể truy cập vào bốt điện thoại).

[Khái niệm có một mutex là hơi vô lý khi xem xét truy cập độc quyền trong thế giới thực, nhưng trong thế giới lập trình, tôi đoán không có cách nào khác để cho các luồng khác 'thấy' rằng một luồng đã thực hiện một số dòng về mã. Có các khái niệm về các biến đổi đệ quy, v.v., nhưng ví dụ này chỉ nhằm cho bạn thấy khái niệm cơ bản. Hy vọng ví dụ này cho bạn một bức tranh rõ ràng về khái niệm này.

Với luồng C++ 11:

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

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man lets go of the door handle and unlocks the door
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

Biên dịch và chạy bằng g++ -std=c++0x -pthread -o thread thread.cpp;./thread

Thay vì sử dụng rõ ràng lockunlock, bạn có thể sử dụng dấu ngoặc như được hiển thị ở đây , nếu bạn đang sử dụng khóa có phạm vi vì lợi thế mà nó cung cấp . Khóa phạm vi có một hiệu suất trên đầu mặc dù. 

Với TBB: Bạn sẽ cần TBB để chạy chương trình bên dưới, nhưng mục đích của việc đăng mã TBB là bạn hiểu trình tự khóa và mở khóa chỉ bằng cách nhìn vào đơn giản mã (có thể hiển thị khóa phạm vi bằng cách không sử dụng thu thập và phát hành - cũng là ngoại lệ an toàn -, nhưng điều này rõ ràng hơn).

#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;

typedef mutex myMutex;
static myMutex sm;
int i = 0;

void someFunction() 
{ 
      //Note: Since a scoped lock is used below, you should know that you 
      //can specify a scope for the mutex using curly brackets, instead of 
      //using lock.acquire() and lock.release(). The lock will automatically 
      //get released when program control goes beyond the scope.
      myMutex::scoped_lock lock;//create a lock
      lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
         //***only one thread can access the lines from here...***
         ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
         sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
         std::cout<<"In someFunction "<<i<<"\n";
         //***...to here***
      lock.release();//releases the lock (duh!)      
}

int main()
{
   tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
   tbb_thread my_thread2(someFunction);
   tbb_thread my_thread3(someFunction);

   my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
   my_thread2.join();
   my_thread3.join();
}

Lưu ý rằng tbb_thread.h không được dùng nữa. Sự thay thế được hiển thị ở đây

233
Nav

Mặc dù một mutex có thể được sử dụng để giải quyết các vấn đề khác, lý do chính khiến chúng tồn tại là để cung cấp loại trừ lẫn nhau và do đó giải quyết cái được gọi là điều kiện chủng tộc. Khi hai (hoặc nhiều) chủ đề hoặc quy trình đang cố gắng truy cập cùng một biến đồng thời, chúng ta có tiềm năng cho một điều kiện cuộc đua. Hãy xem xét các mã sau đây

//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

Các phần bên trong của chức năng này trông rất đơn giản. Đó chỉ là một tuyên bố. Tuy nhiên, một ngôn ngữ giả hội đồng tương đương điển hình có thể là:

load i from memory into a register
add 1 to i
store i back into memory

Bởi vì tất cả các hướng dẫn ngôn ngữ hội tương đương đều được yêu cầu để thực hiện thao tác tăng trên i, nên chúng tôi nói rằng tăng i là một hoạt động không phải là đạn. Một hoạt động nguyên tử là một hoạt động có thể được hoàn thành trên phần cứng với một người bảo đảm không bị gián đoạn một khi việc thực hiện lệnh đã bắt đầu. Tăng i bao gồm một chuỗi gồm 3 hướng dẫn nguyên tử. Trong một hệ thống đồng thời trong đó một số luồng đang gọi hàm, các vấn đề phát sinh khi một luồng đọc hoặc ghi không đúng lúc. Hãy tưởng tượng chúng ta có hai luồng chạy simultaneoulsy và một luồng gọi hàm này ngay lập tức. Chúng ta cũng nói rằng chúng ta đã khởi tạo thành 0. Ngoài ra, giả sử rằng chúng ta có nhiều thanh ghi và hai luồng đang sử dụng các thanh ghi hoàn toàn khác nhau, do đó sẽ không có xung đột. Thời gian thực tế của những sự kiện này có thể là:

thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1

Điều xảy ra là chúng tôi có hai luồng tăng dần đồng thời, hàm của chúng tôi được gọi hai lần, nhưng kết quả không phù hợp với thực tế đó. Có vẻ như chức năng chỉ được gọi một lần. Điều này là do tính nguyên tử bị "phá vỡ" ở cấp độ máy, có nghĩa là các luồng có thể làm gián đoạn lẫn nhau hoặc làm việc sai thời điểm.

Chúng ta cần một cơ chế để giải quyết điều này. Chúng ta cần áp đặt một số thứ tự cho các hướng dẫn ở trên. Một cơ chế phổ biến là chặn tất cả các luồng trừ một. Pthread mutex sử dụng cơ chế này.

Bất kỳ luồng nào phải thực thi một số dòng mã có thể sửa đổi một cách không an toàn các giá trị được chia sẻ bởi các luồng khác cùng một lúc (sử dụng điện thoại để nói chuyện với vợ), trước tiên nên được thực hiện khóa trên mutex. Theo cách này, bất kỳ luồng nào yêu cầu quyền truy cập vào dữ liệu được chia sẻ đều phải thông qua khóa mutex. Chỉ sau đó một luồng sẽ có thể thực thi mã. Phần mã này được gọi là phần quan trọng.

Khi luồng đã thực hiện phần quan trọng, nó sẽ giải phóng khóa trên mutex để một luồng khác có thể có được khóa trên mutex.

Khái niệm có một mutex có vẻ hơi kỳ quặc khi xem xét con người tìm kiếm quyền truy cập độc quyền vào các vật thể thực, nhưng khi lập trình, chúng ta phải có chủ ý. Các chủ đề và quy trình đồng thời không có sự giáo dục về văn hóa và xã hội mà chúng ta làm, vì vậy chúng ta phải buộc họ chia sẻ dữ liệu độc đáo.

Về mặt kỹ thuật, làm thế nào để một mutex hoạt động? Nó không phải chịu các điều kiện chủng tộc giống như chúng ta đã đề cập trước đó? Không phải pthread_mutex_lock () phức tạp hơn một chút mà là một bước tăng đơn giản của một biến?

Về mặt kỹ thuật, chúng tôi cần một số hỗ trợ phần cứng để giúp chúng tôi. Các nhà thiết kế phần cứng cung cấp cho chúng tôi các hướng dẫn máy làm nhiều việc nhưng được bảo đảm là nguyên tử. Một ví dụ kinh điển của một hướng dẫn như vậy là test-and-set (TAS). Khi thử lấy khóa trên tài nguyên, chúng tôi có thể sử dụng TAS có thể kiểm tra xem giá trị trong bộ nhớ có bằng 0. Nếu đó là tín hiệu của chúng tôi rằng tài nguyên đó đang được sử dụng và chúng tôi không làm gì cả (hoặc chính xác hơn Chúng tôi chờ đợi bằng một cơ chế nào đó. Một mutth pthreads sẽ đưa chúng tôi vào một hàng đợi đặc biệt trong hệ điều hành và sẽ thông báo cho chúng tôi khi tài nguyên có sẵn. . Nếu giá trị trong bộ nhớ không phải là 0, TAS sẽ đặt vị trí thành một giá trị khác 0 mà không sử dụng bất kỳ hướng dẫn nào khác. Nó giống như kết hợp hai hướng dẫn hội thành 1 để cung cấp cho chúng ta tính nguyên tử. Do đó, kiểm tra và thay đổi giá trị (nếu thay đổi là phù hợp) không thể bị gián đoạn một khi nó đã bắt đầu. Chúng ta có thể xây dựng các mutexes trên đầu của một hướng dẫn như vậy.

Lưu ý: một số phần có thể xuất hiện tương tự như câu trả lời trước đó. Tôi đã chấp nhận lời mời của anh ấy để chỉnh sửa, anh ấy thích cách ban đầu, vì vậy tôi đang giữ những gì tôi có được truyền vào một chút lời nói của anh ấy.

34
San Jacinto

Các chủ đề tốt nhất hướng dẫn tôi biết là ở đây:

https://computing.llnl.gov/tutorials/pthreads/

Tôi thích rằng nó được viết về API, thay vì về một triển khai cụ thể và nó đưa ra một số ví dụ đơn giản để giúp bạn hiểu về đồng bộ hóa.

11
R..

Tôi tình cờ thấy bài đăng này gần đây và nghĩ rằng nó cần một giải pháp cập nhật cho mutex c ++ 11 của thư viện chuẩn (cụ thể là std :: mutex). 

Tôi đã dán một số mã bên dưới (bước đầu tiên của tôi với một mutex - Tôi đã học được sự tương tranh trên win32 với HANDLE, SetEvent, WaitForMult MônObjects, v.v.).

Vì đó là nỗ lực đầu tiên của tôi với std :: mutex và bạn bè, tôi rất thích xem các bình luận, đề xuất và cải tiến!

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.Push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}
7
fishfood

Hàm pthread_mutex_lock() hoặc mua lại mutex cho chuỗi gọi hoặc chặn luồng cho đến khi có thể lấy được mutex. pthread_mutex_unlock() có liên quan giải phóng mutex.

Hãy nghĩ về mutex như một hàng đợi; mọi luồng cố gắng để có được mutex sẽ được đặt ở cuối hàng đợi. Khi một luồng giải phóng mutex, luồng tiếp theo trong hàng đợi sẽ tắt và hiện đang chạy.

A phần quan trọng đề cập đến một vùng mã nơi không thể xác định được. Thường thì điều này là do nhiều luồng đang cố truy cập vào một biến được chia sẻ. Phần quan trọng không an toàn cho đến khi một số loại đồng bộ hóa được đưa ra. Khóa mutex là một hình thức đồng bộ hóa.

4
chrisaycock

Bạn phải kiểm tra biến mutex trước khi sử dụng vùng được bảo vệ bởi mutex. Vì vậy, pthread_mutex_lock () của bạn có thể (tùy thuộc vào việc triển khai) đợi cho đến khi mutex1 được phát hành hoặc trả về giá trị cho biết rằng không thể lấy được khóa nếu người khác đã khóa nó.

Mutex thực sự chỉ là một semaphore đơn giản hóa. Nếu bạn đọc về họ và hiểu họ, bạn sẽ hiểu về mutexes. Có một số câu hỏi liên quan đến mutexes và semaphores trong SO. Sự khác biệt giữa semaphore nhị phân và mutex , Khi nào chúng ta nên sử dụng mutex và khi nào chúng ta nên sử dụng semaphore v.v. Ví dụ về nhà vệ sinh trong liên kết đầu tiên là một ví dụ tốt như người ta có thể nghĩ đến. Tất cả các mã làm là để kiểm tra nếu khóa có sẵn và nếu có, hãy bảo lưu nó. Lưu ý rằng bạn không thực sự dự trữ nhà vệ sinh, nhưng chìa khóa.

3
Makis

VÍ DỤ SEMAPHORE ::

sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0

sem_wait(&m);
// critical section here
sem_post(&m);

Tham khảo: http://pages.cs.wisc.edu/~remzi/Class/537/Fall2008/Notes/threads-semaphores.txt

2
parasrish

Đối với những người tìm kiếm ví dụ mutex shortex:

#include <mutex>
using namespace std;

int main() {
    mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();

    return 0;
}
0
nathangeorge1