├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── par_sum.rs ├── src └── lib.rs └── tests └── smoke.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | commit-message: 8 | prefix: '' 9 | labels: [] 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | schedule: 12 | - cron: '0 2 * * 0' 13 | 14 | env: 15 | CARGO_INCREMENTAL: 0 16 | CARGO_NET_GIT_FETCH_WITH_CLI: true 17 | CARGO_NET_RETRY: 10 18 | CARGO_TERM_COLOR: always 19 | RUST_BACKTRACE: 1 20 | RUSTFLAGS: -D warnings 21 | RUSTDOCFLAGS: -D warnings 22 | RUSTUP_MAX_RETRIES: 10 23 | 24 | defaults: 25 | run: 26 | shell: bash 27 | 28 | jobs: 29 | fmt: 30 | uses: smol-rs/.github/.github/workflows/fmt.yml@main 31 | security_audit: 32 | uses: smol-rs/.github/.github/workflows/security_audit.yml@main 33 | permissions: 34 | checks: write 35 | contents: read 36 | issues: write 37 | secrets: inherit 38 | 39 | test: 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: [ubuntu-latest] 45 | rust: [nightly, beta, stable] 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Install Rust 49 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 50 | - run: cargo build --all --all-features --all-targets 51 | - name: Run cargo check (without dev-dependencies to catch missing feature flags) 52 | if: startsWith(matrix.rust, 'nightly') 53 | run: cargo check -Z features=dev_dep 54 | - run: cargo test 55 | 56 | msrv: 57 | runs-on: ubuntu-latest 58 | strategy: 59 | matrix: 60 | # When updating this, the reminder to update the minimum supported 61 | # Rust version in Cargo.toml. 62 | rust: ['1.63'] 63 | steps: 64 | - uses: actions/checkout@v4 65 | - name: Install Rust 66 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 67 | - run: cargo build 68 | 69 | clippy: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v4 73 | - name: Install Rust 74 | run: rustup update stable 75 | - run: cargo clippy --all-features --all-targets 76 | 77 | miri: 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v4 81 | - name: Install Rust 82 | run: rustup toolchain install nightly --component miri && rustup default nightly 83 | - run: cargo miri test 84 | env: 85 | MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check -Zmiri-disable-isolation 86 | RUSTFLAGS: ${{ env.RUSTFLAGS }} -Z randomize-layout 87 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | if: github.repository_owner == 'smol-rs' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: taiki-e/create-gh-release-action@v1 18 | with: 19 | changelog: CHANGELOG.md 20 | branch: master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 3.3.1 2 | 3 | - Reimplement using 100% safe code. (#11) 4 | 5 | # Version 3.3.0 6 | 7 | - Add `Parallel::collect()` and `Parallel::finish_in()` to allow for generic containers. (#9) 8 | 9 | # Version 3.2.0 10 | 11 | - Remove `T: Default` bound from `Default` impl. 12 | 13 | # Version 3.1.0 14 | 15 | - Add `Parallel::finish()`. 16 | 17 | # Version 3.0.0 18 | 19 | - Collect results in the same order closures were added. 20 | 21 | # Version 2.2.0 22 | 23 | - Remove `PhantomData` making `'a` invariant. 24 | 25 | # Version 2.1.0 26 | 27 | - Run one of the closures on the main thread. 28 | 29 | # Version 2.0.0 30 | 31 | - `Parallel::run()` now collects results. 32 | 33 | # Version 1.0.1 34 | 35 | - Remove unused `mut`. 36 | 37 | # Version 1.0.0 38 | 39 | - Initial version 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "easy-parallel" 3 | # When publishing a new version: 4 | # - Update CHANGELOG.md 5 | # - Create "v3.x.y" git tag 6 | version = "3.3.1" 7 | authors = ["Stjepan Glavina "] 8 | edition = "2021" 9 | rust-version = "1.63" 10 | description = "Run closures in parallel" 11 | license = "Apache-2.0 OR MIT" 12 | repository = "https://github.com/smol-rs/easy-parallel" 13 | homepage = "https://github.com/smol-rs/easy-parallel" 14 | keywords = ["scope", "thread", "scoped", "spawn"] 15 | categories = ["concurrency"] 16 | exclude = ["/.*"] 17 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # easy-parallel 2 | 3 | [![Build](https://github.com/smol-rs/easy-parallel/workflows/Build%20and%20test/badge.svg)]( 4 | https://github.com/smol-rs/easy-parallel/actions) 5 | [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)]( 6 | https://github.com/smol-rs/easy-parallel) 7 | [![Cargo](https://img.shields.io/crates/v/easy-parallel.svg)]( 8 | https://crates.io/crates/easy-parallel) 9 | [![Documentation](https://docs.rs/easy-parallel/badge.svg)]( 10 | https://docs.rs/easy-parallel) 11 | 12 | Run closures in parallel. 13 | 14 | This is a simple primitive for spawning threads in bulk and waiting for them to complete. 15 | Threads are allowed to borrow local variables from the main thread. 16 | 17 | # Examples 18 | 19 | Run two threads that increment a number: 20 | 21 | ```rust 22 | use easy_parallel::Parallel; 23 | use std::sync::Mutex; 24 | 25 | let mut m = Mutex::new(0); 26 | 27 | Parallel::new() 28 | .add(|| *m.lock().unwrap() += 1) 29 | .add(|| *m.lock().unwrap() += 1) 30 | .run(); 31 | 32 | assert_eq!(*m.get_mut().unwrap(), 2); 33 | ``` 34 | 35 | Square each number of a vector on a different thread: 36 | 37 | ```rust 38 | use easy_parallel::Parallel; 39 | 40 | let v = vec![10, 20, 30]; 41 | 42 | let squares = Parallel::new() 43 | .each(0..v.len(), |i| v[i] * v[i]) 44 | .run(); 45 | 46 | assert_eq!(squares, [100, 400, 900]); 47 | ``` 48 | 49 | Compute the sum of numbers in an array: 50 | 51 | ```rust 52 | use easy_parallel::Parallel; 53 | 54 | fn par_sum(v: &[i32]) -> i32 { 55 | const THRESHOLD: usize = 2; 56 | 57 | if v.len() <= THRESHOLD { 58 | v.iter().copied().sum() 59 | } else { 60 | let half = (v.len() + 1) / 2; 61 | let sums = Parallel::new().each(v.chunks(half), par_sum).run(); 62 | sums.into_iter().sum() 63 | } 64 | } 65 | 66 | let v = [1, 25, -4, 10, 8]; 67 | assert_eq!(par_sum(&v), 40); 68 | ``` 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | #### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally submitted 82 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 83 | dual licensed as above, without any additional terms or conditions. 84 | -------------------------------------------------------------------------------- /examples/par_sum.rs: -------------------------------------------------------------------------------- 1 | use easy_parallel::Parallel; 2 | 3 | fn par_sum(v: &[i32]) -> i32 { 4 | const THRESHOLD: usize = 100; 5 | 6 | if v.len() <= THRESHOLD { 7 | v.iter().copied().sum() 8 | } else { 9 | let half = (v.len() + 1) / 2; 10 | let sums = Parallel::new().each(v.chunks(half), par_sum).run(); 11 | sums.into_iter().sum() 12 | } 13 | } 14 | 15 | fn main() { 16 | let mut v = Vec::new(); 17 | for i in 0..10_000 { 18 | v.push(i); 19 | } 20 | 21 | let sum = dbg!(par_sum(&v)); 22 | assert_eq!(sum, v.into_iter().sum()); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Run closures in parallel. 2 | //! 3 | //! This is a simple primitive for spawning threads in bulk and waiting for them to complete. 4 | //! Threads are allowed to borrow local variables from the main thread. 5 | //! 6 | //! # Examples 7 | //! 8 | //! Run two threads that increment a number: 9 | //! 10 | //! ``` 11 | //! use easy_parallel::Parallel; 12 | //! use std::sync::Mutex; 13 | //! 14 | //! let mut m = Mutex::new(0); 15 | //! 16 | //! Parallel::new() 17 | //! .add(|| *m.lock().unwrap() += 1) 18 | //! .add(|| *m.lock().unwrap() += 1) 19 | //! .run(); 20 | //! 21 | //! assert_eq!(*m.get_mut().unwrap(), 2); 22 | //! ``` 23 | //! 24 | //! Square each number of a vector on a different thread: 25 | //! 26 | //! ``` 27 | //! use easy_parallel::Parallel; 28 | //! 29 | //! let v = vec![10, 20, 30]; 30 | //! 31 | //! let squares = Parallel::new() 32 | //! .each(0..v.len(), |i| v[i] * v[i]) 33 | //! .run(); 34 | //! 35 | //! assert_eq!(squares, [100, 400, 900]); 36 | //! ``` 37 | //! 38 | //! Compute the sum of numbers in an array: 39 | //! 40 | //! ``` 41 | //! use easy_parallel::Parallel; 42 | //! 43 | //! fn par_sum(v: &[i32]) -> i32 { 44 | //! const THRESHOLD: usize = 2; 45 | //! 46 | //! if v.len() <= THRESHOLD { 47 | //! v.iter().copied().sum() 48 | //! } else { 49 | //! let half = (v.len() + 1) / 2; 50 | //! let sums = Parallel::new().each(v.chunks(half), par_sum).run(); 51 | //! sums.into_iter().sum() 52 | //! } 53 | //! } 54 | //! 55 | //! let v = [1, 25, -4, 10, 8]; 56 | //! assert_eq!(par_sum(&v), 40); 57 | //! ``` 58 | 59 | #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] 60 | #![forbid(unsafe_code)] 61 | #![doc( 62 | html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 63 | )] 64 | #![doc( 65 | html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 66 | )] 67 | 68 | use std::fmt; 69 | use std::iter; 70 | use std::panic; 71 | use std::sync::mpsc; 72 | use std::thread; 73 | 74 | /// A builder that runs closures in parallel. 75 | #[must_use] 76 | pub struct Parallel<'a, T> { 77 | /// Closures to run. 78 | closures: Vec T + Send + 'a>>, 79 | } 80 | 81 | impl<'a, T> Parallel<'a, T> { 82 | /// Creates a builder for running closures in parallel. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ``` 87 | /// use easy_parallel::Parallel; 88 | /// 89 | /// let p = Parallel::<()>::new(); 90 | /// ``` 91 | pub fn new() -> Parallel<'a, T> { 92 | Parallel { 93 | closures: Vec::new(), 94 | } 95 | } 96 | 97 | /// Adds a closure to the list. 98 | /// 99 | /// # Examples 100 | /// 101 | /// ``` 102 | /// use easy_parallel::Parallel; 103 | /// 104 | /// Parallel::new() 105 | /// .add(|| println!("hello from a thread")) 106 | /// .run(); 107 | /// ``` 108 | #[allow(clippy::should_implement_trait)] 109 | pub fn add(mut self, f: F) -> Parallel<'a, T> 110 | where 111 | F: FnOnce() -> T + Send + 'a, 112 | T: Send + 'a, 113 | { 114 | self.closures.push(Box::new(f)); 115 | self 116 | } 117 | 118 | /// Adds a cloned closure for each item in an iterator. 119 | /// 120 | /// Each clone of the closure takes an item as an argument. 121 | /// 122 | /// # Examples 123 | /// 124 | /// ``` 125 | /// use easy_parallel::Parallel; 126 | /// 127 | /// Parallel::new() 128 | /// .each(0..5, |i| println!("hello from thread #{}", i)) 129 | /// .run(); 130 | /// ``` 131 | pub fn each(mut self, iter: I, f: F) -> Parallel<'a, T> 132 | where 133 | I: IntoIterator, 134 | F: FnOnce(A) -> T + Clone + Send + 'a, 135 | A: Send + 'a, 136 | T: Send + 'a, 137 | { 138 | for t in iter.into_iter() { 139 | let f = f.clone(); 140 | self.closures.push(Box::new(|| f(t))); 141 | } 142 | self 143 | } 144 | 145 | /// Runs each closure on a separate thread and collects their results. 146 | /// 147 | /// Results are collected in the order in which closures were added. One of the closures always 148 | /// runs on the main thread because there is no point in spawning an extra thread for it. 149 | /// 150 | /// If a closure panics, panicking will resume in the main thread after all threads are joined. 151 | /// 152 | /// # Examples 153 | /// 154 | /// ``` 155 | /// use easy_parallel::Parallel; 156 | /// use std::thread; 157 | /// use std::time::Duration; 158 | /// 159 | /// let res = Parallel::new() 160 | /// .each(1..=3, |i| 10 * i) 161 | /// .add(|| 100) 162 | /// .collect::>(); 163 | /// 164 | /// assert_eq!(res, [10, 20, 30, 100]); 165 | /// ``` 166 | pub fn collect(mut self) -> C 167 | where 168 | T: Send + 'a, 169 | C: FromIterator + Extend, 170 | { 171 | // Get the last closure. 172 | let f = match self.closures.pop() { 173 | None => return iter::empty().collect(), 174 | Some(f) => f, 175 | }; 176 | 177 | // Spawn threads, run the last closure on the current thread. 178 | let (mut results, r) = self.finish_in::<_, _, C>(f); 179 | results.extend(Some(r)); 180 | results 181 | } 182 | 183 | /// Runs each closure on a separate thread and collects their results. 184 | /// 185 | /// Results are collected in the order in which closures were added. One of the closures always 186 | /// runs on the main thread because there is no point in spawning an extra thread for it. 187 | /// 188 | /// If a closure panics, panicking will resume in the main thread after all threads are joined. 189 | /// 190 | /// # Examples 191 | /// 192 | /// ``` 193 | /// use easy_parallel::Parallel; 194 | /// use std::thread; 195 | /// use std::time::Duration; 196 | /// 197 | /// let res = Parallel::new() 198 | /// .each(1..=3, |i| 10 * i) 199 | /// .add(|| 100) 200 | /// .run(); 201 | /// 202 | /// assert_eq!(res, [10, 20, 30, 100]); 203 | /// ``` 204 | pub fn run(self) -> Vec 205 | where 206 | T: Send + 'a, 207 | { 208 | self.collect() 209 | } 210 | 211 | /// Finishes with a closure to run on the main thread, starts threads, and collects results. 212 | /// 213 | /// Results are collected in the order in which closures were added. 214 | /// 215 | /// If a closure panics, panicking will resume in the main thread after all threads are joined. 216 | /// 217 | /// # Examples 218 | /// 219 | /// ``` 220 | /// use easy_parallel::Parallel; 221 | /// use std::thread; 222 | /// use std::time::Duration; 223 | /// 224 | /// let (res, ()) = Parallel::new() 225 | /// .each(1..=3, |i| 10 * i) 226 | /// .finish(|| println!("Waiting for results")); 227 | /// 228 | /// assert_eq!(res, [10, 20, 30]); 229 | /// ``` 230 | pub fn finish(self, f: F) -> (Vec, R) 231 | where 232 | F: FnOnce() -> R, 233 | T: Send + 'a, 234 | { 235 | self.finish_in::<_, _, Vec>(f) 236 | } 237 | 238 | /// Finishes with a closure to run on the main thread, starts threads, and collects results into an 239 | /// arbitrary container. 240 | /// 241 | /// Results are collected in the order in which closures were added. 242 | /// 243 | /// If a closure panics, panicking will resume in the main thread after all threads are joined. 244 | /// 245 | /// # Examples 246 | /// 247 | /// ``` 248 | /// use easy_parallel::Parallel; 249 | /// use std::thread; 250 | /// use std::time::Duration; 251 | /// 252 | /// let (res, ()) = Parallel::new() 253 | /// .each(1..=3, |i| 10 * i) 254 | /// .finish_in::<_, _, Vec>(|| println!("Waiting for results")); 255 | /// 256 | /// assert_eq!(res, [10, 20, 30]); 257 | /// ``` 258 | pub fn finish_in(self, f: F) -> (C, R) 259 | where 260 | F: FnOnce() -> R, 261 | T: Send + 'a, 262 | C: FromIterator, 263 | { 264 | // Set up a new thread scope. 265 | thread::scope(|scope| { 266 | // Join handles for spawned threads. 267 | let mut handles = Vec::new(); 268 | 269 | // Channels to collect results from spawned threads. 270 | let mut receivers = Vec::new(); 271 | 272 | for f in self.closures.into_iter() { 273 | // Wrap into a closure that sends the result back. 274 | let (sender, receiver) = mpsc::channel(); 275 | let f = move || sender.send(f()).unwrap(); 276 | 277 | // Spawn it on the scope. 278 | handles.push(scope.spawn(f)); 279 | receivers.push(receiver); 280 | } 281 | 282 | let mut last_err = None; 283 | 284 | // Run the main closure on the main thread. 285 | let res = panic::catch_unwind(panic::AssertUnwindSafe(f)); 286 | 287 | // Join threads and save the last panic if there was one. 288 | for h in handles { 289 | if let Err(err) = h.join() { 290 | last_err = Some(err); 291 | } 292 | } 293 | 294 | // If a thread has panicked, resume the last collected panic. 295 | if let Some(err) = last_err { 296 | panic::resume_unwind(err); 297 | } 298 | 299 | // Collect the results from threads. 300 | let results = receivers.into_iter().map(|r| r.recv().unwrap()).collect(); 301 | 302 | // If the main closure panicked, resume its panic. 303 | match res { 304 | Ok(r) => (results, r), 305 | Err(err) => panic::resume_unwind(err), 306 | } 307 | }) 308 | } 309 | } 310 | 311 | impl fmt::Debug for Parallel<'_, T> { 312 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 313 | f.debug_struct("Parallel") 314 | .field("len", &self.closures.len()) 315 | .finish() 316 | } 317 | } 318 | 319 | impl Default for Parallel<'_, T> { 320 | fn default() -> Self { 321 | Self::new() 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /tests/smoke.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use easy_parallel::Parallel; 4 | 5 | #[test] 6 | fn smoke() { 7 | let m = Mutex::new(0); 8 | let v = [2, 3, 5, 7, 11]; 9 | 10 | Parallel::new() 11 | .add(|| *m.lock().unwrap() += 10) 12 | .add(|| *m.lock().unwrap() += 20) 13 | .each(v.iter(), |n| *m.lock().unwrap() += *n) 14 | .run(); 15 | 16 | assert_eq!(m.into_inner().unwrap(), 10 + 20 + v.iter().sum::()); 17 | } 18 | 19 | #[test] 20 | fn squares() { 21 | let v = [10, 20, 30]; 22 | 23 | let squares = Parallel::new().each(0..v.len(), |i| v[i] * v[i]).run(); 24 | 25 | assert_eq!(squares, [100, 400, 900]); 26 | } 27 | 28 | #[test] 29 | fn finish() { 30 | let v = [10, 20, 30]; 31 | 32 | let (squares, len) = Parallel::new() 33 | .each(0..v.len(), |i| v[i] * v[i]) 34 | .finish(|| v.len()); 35 | 36 | assert_eq!(squares, [100, 400, 900]); 37 | assert_eq!(len, 3); 38 | } 39 | --------------------------------------------------------------------------------