package main import ( "fmt" "strconv" "time" ) // requester sends requests and waits for responses func requester(id int, reqChan chan<- string, respChan <-chan string) { for i := 0; i < 3; i++ { request := fmt.Sprintf("Request %d from Requester %d", i+1, id) fmt.Printf("Requester %d: Sending '%s' ", id, request) reqChan <- request // Send request response := <-respChan // Wait for response fmt.Printf("Requester %d: Received '%s' ", id, response) time.Sleep(500 * time.Millisecond) } } // responder receives requests and sends back responses func responder(id int, reqChan <-chan string, respChan chan<- string) { for req := range reqChan { fmt.Printf("Responder %d: Processing '%s' ", id, req) // Simulate processing time time.Sleep(100 * time.Millisecond) response := fmt.Sprintf("Processed: '%s' by Responder %d", req, id) respChan <- response // Send response } fmt.Printf("Responder %d: Shutting down. ", id) } func main() { // Channels for bidirectional communication reqs1 := make(chan string) resps1 := make(chan string) // Start a responder go responder(1, reqs1, resps1) // Start a requester go requester(1, reqs1, resps1) // Give time for goroutines to finish time.Sleep(5 * time.Second) fmt.Println("Main: Shutting down communication example.") // In a real application, you'd have explicit close/wait mechanisms, // e.g., using context.WithCancel and a waitgroup to ensure cleanup. // For this snippet, a simple sleep demonstrates the interaction. // To properly shut down responder, you'd close its input channel. // close(reqs1) // This would signal responder to exit its loop }