加入注释 #25

Open
VichyTong wants to merge 1 commits from VichyTong/mindspore2022:master into master
3 changed files with 263 additions and 66 deletions

View File

@ -14,12 +14,16 @@
* limitations under the License.
*/
// Include the header file "api_register.h" from the "common/pybind_api" directory
#include "include/common/pybind_api/api_register.h"
namespace mindspore {
// Define the member function GetSingleton() of the class PybindDefineRegister
PybindDefineRegister &PybindDefineRegister::GetSingleton() {
// Create a static instance of PybindDefineRegister using the default constructor
static PybindDefineRegister instance{};
// Return the static instance
return instance;
}

View File

@ -22,68 +22,104 @@
#include <algorithm>
#include "utils/convert_utils_base.h"
// Start of the namespace "mindspore"
namespace mindspore {
DuplexPipe::~DuplexPipe() {
// pid_ < 0 means the child process is invalid or closed, pid_ == 0 means this process is child
if (pid_ > 0) {
(void)kill(pid_, SIGKILL);
// Destructor for the DuplexPipe class
DuplexPipe::~DuplexPipe() {
// Check if the child process is valid or closed
// pid_ < 0 means the child process is invalid or closed
// pid_ == 0 means this process is the child process
if (pid_ > 0) {
// Send the SIGKILL signal to terminate the child process
(void)kill(pid_, SIGKILL);
}
}
}
} // End of the namespace "mindspore"
// Open a duplex pipe by creating two pipes, one for each direction of communication
int DuplexPipe::Open(const std::initializer_list<std::string> &arg_list, bool append_fds) {
// Create the first pipe
if (pipe(fd1_) == -1) {
// If the pipe creation fails, log an error message with the error number
DP_EXCEPTION << "pipe 1 failed, errno: " << errno;
}
// Create the second pipe
if (pipe(fd2_) == -1) {
// If the pipe creation fails, close the first pipe and log an error message with the error number
close(fd1_[0]);
close(fd1_[1]);
DP_EXCEPTION << "pipe 2 failed, errno: " << errno;
}
}
// Create a child process using fork() and store the process ID in pid_
pid_ = fork();
// Check if fork() failed
if (pid_ < 0) {
// Close file descriptors to prevent resource leaks
close(fd1_[0]);
close(fd1_[1]);
close(fd2_[0]);
close(fd2_[1]);
// Log an error message with the error code
DP_EXCEPTION << "fork failed, errno: " << errno;
} else if (pid_ == 0) { // Remote process
// Here cannot record log before execvp called, because glog will warn "File Exist" if log to file.
}
// Check if the process is the child process
else if (pid_ == 0) { // Remote process
// Save the current standard output and standard input file descriptors
remote_stdout_ = dup(STDOUT_FILENO);
remote_stdin_ = dup(STDIN_FILENO);
// Close unnecessary file descriptors
close(fd1_[1]);
close(fd2_[0]);
// Check if append_fds is false
if (!append_fds) {
// Redirect the standard input and standard output to the pipe file descriptors
dup2(fd1_[0], STDIN_FILENO);
dup2(fd2_[1], STDOUT_FILENO);
}
// Create a vector to store the arguments for execvp
std::vector<const char *> args;
// Convert each argument from a string to a const char * and add it to the vector
std::transform(arg_list.begin(), arg_list.end(), std::back_inserter(args),
[](const std::string &arg) -> const char * { return arg.c_str(); });
if (append_fds) {
std::string fd10 = std::to_string(fd1_[0]).c_str();
args.emplace_back(fd10.c_str());
std::string fd21 = std::to_string(fd2_[1]).c_str();
args.emplace_back(fd21.c_str());
}
args.emplace_back(nullptr);
if (execvp(args[0], const_cast<char *const *>(&args[0])) == -1) {
DP_EXCEPTION << "execute " << args[0] << " failed, errno: " << errno;
}
} else { // Local process
DP_INFO << "Local process, id: " << getpid() << ", " << fd2_[0] << "/" << fd1_[1];
local_stdout_ = dup(STDOUT_FILENO);
local_stdin_ = dup(STDIN_FILENO);
close(fd1_[0]);
close(fd2_[1]);
if (append_fds) { // If append_fds is true
std::string fd10 = std::to_string(fd1_[0]).c_str(); // Convert the integer fd1_[0] to a string
args.emplace_back(fd10.c_str()); // Add the converted string to the args vector
std::string fd21 = std::to_string(fd2_[1]).c_str(); // Convert the integer fd2_[1] to a string
args.emplace_back(fd21.c_str()); // Add the converted string to the args vector
}
args.emplace_back(nullptr); // Add a nullptr to the end of the args vector
if (execvp(args[0], const_cast<char *const *>(&args[0])) == -1) { // Execute the command specified by args[0] with the arguments in args
DP_EXCEPTION << "execute " << args[0] << " failed, errno: " << errno; // If execvp returns -1, log an error message with the failed command and the value of errno
}
} else { // If append_fds is false, i.e., local process
DP_INFO << "Local process, id: " << getpid() << ", " << fd2_[0] << "/" << fd1_[1]; // Log an information message with the process ID and the values of fd2_[0] and fd1_[1]
local_stdout_ = dup(STDOUT_FILENO); // Duplicate the file descriptor for standard output and store it in local_stdout_
local_stdin_ = dup(STDIN_FILENO); // Duplicate the file descriptor for standard input and store it in local_stdin_
close(fd1_[0]); // Close the read end of the pipe
close(fd2_[1]); // Close the write end of the pipe
// Create a shared pointer to a SignalHandler object using std::make_shared
// Pass weak_from_this() as the first argument, which returns a weak pointer to the current object
// Pass &pid_ as the second argument, which is the address of the pid_ variable
signal_handler_ = std::make_shared<SignalHandler>(weak_from_this(), &pid_);
}
// Return 0 to indicate successful program termination
return 0;
}
void DuplexPipe::Write(const std::string &buf, bool flush) const {
// Write the string into pipe
// Write the string into the pipe
if (write(fd1_[1], buf.data(), buf.size()) == -1) {
DP_ERROR << "write failed, errno: " << errno;
return;
@ -98,110 +134,175 @@ void DuplexPipe::Write(const std::string &buf, bool flush) const {
DP_DEBUG << "<< [" << buf << "]";
}
// Function to read a string from a pipe
std::string DuplexPipe::Read() {
// Read the string from pipe
std::string buf;
// Read one line or multiple lines
while (1) {
SetTimeOut();
ssize_t size = read(fd2_[0], c_buf_, kBufferSize); // MAYBE BLOCKED, Till reading something
if (size <= 0) {
std::string buf; // Create an empty string to store the read data
while (1) { // Loop indefinitely until a break statement is encountered
SetTimeOut(); // Set a timeout for reading from the pipe
ssize_t size = read(fd2_[0], c_buf_, kBufferSize); // Read data from the pipe into a buffer
if (size <= 0) { // If no data is read or an error occurs, break the loop
break;
}
CancelTimeOut();
bool line_end = c_buf_[size - 1] == '\n';
buf.append(c_buf_, LongToSize(line_end ? size - 1 : size)); // Copy without the last '\n'
if (line_end) {
CancelTimeOut(); // Cancel the timeout as data has been successfully read
bool line_end = c_buf_[size - 1] == '\n'; // Check if the last character read is a newline character
buf.append(c_buf_, LongToSize(line_end ? size - 1 : size)); // Append the read data to the buffer, excluding the last newline character if present
if (line_end) { // If the last character read is a newline character, break the loop
break;
}
}
DP_DEBUG << ">> [" << buf << "]";
return buf;
DP_DEBUG << ">> [" << buf << "]"; // Print the read string to the debug log
return buf; // Return the read string
}
// A member function of the DuplexPipe class that writes a string to stdout with the option to flush the output
void DuplexPipe::WriteWithStdout(const std::string &buf, bool flush) {
// Redirect the standard output to the write end of the pipe
dup2(fd1_[1], STDOUT_FILENO);
// Write the string into pipe
std::cout << buf;
// If flush is true, flush the output by adding a newline character
if (flush) {
// Flush into the pipe
std::cout << std::endl;
}
// Restore the original stdout by redirecting it back to the local stdout
dup2(local_stdout_, STDOUT_FILENO);
}
// Define a member function `ReadWithStdin` of the `DuplexPipe` class that returns a `std::string`
std::string DuplexPipe::ReadWithStdin() {
std::string buf;
std::string buf; // Declare a string variable `buf` to store the input
// Redirect the read end of the pipe (`fd2_[0]`) to the standard input file descriptor (`STDIN_FILENO`)
dup2(fd2_[0], STDIN_FILENO);
// Maybe blocked
// Set a timeout for reading from `std::cin`
SetTimeOut();
// Read a line of input from `std::cin` and store it in `buf`
std::getline(std::cin, buf); // Not use 'std::cin >>' to include space
// Cancel the timeout
CancelTimeOut();
// Restore the original standard input file descriptor
dup2(local_stdin_, STDIN_FILENO);
// Return the read input stored in `buf`
return buf;
}
DuplexPipe &DuplexPipe::operator<<(const std::string &buf) {
Write(buf);
// Return a reference to the current object (this) to allow chaining of << operators
return *this;
}
DuplexPipe &DuplexPipe::operator>>(std::string &buf) {
buf = Read();
// Return a reference to the current object (DuplexPipe) to allow chaining of the operator
return *this;
}
// This function is a member function of the DuplexPipe class
// It is used to close the file descriptors and reset the process ID
void DuplexPipe::Close() noexcept {
// Close the read end of the first pipe
close(fd1_[0]);
// Close the write end of the first pipe
close(fd1_[1]);
// Close the read end of the second pipe
close(fd2_[0]);
// Close the write end of the second pipe
close(fd2_[1]);
// Reset the process ID to -1
pid_ = -1;
}
// Define the constructor for the SignalHandler class, which takes a weak pointer to a DuplexPipe object and a pointer to a pid_t variable as parameters
DuplexPipe::SignalHandler::SignalHandler(const std::weak_ptr<DuplexPipe> &dp, pid_t *pid) {
// Assign the weak pointer to the dp_ member variable
dp_ = dp;
// Assign the pid pointer to the child_pid_ member variable
child_pid_ = pid;
// Register a signal handler for the SIGCHLD signal using the SigChildHandler function
signal(SIGCHLD, SigChildHandler);
// Register a signal handler for the SIGPIPE signal using the SigPipeHandler function
signal(SIGPIPE, SigPipeHandler);
}
// Definition of the destructor for the SignalHandler class within the DuplexPipe namespace
DuplexPipe::SignalHandler::~SignalHandler() {}
// Define the SetAlarm function of the SignalHandler class, which takes an unsigned integer interval_secs as a parameter
void DuplexPipe::SignalHandler::SetAlarm(unsigned int interval_secs) const {
// Set the signal handler for the SIGALRM signal to the SigAlarmHandler function
signal(SIGALRM, SigAlarmHandler);
// Set an alarm to trigger after the specified interval_secs
alarm(interval_secs);
}
void DuplexPipe::SignalHandler::CancelAlarm() const { (void)alarm(0); }
// Define a member function named "CancelAlarm" in the class "SignalHandler" of the "DuplexPipe" class
void DuplexPipe::SignalHandler::CancelAlarm() const {
// Call the alarm function with a parameter of 0 to cancel any previously set alarm
(void)alarm(0);
}
// Define the function `SigAlarmHandler` which is a member function of the `SignalHandler` class inside the `DuplexPipe` class
void DuplexPipe::SignalHandler::SigAlarmHandler(int sig) {
// Print the signal number and the value of the `child_pid_` member variable using the DP_INFO macro
DP_INFO << "Signal: " << sig << ", child_pid_: " << child_pid_;
// Try to lock the weak pointer `dp_` to get a shared pointer to the `DuplexPipe` object
auto shared_dp = dp_.lock();
// Check if the shared pointer is not null
if (shared_dp != nullptr) {
// Call the `NotifyTimeOut` function of the `DuplexPipe` object through the shared pointer
shared_dp->NotifyTimeOut();
}
// Check if the `child_pid_` member variable is not null
if (child_pid_ != nullptr) {
// Set the value of the `child_pid_` pointer to -1
*child_pid_ = -1;
}
}
// Define the SignalHandler function for handling the SIGPIPE signal
void DuplexPipe::SignalHandler::SigPipeHandler(int sig) {
// Log the signal number and the child process ID
DP_INFO << "Signal: " << sig << ", child_pid_: " << child_pid_;
// Attempt to lock the weak pointer to the DuplexPipe object
auto shared_dp = dp_.lock();
// If the weak pointer is still valid (i.e., the DuplexPipe object still exists)
if (shared_dp != nullptr) {
// Call the NotifyFinalize function of the DuplexPipe object to notify it of the signal
shared_dp->NotifyFinalize();
}
// If the child process ID is not null
if (child_pid_ != nullptr) {
// Set the value of the child process ID to -1 to indicate that it is no longer valid
*child_pid_ = -1;
}
}
// Define the function `SigChildHandler` belonging to the `SignalHandler` class in the `DuplexPipe` namespace
void DuplexPipe::SignalHandler::SigChildHandler(int) {
int status;
// Check if the child process ID is not null
if (child_pid_ != nullptr) {
// Use the `waitpid` function to wait for the child process to change state
// The `WNOHANG` flag specifies that the function should return immediately if the child process has not changed state
// The `WUNTRACED` flag specifies that the function should also return if the child process has been stopped
(void)waitpid(*child_pid_, &status, WNOHANG | WUNTRACED);
// Set the child process ID to -1 to indicate that there is no child process
*child_pid_ = -1;
}
}
} // namespace mindspore
} // namespace mindspore

View File

@ -29,144 +29,236 @@ constexpr size_t kDeviceNum = 8;
constexpr size_t kMaxThreadNum = 23;
constexpr size_t kYieldThreshold = 1000;
// Constructor for the ThreadPool class
ThreadPool::ThreadPool() {
// Get the number of hardware cores available on the system and subtract 1
size_t process_core_num = std::thread::hardware_concurrency() - 1;
// If the calculated number of cores is less than 1, set it to 1
if (process_core_num < 1) {
process_core_num = 1;
}
// Check if the ENABLE_D or ENABLE_GPU macros are defined
#if ENABLE_D || ENABLE_GPU
// If defined, divide the calculated number of cores by kDeviceNum and assign it to max_thread_num_
max_thread_num_ = process_core_num / kDeviceNum;
#else
// If not defined, assign the calculated number of cores to max_thread_num_
max_thread_num_ = process_core_num;
#endif
// If the value of max_thread_num_ is less than 1, set it to 1
if (max_thread_num_ < 1) {
max_thread_num_ = 1;
}
// If the value of max_thread_num_ is greater than kMaxThreadNum, set it to kMaxThreadNum
if (max_thread_num_ > kMaxThreadNum) {
max_thread_num_ = kMaxThreadNum;
}
}
// Synchronize the run loop of the thread pool with the given thread context
void ThreadPool::SyncRunLoop(const std::shared_ptr<ThreadContext> &context) {
// If the thread context is null, return immediately
if (context == nullptr) {
return;
}
// Initialize a variable to keep track of the number of times the loop has yielded
size_t yield_count = 0;
// Enter an infinite loop
while (true) {
// If the exit flag is set, return from the function
if (exit_run_) {
return;
}
// ... (additional code here)
}
}
// Check if the task pointer in the context is null
if (!context->task) {
// Increment the yield count
++yield_count;
// Check if the yield count has exceeded the yield threshold
if (yield_count > kYieldThreshold) {
// Reset the yield count
yield_count = 0;
// Acquire a lock on the context's mutex
std::unique_lock<std::mutex> lock(context->mutex);
// Wait on the condition variable until either a new task is assigned or the exit flag is set
context->cond_var.wait(lock, [&context, this] { return context->task != nullptr || exit_run_; });
} else {
// Yield the current thread's execution to allow other threads to run
std::this_thread::yield();
// Continue to the next iteration of the loop
continue;
}
}
// Check if the variable "exit_run_" is true
if (exit_run_) {
return;
}
try {
// Get a reference to the task object stored in the context
auto &task = *(context->task);
// Call the task function
task();
} catch (std::exception &e) {
// If an exception is thrown, set the exception flag in the MsException singleton
MsException::Instance().SetException();
}
// Reset the yield count to 0
yield_count = 0;
// Set the task pointer in the context to nullptr
context->task = nullptr;
}
}
bool ThreadPool::SyncRun(const std::vector<Task> &tasks) {
if (tasks.empty()) {
return true;
return true; // If it is empty, return true indicating successful execution
}
// Check if there is only one task in the vector
if (tasks.size() == 1) {
auto ret = tasks[0]();
return ret == SUCCESS;
auto ret = tasks[0](); // Execute the task and store the return value
return ret == SUCCESS; // Return true if the return value is SUCCESS, false otherwise
}
std::unique_lock<std::mutex> lock(pool_mtx_);
exit_run_ = false;
size_t task_num = tasks.size();
size_t thread_num = sync_run_threads_.size();
std::unique_lock<std::mutex> lock(pool_mtx_); // Acquire a unique lock on the mutex
exit_run_ = false; // Set the exit_run_ flag to false
size_t task_num = tasks.size(); // Get the number of tasks
size_t thread_num = sync_run_threads_.size(); // Get the number of threads
// Check if the number of threads is less than the maximum thread number and less than the number of tasks
if (thread_num < max_thread_num_ && thread_num < task_num) {
auto new_thread_num = max_thread_num_;
auto new_thread_num = max_thread_num_; // Set the new thread number to the maximum thread number
// Check if the number of tasks is less than the maximum thread number
if (task_num < max_thread_num_) {
new_thread_num = task_num;
new_thread_num = task_num; // Set the new thread number to the number of tasks
}
contexts_.resize(new_thread_num);
contexts_.resize(new_thread_num); // Resize the contexts_ vector to the new thread number
// Create new thread contexts for the additional threads
for (size_t i = thread_num; i < new_thread_num; ++i) {
contexts_[i] = std::make_shared<ThreadContext>();
contexts_[i] = std::make_shared<ThreadContext>(); // Create a new ThreadContext and store it in the contexts_ vector
}
// Create a new thread and add it to the vector sync_run_threads_ by calling the SyncRunLoop function of the ThreadPool class with the current context as an argument
sync_run_threads_.emplace_back(std::thread(&ThreadPool::SyncRunLoop, this, contexts_[i]));
}
}
// If there are no contexts, return true
if (contexts_.empty()) {
return true;
}
// Set the used_thread_num to the size of the contexts_ vector
size_t used_thread_num = contexts_.size();
// If the task_num is less than the used_thread_num, set the used_thread_num to the task_num
if (task_num < used_thread_num) {
used_thread_num = task_num;
}
// Initialize the running variable to true
bool running = true;
// Initialize the task_index variable to 0
size_t task_index = 0;
// Enter a while loop that continues until running is false
while (running) {
// Set running to false
running = false;
// Iterate over the used_thread_num
for (size_t i = 0; i < used_thread_num; ++i) {
// Check if the current context is not null
MS_EXCEPTION_IF_NULL(contexts_[i]);
// Get the task_run variable from the current context
auto &task_run = contexts_[i]->task;
// If task_run is not null, set running to true
if (task_run) {
running = true;
} else if (task_index < task_num) {
}
// If task_index is less than task_num, continue to the next iteration
else if (task_index < task_num) {
// Create a lock_guard object to lock the mutex associated with the i-th context
std::lock_guard<std::mutex> task_lock(contexts_[i]->mutex);
// Assign the i-th context's task pointer to the task at task_index in the tasks array
contexts_[i]->task = &(tasks[task_index]);
// Notify one waiting thread that a task is available
contexts_[i]->cond_var.notify_one();
// Set the running flag to true to indicate that a task is being executed
running = true;
// Increment the task_index to move to the next task in the tasks array
++task_index;
}
}
// If there is at least one task running, yield the current thread to allow other threads to run
if (running) {
std::this_thread::yield();
}
}
// Return true to indicate that all tasks have been assigned and executed
return true;
}
// Define a member function named "GetInstance" for the ThreadPool class
ThreadPool &ThreadPool::GetInstance() {
// Declare a static instance of the ThreadPool class, initialized with default constructor
static ThreadPool instance{};
// Return the static instance of the ThreadPool class
return instance;
}
// A function to clear the thread pool
void ThreadPool::ClearThreadPool() {
std::lock_guard<std::mutex> sync_run_lock(pool_mtx_);
if (exit_run_) {
std::lock_guard<std::mutex> sync_run_lock(pool_mtx_); // Lock the mutex to ensure thread safety
if (exit_run_) { // If the exit flag is already set, return
return;
}
exit_run_ = true;
for (auto &context : contexts_) {
MS_EXCEPTION_IF_NULL(context);
context->cond_var.notify_one();
exit_run_ = true; // Set the exit flag to true
for (auto &context : contexts_) { // Iterate through all the contexts in the thread pool
MS_EXCEPTION_IF_NULL(context); // Check if the context is null
context->cond_var.notify_one(); // Notify the context's condition variable
}
for (auto &it : sync_run_threads_) {
if (it.joinable()) {
it.join();
for (auto &it : sync_run_threads_) { // Iterate through all the threads in the thread pool
if (it.joinable()) { // If the thread is joinable
it.join(); // Join the thread
}
}
sync_run_threads_.clear();
sync_run_threads_.clear(); // Clear the vector of threads
}
// Destructor for the ThreadPool class
ThreadPool::~ThreadPool() {
try {
ClearThreadPool();
ClearThreadPool(); // Call the ClearThreadPool function to clean up the thread pool
} catch (...) {
// If an exception is thrown during the cleanup process, catch it here
// and do nothing (since we are in a destructor and cannot throw exceptions)
// This is not ideal, but it prevents the program from crashing
// It would be better to handle the exception properly, but that is not done here
// Handle the exception properly
// exit
}
}
} // namespace common
} // namespace mindspore
} // namespace mindspore