// Package main demonstrates concurrent usage patterns for the go-jdenticon library. // This example shows safe and efficient ways to generate identicons from multiple goroutines. package main import ( "context" "fmt" "log" "sync" "time" "gitea.dockr.co/kev/go-jdenticon/jdenticon" ) func main() { fmt.Println("Go Jdenticon - Concurrent Usage Examples") fmt.Println("========================================") // Example 1: Using package-level functions (simplest) fmt.Println("\n1. Package-level functions (uses singleton generator)") demonstratePackageLevelConcurrency() // Example 2: Shared generator instance (recommended for performance) fmt.Println("\n2. Shared generator instance (optimal performance)") demonstrateSharedGenerator() // Example 3: Cache performance monitoring fmt.Println("\n3. Cache performance monitoring") demonstrateCacheMonitoring() // Example 4: High-throughput concurrent generation fmt.Println("\n4. High-throughput concurrent generation") demonstrateHighThroughput() } // demonstratePackageLevelConcurrency shows the simplest concurrent usage pattern func demonstratePackageLevelConcurrency() { userEmails := []string{ "alice@example.com", "bob@example.com", "charlie@example.com", "diana@example.com", "eve@example.com", } var wg sync.WaitGroup for i, email := range userEmails { wg.Add(1) go func(id int, userEmail string) { defer wg.Done() // Safe: Package-level functions use internal singleton icon, err := jdenticon.Generate(context.Background(), userEmail, 64) if err != nil { log.Printf("Worker %d failed to generate icon: %v", id, err) return } // Icons are immutable and safe to use concurrently svg, err := icon.ToSVG() if err != nil { log.Printf("Worker %d failed to generate SVG: %v", id, err) return } fmt.Printf(" Worker %d: Generated %d-byte SVG for %s\n", id, len(svg), userEmail) }(i, email) } wg.Wait() fmt.Println(" ✓ All workers completed successfully") } // demonstrateSharedGenerator shows optimal performance pattern with shared generator func demonstrateSharedGenerator() { // Create custom configuration config, err := jdenticon.Configure( jdenticon.WithColorSaturation(0.8), jdenticon.WithPadding(0.1), jdenticon.WithHueRestrictions([]float64{120, 180, 240}), // Blue/green theme ) if err != nil { log.Fatalf("Failed to create config: %v", err) } // Create generator with larger cache for concurrent workload generator, err := jdenticon.NewGeneratorWithConfig(config, 1000) if err != nil { log.Fatalf("Failed to create generator: %v", err) } const numWorkers = 10 const iconsPerWorker = 5 var wg sync.WaitGroup start := time.Now() for workerID := 0; workerID < numWorkers; workerID++ { wg.Add(1) go func(id int) { defer wg.Done() for i := 0; i < iconsPerWorker; i++ { userID := fmt.Sprintf("user-%d-%d@company.com", id, i) // Safe: Multiple goroutines can use the same generator icon, err := generator.Generate(context.Background(), userID, 96) if err != nil { log.Printf("Worker %d failed to generate icon %d: %v", id, i, err) continue } // Generate both formats concurrently on the same icon var pngData []byte var svgData string var formatWg sync.WaitGroup formatWg.Add(2) go func() { defer formatWg.Done() pngData, _ = icon.ToPNG() }() go func() { defer formatWg.Done() svgData, _ = icon.ToSVG() }() formatWg.Wait() fmt.Printf(" Worker %d: Generated PNG (%d bytes) and SVG (%d bytes) for %s\n", id, len(pngData), len(svgData), userID) } }(workerID) } wg.Wait() duration := time.Since(start) totalIcons := numWorkers * iconsPerWorker fmt.Printf(" ✓ Generated %d icons in %v (%.0f icons/sec)\n", totalIcons, duration, float64(totalIcons)/duration.Seconds()) } // demonstrateCacheMonitoring shows how to monitor cache performance func demonstrateCacheMonitoring() { generator, err := jdenticon.NewGeneratorWithConfig(jdenticon.DefaultConfig(), 100) if err != nil { log.Fatalf("Failed to create generator: %v", err) } // Generate some icons to populate cache testUsers := []string{ "user1@test.com", "user2@test.com", "user3@test.com", "user4@test.com", "user5@test.com", } var wg sync.WaitGroup // First pass: populate cache fmt.Println(" Populating cache...") for _, user := range testUsers { wg.Add(1) go func(u string) { defer wg.Done() _, _ = generator.Generate(context.Background(), u, 64) }(user) } wg.Wait() hits1, misses1 := generator.GetCacheMetrics() fmt.Printf(" After first pass - Hits: %d, Misses: %d\n", hits1, misses1) // Second pass: should hit cache fmt.Println(" Requesting same icons (should hit cache)...") for _, user := range testUsers { wg.Add(1) go func(u string) { defer wg.Done() _, _ = generator.Generate(context.Background(), u, 64) }(user) } wg.Wait() hits2, misses2 := generator.GetCacheMetrics() fmt.Printf(" After second pass - Hits: %d, Misses: %d\n", hits2, misses2) if hits2 > hits1 { ratio := float64(hits2) / float64(hits2+misses2) * 100 fmt.Printf(" ✓ Cache hit ratio: %.1f%%\n", ratio) } } // demonstrateHighThroughput shows high-performance concurrent generation func demonstrateHighThroughput() { generator, err := jdenticon.NewGeneratorWithConfig(jdenticon.DefaultConfig(), 2000) if err != nil { log.Fatalf("Failed to create generator: %v", err) } const numWorkers = 20 const duration = 2 * time.Second var wg sync.WaitGroup stopChan := make(chan struct{}) // Start timer go func() { time.Sleep(duration) close(stopChan) }() start := time.Now() // Launch workers for i := 0; i < numWorkers; i++ { wg.Add(1) go func(workerID int) { defer wg.Done() count := 0 for { select { case <-stopChan: fmt.Printf(" Worker %d generated %d icons\n", workerID, count) return default: userID := fmt.Sprintf("load-test-user-%d-%d", workerID, count) _, err := generator.Generate(context.Background(), userID, 32) if err != nil { log.Printf("Generation failed: %v", err) continue } count++ } } }(i) } wg.Wait() actualDuration := time.Since(start) hits, misses := generator.GetCacheMetrics() total := hits + misses throughput := float64(total) / actualDuration.Seconds() fmt.Printf(" ✓ Generated %d icons in %v (%.0f icons/sec)\n", total, actualDuration, throughput) fmt.Printf(" ✓ Cache performance - Hits: %d, Misses: %d (%.1f%% hit rate)\n", hits, misses, float64(hits)/float64(total)*100) }