Skip to content

Commit cc86de9

Browse files
committed
Fix: Consistent threads capacity in Go tests
1 parent 3590961 commit cc86de9

File tree

2 files changed

+40
-14
lines changed

2 files changed

+40
-14
lines changed

golang/README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ func main() {
8484
}
8585
```
8686

87-
Notes:
88-
- Always call `Reserve(capacity)` before the first write.
89-
- Prefer single-caller writes with internal parallelism via `ChangeThreadsAdd` and internal parallel searches via `ChangeThreadsSearch`, instead of calling `Add` concurrently.
87+
Always call `Reserve(capacity)` before the first write.
9088

9189
3. Get USearch:
9290

@@ -184,3 +182,32 @@ keys, distances, err := usearch.ExactSearch(
184182
maxResults, 0, // 0 threads = auto-detect
185183
)
186184
```
185+
186+
## Concurrency
187+
188+
USearch supports concurrent operations from multiple goroutines. Use `ChangeThreadsAdd` and `ChangeThreadsSearch` to configure the number of concurrent operations allowed:
189+
190+
```go
191+
err := index.ChangeThreadsAdd(8) // Allow up to 8 concurrent additions
192+
err = index.ChangeThreadsSearch(16) // Allow up to 16 concurrent searches
193+
```
194+
195+
When using multiple goroutines, reserve at least as many threads as the number of concurrent callers:
196+
197+
```go
198+
const numWorkers = 10
199+
200+
// Reserve threads for concurrent searches
201+
_ = index.ChangeThreadsSearch(numWorkers)
202+
203+
var wg sync.WaitGroup
204+
for i := 0; i < numWorkers; i++ {
205+
wg.Add(1)
206+
go func() {
207+
defer wg.Done()
208+
keys, distances, err := index.Search(queryVector, 10)
209+
// ...
210+
}()
211+
}
212+
wg.Wait()
213+
```

golang/lib_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package usearch
22

33
import (
4-
"fmt"
4+
"errors"
55
"io"
66
"math"
77
"runtime"
@@ -786,16 +786,15 @@ func TestConcurrentSearches(t *testing.T) {
786786
// Pre-populate with data
787787
testVectors := populateIndex(t, index, 200)
788788

789-
// Let the library parallelize search internally as well
790-
_ = index.ChangeThreadsSearch(uint(runtime.NumCPU()))
791-
792789
const numGoroutines = 30
793790
const searchesPerGoroutine = 50
794791

792+
// Reserve enough threads for all concurrent search operations
793+
_ = index.ChangeThreadsSearch(numGoroutines)
794+
795795
var wg sync.WaitGroup
796-
errorChan := make(chan error, numGoroutines)
796+
errChan := make(chan error, numGoroutines)
797797

798-
// Only concurrent searches - no mixed operations
799798
for i := 0; i < numGoroutines; i++ {
800799
wg.Add(1)
801800
go func(goroutineID int) {
@@ -808,30 +807,30 @@ func TestConcurrentSearches(t *testing.T) {
808807

809808
keys, distances, err := index.Search(query, 10)
810809
if err != nil {
811-
errorChan <- err
810+
errChan <- err
812811
return
813812
}
814813

815814
// Basic validation - should find at least the exact match
816815
if len(keys) == 0 || len(distances) == 0 {
817-
errorChan <- fmt.Errorf("search returned empty results")
816+
errChan <- errors.New("search returned empty results")
818817
return
819818
}
820819

821820
// First result should be the exact match
822821
if keys[0] != Key(queryIndex) || math.Abs(float64(distances[0])) > distanceTolerance {
823-
errorChan <- fmt.Errorf("search results inconsistent: expected key %d, got %d", queryIndex, keys[0])
822+
errChan <- errors.New("search results inconsistent")
824823
return
825824
}
826825
}
827826
}(i)
828827
}
829828

830829
wg.Wait()
831-
close(errorChan)
830+
close(errChan)
832831

833832
// Check for any errors
834-
for err := range errorChan {
833+
for err := range errChan {
835834
t.Fatalf("Concurrent search failed: %v", err)
836835
}
837836
})

0 commit comments

Comments
 (0)