From deeeb35c6b21030ba880457c2ab94d7ba810ca3c Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Sat, 5 Aug 2023 23:54:12 +0000 Subject: [PATCH 01/10] Fixed readme... At least, I'm pretty sure. --- README.md | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 078c2483..42d02d22 100755 --- a/README.md +++ b/README.md @@ -43,47 +43,49 @@ The **src** directory serves as the main source code directory and contains the ### To get started with our Autonomy Software development, follow these steps: #### 1. Download and install required software: - - Download and install Visual Studio Code from [here](https://code.visualstudio.com/download) - - Download and install git-scm from [here](https://git-scm.com/downloads) - - Download and install Docker from [here](https://docs.docker.com/get-docker/) + +- Download and install Visual Studio Code from [here](https://code.visualstudio.com/download) +- Download and install git-scm from [here](https://git-scm.com/downloads) +- Download and install Docker from [here](https://docs.docker.com/get-docker/) - **(Optional) Needed for container GPU support.** - - Download and install NVIDIA Container Toolkit from [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) - This is only applicable if your machine has a GPU with CUDA. Windows users should integrate WSL (Windows Subsystem for Linux) into their Docker install. [Medium](https://medium.com/htc-research-engineering-blog/nvidia-docker-on-wsl2-f891dfe34ab) has an okay guide. +(Optional) Needed for container GPU support. + - Download and install NVIDIA Container Toolkit from [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) This is only applicable if your machine has a GPU with CUDA. Windows users should integrate WSL (Windows Subsystem for Linux) into their Docker install. [Medium](https://medium.com/htc-research-engineering-blog/nvidia-docker-on-wsl2-f891dfe34ab) has an okay guide. - NOTE: For all installs, select the ADD TO PATH options whenever available. +NOTE: For all installs, select the ADD TO PATH options whenever available. #### 2. Open VSCode and install extensions: - - Open VSCode and goto the extensions tab on the left toolbar. - - Use the searchbar to find the `Dev Containers` extension and click the install button. - - Feel free to install any other useful extensions that might help you develop your code. - ![](data/README_Resources/images/vscode_install_extensions.png) - NOTE: In the later steps when we start the devcontainer, It's extensions will be seperate from the extensions locally installed on your - computer. You can still install extensions to the devcontainer, but the extensions and their settings won't be persistant between container rebuild. - If you would like an extension to come packaged in with the container, ask one of our [software leads](https://github.com/orgs/MissouriMRDT/teams/software_leads) +- Open VSCode and goto the extensions tab on the left toolbar. +- Use the searchbar to find the `Dev Containers` extension and click the install button. +- Feel free to install any other useful extensions that might help you develop your code. + +![](data/README_Resources/images/vscode_install_extensions.png) + +NOTE: In the later steps when we start the devcontainer, It's extensions will be seperate from the extensions locally installed on your computer. You can still install extensions to the devcontainer, but the extensions and their settings won't be persistant between container rebuild. If you would like an extension to come packaged in with the container, ask one of our [software leads](https://github.com/orgs/MissouriMRDT/teams/software_leads) #### 3. Clone the Autonomy_Software repo: - - Type the sequence `CTRL + SHIFT + P` to open the editor commands. - - Then, start typing: 'git clone' and an option called `Git: Clone (Recursive)` will show up. Select that option and enter this URL when prompted: ```https://github.com/MissouriMRDT/Autonomy_Software.git```. - - Next, it will ask you where you want to store the repo files on your local machine. It's smart to store them in a organized folder structure located somewhere you won't forget and is that easily accessible. - - Finally, when the repo is done cloning, VSCode will ask you if you want to open the newly cloned workspace. Click 'open'. + +- Type the sequence `CTRL + SHIFT + P` to open the editor commands. +- Then, start typing: 'git clone' and an option called `Git: Clone (Recursive)` will show up. Select that option and enter this URL when prompted: ```https://github.com/MissouriMRDT/Autonomy_Software.git```. +- Next, it will ask you where you want to store the repo files on your local machine. It's smart to store them in a organized folder structure located somewhere you won't forget and is that easily accessible. +- Finally, when the repo is done cloning, VSCode will ask you if you want to open the newly cloned workspace. Click 'open'. #### 4. Open the repo inside of our devcontainer: - All developement for Autonomy_Software should be done using our custom made docker image. This image is specifically built so that it is compatible with our codebase. All linux packages, libraries, environment settings, and other configurations are baked into the image. So when you spin up a docker container from our image, it's guarenteed to work perfect and compile easy. This prevents everyone from spending hours trying to setup their environment on widely varying machines with different software and drivers. - - When the cloned folder is first opened, VSCode should detect that this repo is setup for a devcontainer and show a prompt. Click 'open'. - - If VSCode doesn't automatically ask to open the devcontainer, you can manually open it by typing `CTRL + SHIFT + P` and that finding and selecting the `Dev Containers: Rebuild Container` option. +All developement for Autonomy_Software should be done using our custom made docker image. This image is specifically built so that it is compatible with our codebase. All linux packages, libraries, environment settings, and other configurations are baked into the image. So when you spin up a docker container from our image, it's guarenteed to work perfect and compile easy. This prevents everyone from spending hours trying to setup their environment on widely varying machines with different software and drivers. + +- When the cloned folder is first opened, VSCode should detect that this repo is setup for a devcontainer and show a prompt. Click 'open'. +- If VSCode doesn't automatically ask to open the devcontainer, you can manually open it by typing `CTRL + SHIFT + P` and that finding and selecting the `Dev Containers: Rebuild Container` option. Give the container some time to install and setup extensions and other settings, you'll know it's done when the OUTPUT at the buttom stops printing. -#### 5. First build and run: - - Once the devcontainer has been opened and extensions have been automatically setup, a box will appear asking you what CMAKE kit you want to compile with. Select the one that points to `/usr/bin/g++` and `/usr/bin/gcc`. +##### 5. First build and run: +- Once the devcontainer has been opened and extensions have been automatically setup, a box will appear asking you what CMAKE kit you want to compile with. Select the one that points to `/usr/bin/g++` and `/usr/bin/gcc`. ![](data/README_Resources/images/kit_selection_first_container_start.png) - - If the CMake cache needs to be generated or updated, the extension will configure the project automatically. This process may take a few seconds, depending on your hardware. Use the build, run, and debug buttons to easily perform those actions. +- If the CMake cache needs to be generated or updated, the extension will configure the project automatically. This process may take a few seconds, depending on your hardware. Use the build, run, and debug buttons to easily perform those actions. ![](data/README_Resources/images/toolbar_build_run.png) - - Normal make commands (such as `make clean`) can be performed by navigating into the `build/` directory with `cd build/` and running the desired commands. +- Normal make commands (such as `make clean`) can be performed by navigating into the `build/` directory with `cd build/` and running the desired commands. #### 6. Explore the different directories to understand the structure and purpose of each. #### 7. Refer to the specific README files within each directory for detailed information and guidelines on organizing files and using the functionalities. From d47a2a3d6e981e4ddc0b41e23318b6c913004c8c Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Sat, 5 Aug 2023 23:57:42 +0000 Subject: [PATCH 02/10] Added header for contstants namespace. --- src/AutonomyConstants.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AutonomyConstants.h b/src/AutonomyConstants.h index e8171075..04647ad5 100644 --- a/src/AutonomyConstants.h +++ b/src/AutonomyConstants.h @@ -11,11 +11,19 @@ #ifndef CONSTS_H #define CONSTS_H +/****************************************************************************** + * @brief Namespace containing all constants for autonomy software. Including + * AutonomyGlobals.h will also include this namespace. + * + * + * @author ClayJay3 (claytonraycowen@gmail.com) + * @date 2023-08-05 + ******************************************************************************/ namespace constants { // Drive Constants const int MAX_DRIVE_POWER = 250; const int MIN_DRIVE_POWER = 50; -} // namespace constants +} // namespace constants #endif // CONSTS_H From d4033c98c9f28fd80dc6903c29ee1c620e2602b8 Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Mon, 14 Aug 2023 21:34:23 +0000 Subject: [PATCH 03/10] Began writing threading docs. --- src/interfaces/AutonomyThread.hpp | 3 +- src/threads/README.md | 145 ++++++++++++++++++++++-------- 2 files changed, 109 insertions(+), 39 deletions(-) diff --git a/src/interfaces/AutonomyThread.hpp b/src/interfaces/AutonomyThread.hpp index a7f01c12..edc53f44 100644 --- a/src/interfaces/AutonomyThread.hpp +++ b/src/interfaces/AutonomyThread.hpp @@ -48,10 +48,9 @@ class AutonomyThread /****************************************************************************** * @brief This method is ran in a seperate thread. It is a middleware between the * class member thread and the user code that handles graceful stopping of - * user code. + * user code. This method is intentionally designed to not return anything. * * @param bStopThread - Atomic shared variable that signals the thread to stop interating. - * @return T - Variable return type from user code. * * @author ClayJay3 (claytonraycowen@gmail.com) * @date 2023-0724 diff --git a/src/threads/README.md b/src/threads/README.md index 5f58cb48..5aa8669f 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -1,38 +1,109 @@ -\dir src/threads - -# Threads Directory - -The **Threads** directory is dedicated to storing C++ files related to multithreading in the rover project. This directory focuses on managing the main classes of states, vision, and other modules that require their own dedicated threads for parallel execution. - -## Directory Structure - -The **Threads** directory stores files representing the main classes of different modules, each of which will spawn its own thread for parallel execution. The structure within the directory will typically resemble the organization of the corresponding modules, such as states, vision, and other relevant components. - -## Usage - -Here's a general guideline for organizing files within the **Threads** directory: - -- Create a separate file for each main class that requires its own dedicated thread. -- Use descriptive names for the files that reflect the purpose or functionality of each class. -- Inherit the `AutonomyThread` abstract class in the interfaces folder, to ensure the class you want to thread will integrate with the rest of the codebase. -- Ensure that the files are properly documented with comments explaining the class's role, its interaction with other modules, and any relevant details. -- Implement the necessary thread management mechanisms within each class, adhering to best practices for multithreading. - -For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file as `VisionThread.py`. - -Make sure to update this README file whenever new files or modules requiring dedicated threads are added to the **Threads** directory, providing a brief summary of each class's purpose and functionality. - -Remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads. - -## Considerations - -When working with multithreading, keep in mind the following considerations: - -- Pay attention to thread safety and avoid race conditions by properly synchronizing access to shared resources. -- Take advantage of thread-safe data structures and synchronization primitives provided by the chosen programming language or libraries. -- Implement error handling and graceful shutdown mechanisms to handle exceptions and ensure proper termination of threads. -- Consider performance implications, such as potential bottlenecks or resource contention, when designing the multithreading architecture. - -The `AutonomyThread` interface will attempt to make the process of running the code for your subsystem eas by handling most of the shared memory and scheduling. Be diligent in testing and debugging the multithreaded code to ensure the desired behavior and avoid potential issues like deadlocks or data corruption. - +\dir src/threads + +# Threads Directory + +The **Threads** directory is dedicated to storing C++ files related to multithreading in the rover project. This directory focuses on managing the main classes of states, vision, and other modules that require their own dedicated threads for parallel execution. + +## Directory Structure + +The **Threads** directory stores files representing the main classes of different modules, each of which will spawn its own thread for parallel execution. The structure within the directory will typically resemble the organization of the corresponding modules, such as states, vision, and other relevant components. + +## Usage + +Here's a general guideline for organizing files within the **Threads** directory: + +- Create a separate file for each main class that requires its own dedicated thread. +- Use descriptive names for the files that reflect the purpose or functionality of each class. +- Inherit the `AutonomyThread` abstract class in the interfaces folder, to ensure the class you want to thread will integrate with the rest of the codebase. +- Ensure that the files are properly documented with comments explaining the class's role, its interaction with other modules, and any relevant details. +- Implement the necessary thread management mechanisms within each class, adhering to best practices for multithreading. + +### Detailed Walkthrough + +#### Step 0: Do I really need this to run in a new thread? +Before continuing, ask yourself if the code you've written will actually benefit from being ran outside of the main program: +- **_Is your code for a new subsystem that hasn't aleady been implemented?_** +- **_Will your code actually benefit from running in a seperate thread; is the code parallelizable or does it wait/depend on other external code running first?_** +- **_What kind of system or program resources does my code access?_** + +All these question are extremely valid when you are writing code for a new system that you think could benefit from multithreading. Sharing memory between threads is not generally something that you want to do, so plan carefully when designing your class. Have the main class do all of the resource locking by creating getters and/or setters for each piece of data that you want to be accesible from outside of your threaded code. Since the `AutonomyThread` interface was designed so that only a single special method is ran in a different thread, resource handling should be relative simple if you are familiar with the different methods of mutexes and locking in C++. It will also be useful to know how to correctly use atomics in C++, as these let you freely share certain resources between threads without needed to manually lock and unlock them. + +#### Step 1: Create the file and class. +Create a new file in the `threads` directory and name it appropriately. For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file `VisionThread.py`. Then, create a class with the same name as the file and inherit the `AutonomyThread` interface and define the template type like shown below: +``` +#include "../../src/interfaces/AutonomyThread.hpp" + +/****************************************************************************** + * @brief This class inherits the AutonomyThread interface so that it can inherit + * and implement its parent methods. This allows the programmer to easily thread code + * put in a specific function and also give them the ability to create a thread pool + * of subroutines and the ability to parallelize loops. + * + * + * @author ClayJay3 (claytonraycowen@gmail.com) + * @date 2023-07-27 + ******************************************************************************/ +class VisionThread : public AutonomyThread // <-- The '' part lets you define what can be returned from the thread pool. +``` + +Remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads. + +### Mutexes and Locks +When working with different mutex types in C++20, it's important to understand how locks can be acquired to ensure proper synchronization and avoid potential issues like deadlocks. Here's a detailed explanation of the types of locks that can be acquired for each mutex type: + +### 1. `std::mutex` + +- **Lock Type:** Exclusive Lock +- **Acquisition:** A thread can acquire an exclusive lock on a `std::mutex` using the `std::unique_lock` class or by directly calling the `lock()` member function on the mutex. + +- **Use Case:** Use an exclusive lock when you need to ensure that only one thread can access a critical section of code or a shared resource at a time. + +### 2. `std::timed_mutex` + +- **Lock Type:** Exclusive Lock with Timeout +- **Acquisition:** A thread can attempt to acquire an exclusive lock on a `std::timed_mutex` using the `std::unique_lock` class and providing a timeout value when calling the `try_lock_for()` or `try_lock_until()` member functions. + +- **Use Case:** Use an exclusive lock with timeout when you want to prevent indefinite waiting for a lock and need to decide on an alternative action if the lock cannot be acquired within a specified time. + +### 3. `std::recursive_mutex` + +- **Lock Type:** Exclusive Lock (Recursive) +- **Acquisition:** Similar to a `std::mutex`, a thread can acquire an exclusive lock on a `std::recursive_mutex` using the `std::unique_lock` class or by directly calling the `lock()` member function on the mutex. The same thread can acquire the lock multiple times without causing a deadlock. + +- **Use Case:** Use a recursive lock when you have a nested structure of function calls that require the same mutex protection, and you want to avoid deadlocks caused by recursive locking. + +### 4. `std::recursive_timed_mutex` + +- **Lock Type:** Exclusive Lock (Recursive) with Timeout +- **Acquisition:** Similar to a `std::timed_mutex`, a thread can attempt to acquire an exclusive lock on a `std::recursive_timed_mutex` using the `std::unique_lock` class and specifying a timeout value with `try_lock_for()` or `try_lock_until()`. + +- **Use Case:** Use a recursive lock with timeout when you need both the ability to recursively lock and a mechanism to avoid indefinite waiting for the lock. + +### 5. `std::shared_mutex` + +- **Lock Type:** Shared Lock (Read Lock), Exclusive Lock (Write Lock) +- **Acquisition:** A thread can acquire a shared lock (read lock) on a `std::shared_mutex` using the `std::shared_lock` class. To acquire an exclusive lock (write lock), use the `std::unique_lock` class or call the `lock()` member function directly. + +- **Use Case:** Use a shared lock when you have a resource that is frequently read by multiple threads. Use an exclusive lock when you need to modify the resource to ensure exclusive access. + +### 6. `std::shared_timed_mutex` + +- **Lock Type:** Shared Lock (Read Lock), Exclusive Lock (Write Lock) with Timeout +- **Acquisition:** Similar to `std::shared_mutex`, a thread can acquire shared and exclusive locks using `std::shared_lock` and `std::unique_lock`, respectively. Additionally, you can use the `try_lock_shared_for()` and `try_lock_shared_until()` functions to attempt to acquire a shared lock with a timeout. + +- **Use Case:** Use a shared timed lock when you need to read a resource concurrently but want to avoid indefinite waiting. Use an exclusive timed lock when you need to write with a timeout. + +Always choose the appropriate type of lock based on your application's requirements and concurrency patterns to ensure efficient and safe synchronization of shared resources. + +## Final Considerations + +When working with multithreading, keep in mind the following considerations: + +- Pay attention to thread safety and avoid race conditions by properly synchronizing access to shared resources. +- Take advantage of thread-safe data structures and synchronization primitives provided by the chosen programming language or libraries. +- Implement error handling and graceful shutdown mechanisms to handle exceptions and ensure proper termination of threads. +- Consider performance implications, such as potential bottlenecks or resource contention, when designing the multithreading architecture. + +The `AutonomyThread` interface will attempt to make the process of running the code for your subsystem eas by handling most of the shared memory and scheduling. Be diligent in testing and debugging the multithreaded code to ensure the desired behavior and avoid potential issues like deadlocks or data corruption. + Happy coding and threading! \ No newline at end of file From e51f2d10b0da57ee0211371dabaa6e50b7a1ae28 Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Mon, 14 Aug 2023 21:55:04 +0000 Subject: [PATCH 04/10] Little more progess on docs. --- src/threads/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/threads/README.md b/src/threads/README.md index 5aa8669f..c1c89ee7 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -28,16 +28,18 @@ Before continuing, ask yourself if the code you've written will actually benefit All these question are extremely valid when you are writing code for a new system that you think could benefit from multithreading. Sharing memory between threads is not generally something that you want to do, so plan carefully when designing your class. Have the main class do all of the resource locking by creating getters and/or setters for each piece of data that you want to be accesible from outside of your threaded code. Since the `AutonomyThread` interface was designed so that only a single special method is ran in a different thread, resource handling should be relative simple if you are familiar with the different methods of mutexes and locking in C++. It will also be useful to know how to correctly use atomics in C++, as these let you freely share certain resources between threads without needed to manually lock and unlock them. -#### Step 1: Create the file and class. +**NOTE: Look in the `examples/threading` directory to see example implementations of what this guide discusses.** + +#### Step 1: Create the class and inherit the interface. Create a new file in the `threads` directory and name it appropriately. For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file `VisionThread.py`. Then, create a class with the same name as the file and inherit the `AutonomyThread` interface and define the template type like shown below: ``` #include "../../src/interfaces/AutonomyThread.hpp" /****************************************************************************** * @brief This class inherits the AutonomyThread interface so that it can inherit - * and implement its parent methods. This allows the programmer to easily thread code - * put in a specific function and also give them the ability to create a thread pool - * of subroutines and the ability to parallelize loops. + * and implement its base methods. This allows the programmer to easily thread code + * put in a specific function and it also gives them the ability to create a thread pool + * of subroutines and parallelize loops. * * * @author ClayJay3 (claytonraycowen@gmail.com) @@ -46,6 +48,9 @@ Create a new file in the `threads` directory and name it appropriately. For exam class VisionThread : public AutonomyThread // <-- The '' part lets you define what can be returned from the thread pool. ``` +#### Step 2: Implement the inherited methods. +The `AutonomyThread` base class contains some [virtual](https://www.geeksforgeeks.org/virtual-function-cpp/) and [pure virtual](https://www.geeksforgeeks.org/pure-virtual-functions-and-abstract-classes/#) methods that your new child class should inherit. + Remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads. ### Mutexes and Locks From 6f0e960b5d6052fbfdf0c7e9fb552f2485e70c7a Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 16:31:10 +0000 Subject: [PATCH 05/10] More progress on readme. --- src/threads/README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/threads/README.md b/src/threads/README.md index c1c89ee7..40f27be4 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -8,7 +8,7 @@ The **Threads** directory is dedicated to storing C++ files related to multithre The **Threads** directory stores files representing the main classes of different modules, each of which will spawn its own thread for parallel execution. The structure within the directory will typically resemble the organization of the corresponding modules, such as states, vision, and other relevant components. -## Usage +## General Usage Here's a general guideline for organizing files within the **Threads** directory: @@ -18,9 +18,9 @@ Here's a general guideline for organizing files within the **Threads** directory - Ensure that the files are properly documented with comments explaining the class's role, its interaction with other modules, and any relevant details. - Implement the necessary thread management mechanisms within each class, adhering to best practices for multithreading. -### Detailed Walkthrough +## Detailed Walkthrough -#### Step 0: Do I really need this to run in a new thread? +### Step 0: Do I really need this to run in a new thread? Before continuing, ask yourself if the code you've written will actually benefit from being ran outside of the main program: - **_Is your code for a new subsystem that hasn't aleady been implemented?_** - **_Will your code actually benefit from running in a seperate thread; is the code parallelizable or does it wait/depend on other external code running first?_** @@ -30,7 +30,7 @@ All these question are extremely valid when you are writing code for a new syste **NOTE: Look in the `examples/threading` directory to see example implementations of what this guide discusses.** -#### Step 1: Create the class and inherit the interface. +### Step 1: Create the class and inherit the interface. Create a new file in the `threads` directory and name it appropriately. For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file `VisionThread.py`. Then, create a class with the same name as the file and inherit the `AutonomyThread` interface and define the template type like shown below: ``` #include "../../src/interfaces/AutonomyThread.hpp" @@ -48,12 +48,19 @@ Create a new file in the `threads` directory and name it appropriately. For exam class VisionThread : public AutonomyThread // <-- The '' part lets you define what can be returned from the thread pool. ``` -#### Step 2: Implement the inherited methods. -The `AutonomyThread` base class contains some [virtual](https://www.geeksforgeeks.org/virtual-function-cpp/) and [pure virtual](https://www.geeksforgeeks.org/pure-virtual-functions-and-abstract-classes/#) methods that your new child class should inherit. +### Step 2: Implement the inherited methods. +The `AutonomyThread` base class contains some [virtual](https://www.geeksforgeeks.org/virtual-function-cpp/) and [pure virtual](https://www.geeksforgeeks.org/pure-virtual-functions-and-abstract-classes/#) methods that your new child class should inherit. The parent class (AutonomyThread) does some stuff in the background that will allow you to easily run the code put in these functions parallel to the main thread. + +The two methods that you need to implement are called [ThreadedContinuousCode()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a856f865268995d910a4c5deafe1a47f1) and [PooledLinearCode()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a442ce800c8d195164bb8caa25622551a). +- ThreadedContinuousCode is where you will put your main code for the thread, this is the code that you want to loop forever until either you or the program stops it. The code that you put in this method should not block the rest of the code, **do not put a `while(true)` loop inside this method.** The parent class that you inherited will handle the looping and stop signaling internally, as shown [here](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#aeafe6a5ff40437d14425d5af73b48019). +- PooledLinearCode is where you can optionally put any _highly parallelizable sub-routine code_ that you want to run in an arbitrarly large pool of threads. For example, if you have four images from four different cameras and they all need the AR tag detection algorithms ran, you can start a thread pool and process all four in roughly the same time it would normally take one to process. (Assuming you AR tag detector can be ran in parallel without blocking). + + +**To stop all threads, call the [RequestStop()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a83a49c4a92e0c983909d24edfa74843d) method from inside or outside of your class. _You CAN call the RequestStop() method from inside of your threaded code._** Remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads. -### Mutexes and Locks +## Mutexes and Locks When working with different mutex types in C++20, it's important to understand how locks can be acquired to ensure proper synchronization and avoid potential issues like deadlocks. Here's a detailed explanation of the types of locks that can be acquired for each mutex type: ### 1. `std::mutex` From 617680a2bda80d33cc5eb69df5f357559c1a7cfb Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 17:16:30 +0000 Subject: [PATCH 06/10] Small document header changes to code. --- examples/threading/ArucoGenerateTags.hpp | 1 - src/interfaces/AutonomyThread.hpp | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/threading/ArucoGenerateTags.hpp b/examples/threading/ArucoGenerateTags.hpp index c952829d..6bd6ed4f 100644 --- a/examples/threading/ArucoGenerateTags.hpp +++ b/examples/threading/ArucoGenerateTags.hpp @@ -97,7 +97,6 @@ class ArucoGenerateTagsThreaded : public AutonomyThread { ////////////////////////////////////////////////// // This code linearly runs through every tag. - // UNCOMMENT to see the speed difference. ////////////////////////////////////////////////// for (int i = 0; i < m_nNumTagsToGenerate; ++i) { diff --git a/src/interfaces/AutonomyThread.hpp b/src/interfaces/AutonomyThread.hpp index edc53f44..81ce3d73 100644 --- a/src/interfaces/AutonomyThread.hpp +++ b/src/interfaces/AutonomyThread.hpp @@ -263,7 +263,9 @@ class AutonomyThread /****************************************************************************** * @brief Accessor for the Pool Results private member. The action of getting - * results will destroy and remove them from this object. + * results will destroy and remove them from this object. This method blocks + * if the thread is not finished, so no need to call JoinPool() before getting + * results. * * @return std::vector - A vector containing the returns from each thread that * ran the PooledLinearCode. @@ -408,7 +410,7 @@ class AutonomyThread bool Joinable() const { // Check current number of running and queued tasks. - if (m_thMainThread.get_tasks_total() <= 0 && m_thPool.get_tasks_total() && m_thLoopPool.get_tasks_total()) + if (m_thMainThread.get_tasks_total() <= 0 && m_thPool.get_tasks_total() <= 0 && m_thLoopPool.get_tasks_total() <= 0) { // Threads are joinable. return true; From 29209d1ad27de821367c3b1d8eb5c13a7e16f27d Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 17:16:57 +0000 Subject: [PATCH 07/10] Final draft of readme. --- src/threads/README.md | 91 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/src/threads/README.md b/src/threads/README.md index 40f27be4..dd5ef568 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -53,15 +53,49 @@ The `AutonomyThread` base class contains some [virtual](https://www.geeksforgeek The two methods that you need to implement are called [ThreadedContinuousCode()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a856f865268995d910a4c5deafe1a47f1) and [PooledLinearCode()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a442ce800c8d195164bb8caa25622551a). - ThreadedContinuousCode is where you will put your main code for the thread, this is the code that you want to loop forever until either you or the program stops it. The code that you put in this method should not block the rest of the code, **do not put a `while(true)` loop inside this method.** The parent class that you inherited will handle the looping and stop signaling internally, as shown [here](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#aeafe6a5ff40437d14425d5af73b48019). -- PooledLinearCode is where you can optionally put any _highly parallelizable sub-routine code_ that you want to run in an arbitrarly large pool of threads. For example, if you have four images from four different cameras and they all need the AR tag detection algorithms ran, you can start a thread pool and process all four in roughly the same time it would normally take one to process. (Assuming you AR tag detector can be ran in parallel without blocking). - +- PooledLinearCode is where you can optionally put any _highly parallelizable sub-routine code_ that you want to run in an arbitrarly large pool of threads. For example, if you have four images from four different cameras and they all need the AR tag detection algorithms ran, you can start a thread pool and process all four in roughly the same time it would normally take one to process. (Assuming your AR tag detector can be ran in parallel without blocking). This code can only be called from within your class, your code inside of the ThreadedContinuousCode method should handle the [starting](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#ad763e85a17af864687366c44462bd44a), [stopping](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#af7c99c41b6512725fc0715dabe7f500d), and [results fetching](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a6f9941fe0b02013a0be3eba330b68b73) of your thread pool. **To stop all threads, call the [RequestStop()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a83a49c4a92e0c983909d24edfa74843d) method from inside or outside of your class. _You CAN call the RequestStop() method from inside of your threaded code._** -Remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads. +## Step 3: Ensure that you are correctly handling your resources. +Multi-threaded programming brings a new dimension of complexity to software development. It enables multiple threads to run concurrently, improving performance and responsiveness. However, with this power comes the responsibility of managing shared resources effectively. Mishandling resources in a multi-threaded environment can lead to subtle bugs, performance issues, and even security vulnerabilities. This guide emphasizes the significance of proper resource handling and provides best practices to ensure thread-safe, efficient, and reliable multi-threaded applications. **_To aid your decision on what mutex or lock you should use, check out the Mutexes and Locks section below. For a list of atomic, check out the Atomic section below._** + +1. Understanding Shared Resources +Shared resources are data structures, objects, or sections of code that are accessed by multiple threads concurrently. Examples include data containers, global variables, databases, and files. When multiple threads access or modify shared resources without proper synchronization, race conditions and data inconsistencies can occur. + +2. Concurrency Challenges (don't do this) + + - Race Conditions: Multiple threads attempting to access or modify a shared resource simultaneously can lead to unexpected and erroneous behavior. + - Deadlocks: Threads can get stuck in a state where each thread is waiting for a resource that another thread holds, causing a deadlock. + - Data Inconsistencies: Concurrent modifications can lead to data corruption or unexpected behavior if not synchronized correctly. + - Performance Degradation: Poorly managed synchronization can introduce unnecessary overhead and reduce the benefits of parallelism. + +3. Ensuring Proper Resource Handling (do this instead) + + - Use Synchronization Mechanisms: Employ synchronization mechanisms like mutexes, semaphores, and condition variables to ensure exclusive access to shared resources. + Choose the appropriate synchronization mechanism based on your resource and access patterns. + - Minimize Lock Scope: Lock resources only when necessary and release them as soon as possible to reduce contention and improve performance. + Avoid holding locks during time-consuming operations. + - Avoid Nested Locks: Nested locks can lead to deadlocks if not managed carefully. Whenever possible, use higher-level abstractions like std::lock_guard to manage locks automatically. + - Use Read-Write Locks: For resources that are read more frequently than they are written, use read-write locks (std::shared_mutex in C++20) to allow concurrent reads and exclusive writes. + - Use Atomic Operations: Atomic operations provide a way to modify variables without the risk of race conditions. Use atomic types and functions to perform simple operations safely. + - Thread Safety Design: Design classes and data structures with thread safety in mind. Use encapsulation to hide implementation details and provide controlled access to resources. + - Avoid Global State: Minimize the use of global variables and shared state. Instead, pass required data as arguments to threads or use thread-local storage when appropriate. + - Test Rigorously: Thoroughly test your multi-threaded code with various scenarios, stressing resource access, contention, and boundary cases. + - Debugging and Profiling: Use debugging tools and profilers to identify and rectify concurrency issues. + +4. Benefits of Proper Resource Handling + + - Reliability: Proper resource handling leads to consistent behavior and predictable outcomes in multi-threaded applications. + - Performance: Effective synchronization ensures optimal utilization of resources and prevents unnecessary contention, leading to better performance. + - Scalability: Well-managed resources allow applications to scale efficiently across multiple cores or processors. + - Maintainability: Code that follows proper resource handling practices is easier to understand, debug, and maintain. + - Security: Proper synchronization prevents unintended access to sensitive data and reduces the risk of data corruption or unauthorized modifications. ## Mutexes and Locks -When working with different mutex types in C++20, it's important to understand how locks can be acquired to ensure proper synchronization and avoid potential issues like deadlocks. Here's a detailed explanation of the types of locks that can be acquired for each mutex type: +When working with different mutex types in C++20, it's important to understand how locks can be acquired to ensure proper synchronization and avoid potential issues like deadlocks. **_Always remember to integrate the thread-related classes with other components of the project to ensure proper coordination and synchronization between threads._** + +Here's a detailed explanation of the types of locks that can be acquired for each mutex type: ### 1. `std::mutex` @@ -107,6 +141,53 @@ When working with different mutex types in C++20, it's important to understand h Always choose the appropriate type of lock based on your application's requirements and concurrency patterns to ensure efficient and safe synchronization of shared resources. +## Atomics +In C++20, the Standard Library provides a variety of atomic types and operations for concurrent programming. Atomic types ensure that certain operations on shared variables are performed atomically, without the risk of race conditions. For example, see the `AutonomyThread` interface and [it's use](https://missourimrdt.github.io/Autonomy_Software/AutonomyThread_8hpp_source.html) of `std::atomic_bool` (`std::atomic`) for the stop signalling variable. Any of the threads that have access to that variable could manipulate it, but since it's atomic no mutexes or locks are needed, it just does it automatically. + +Here is a list of common atomic types and objects available in C++20, keep in mind there may be more: + +1. **Atomic Integral Types**: + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + - `std::atomic` + +2. **Atomic Floating-Point Types**: + - `std::atomic` + - `std::atomic` + - `std::atomic` + +3. **Atomic Pointer Types**: + - `std::atomic` + - `std::atomic` (for atomic null pointer operations) + +4. **Atomic Operations**: + - `std::atomic_flag`: A special atomic type used for simple flag-based synchronization. It provides atomic test-and-set operations. + +5. **Atomic Operations on Standard Types**: + - `std::atomic::load()`: Atomically retrieves the value. + - `std::atomic::store(value)`: Atomically sets the value. + - `std::atomic::exchange(value)`: Atomically sets a new value and returns the previous value. + - `std::atomic::compare_exchange_strong(expected, desired)`: Atomically compares the value and, if equal, updates it with a new value. + - `std::atomic::compare_exchange_weak(expected, desired)`: Similar to `compare_exchange_strong`, but allows spurious failure. + +6. **Arithmetic Operations**: + - `std::atomic::fetch_add(value)`: Atomically adds a value and returns the original value. + - `std::atomic::fetch_sub(value)`: Atomically subtracts a value and returns the original value. + - `std::atomic::fetch_and(value)`: Atomically performs a bitwise AND with a value and returns the original value. + - `std::atomic::fetch_or(value)`: Atomically performs a bitwise OR with a value and returns the original value. + - `std::atomic::fetch_xor(value)`: Atomically performs a bitwise XOR with a value and returns the original value. + +Remember that using atomic types and operations correctly requires a solid understanding of concurrency principles. Proper use of atomics can help prevent race conditions and data corruption while maintaining the performance benefits of multi-threading. + ## Final Considerations When working with multithreading, keep in mind the following considerations: @@ -116,6 +197,6 @@ When working with multithreading, keep in mind the following considerations: - Implement error handling and graceful shutdown mechanisms to handle exceptions and ensure proper termination of threads. - Consider performance implications, such as potential bottlenecks or resource contention, when designing the multithreading architecture. -The `AutonomyThread` interface will attempt to make the process of running the code for your subsystem eas by handling most of the shared memory and scheduling. Be diligent in testing and debugging the multithreaded code to ensure the desired behavior and avoid potential issues like deadlocks or data corruption. +The `AutonomyThread` interface will attempt to make the process of running the code for your subsystem easy by handling most of annoying thread scheduling and management for you. Be diligent in testing and debugging the multithreaded code to ensure the desired behavior and avoid potential issues like deadlocks or data corruption. Always double check your logic and resource management! Happy coding and threading! \ No newline at end of file From 96d79d8db67ec53b0f02e8eca861be4bb1f6b0f0 Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 17:21:53 +0000 Subject: [PATCH 08/10] Add examples to threads readme. --- src/threads/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/threads/README.md b/src/threads/README.md index dd5ef568..c7fdbd82 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -55,6 +55,8 @@ The two methods that you need to implement are called [ThreadedContinuousCode()] - ThreadedContinuousCode is where you will put your main code for the thread, this is the code that you want to loop forever until either you or the program stops it. The code that you put in this method should not block the rest of the code, **do not put a `while(true)` loop inside this method.** The parent class that you inherited will handle the looping and stop signaling internally, as shown [here](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#aeafe6a5ff40437d14425d5af73b48019). - PooledLinearCode is where you can optionally put any _highly parallelizable sub-routine code_ that you want to run in an arbitrarly large pool of threads. For example, if you have four images from four different cameras and they all need the AR tag detection algorithms ran, you can start a thread pool and process all four in roughly the same time it would normally take one to process. (Assuming your AR tag detector can be ran in parallel without blocking). This code can only be called from within your class, your code inside of the ThreadedContinuousCode method should handle the [starting](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#ad763e85a17af864687366c44462bd44a), [stopping](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#af7c99c41b6512725fc0715dabe7f500d), and [results fetching](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a6f9941fe0b02013a0be3eba330b68b73) of your thread pool. +Take a look at [this example](https://missourimrdt.github.io/Autonomy_Software/classArucoGenerateTagsThreaded.html) and [this example](https://missourimrdt.github.io/Autonomy_Software/classPrimeCalculatorThread.html) for how to implement the inherited funtions. + **To stop all threads, call the [RequestStop()](https://missourimrdt.github.io/Autonomy_Software/classAutonomyThread.html#a83a49c4a92e0c983909d24edfa74843d) method from inside or outside of your class. _You CAN call the RequestStop() method from inside of your threaded code._** ## Step 3: Ensure that you are correctly handling your resources. From 9bcdfdbb3f18b89ee9279818559843c65a1b5fc6 Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 18:51:31 +0000 Subject: [PATCH 09/10] Fix main readme typo. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 96462826..1b094e42 100755 --- a/README.md +++ b/README.md @@ -115,9 +115,9 @@ NOTE: In the later steps when we start the devcontainer, It's extensions will be All developement for Autonomy_Software should be done using our custom made docker image. This image is specifically built so that it is compatible with our codebase. All linux packages, libraries, environment settings, and other configurations are baked into the image. So when you spin up a docker container from our image, it's guarenteed to work perfect and compile easy. This prevents everyone from spending hours trying to setup their environment on widely varying machines with different software and drivers. - When the cloned folder is first opened, VSCode should detect that this repo is setup for a devcontainer and show a prompt. Click 'open'. -- If VSCode doesn't automatically ask to open the devcontainer, you can manually open it by typing `CTRL + SHIFT + P` and that finding and selecting the `Dev Containers: Rebuild Container` option. +- If VSCode doesn't automatically ask to open the devcontainer, you can manually open it by typing `CTRL + SHIFT + P` and then finding and selecting the `Dev Containers: Rebuild Container` option. - Give the container some time to install and setup extensions and other settings, you'll know it's done when the OUTPUT at the buttom stops printing. + Give the container some time to install and setup extensions, you'll know it's done when the OUTPUT at the buttom stops printing. ##### 5. First build and run: - Once the devcontainer has been opened and extensions have been automatically setup, a box will appear asking you what CMAKE kit you want to compile with. Select the one that points to `/usr/bin/g++` and `/usr/bin/gcc`. @@ -137,7 +137,7 @@ Debugging a C++ application in Visual Studio Code can be made seamless and effic ## Setting up the Development Environment -1. Open this project in a devcontainer: First, install Microsoft's ~Dev Containers~ extension from the marketplace. Then hit `CTRL + SHIFT + P` sequence +1. Open this project in a devcontainer: First, install Microsoft's _Dev Containers_ extension from the marketplace. Then hit `CTRL + SHIFT + P` sequence to open the editor commands, and select the `Dev Container: Rebuild Container` option. 3. Select the CMake Kit: Once you open the project, you'll be prompted to select the CMake Kit for your project. You can do this either from the bottom status bar or during the initial setup of the development container a prompt will automatically show up. The CMake Kit represents the C++ toolchain used for building the project (compiler, architecture, etc.). Choose the appropriate kit for your project. From 85307e85676db0d2edd23b53e92a6045e6a1344b Mon Sep 17 00:00:00 2001 From: ClayJay3 Date: Tue, 15 Aug 2023 19:23:28 +0000 Subject: [PATCH 10/10] Fixed .py typo --- src/threads/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/threads/README.md b/src/threads/README.md index c7fdbd82..495e6f94 100644 --- a/src/threads/README.md +++ b/src/threads/README.md @@ -31,7 +31,7 @@ All these question are extremely valid when you are writing code for a new syste **NOTE: Look in the `examples/threading` directory to see example implementations of what this guide discusses.** ### Step 1: Create the class and inherit the interface. -Create a new file in the `threads` directory and name it appropriately. For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file `VisionThread.py`. Then, create a class with the same name as the file and inherit the `AutonomyThread` interface and define the template type like shown below: +Create a new file in the `threads` directory and name it appropriately. For example, if there is a main class responsible for managing the states of the rover, create a file named `StateMachineThread.cpp`. Similarly, if there is a main class handling vision processing, name the file `VisionThread.cpp`. Then, create a class with the same name as the file and inherit the `AutonomyThread` interface and define the template type like shown below: ``` #include "../../src/interfaces/AutonomyThread.hpp"