Subscribe with concurrency control

Configures number of threads for receiving messages and processing message callbacks.

Documentation pages that include this code sample

To view the code sample used in context, see the following documentation:

Code Sample


Before trying this sample, follow the C++ setup instructions in the Pub/Sub Quickstart Using Client Libraries. For more information, see the Pub/Sub C++ API reference documentation.

namespace pubsub = google::cloud::pubsub;
using google::cloud::future;
using google::cloud::StatusOr;
[](std::string project_id, std::string subscription_id) {
  // Create a subscriber with 16 threads handling I/O work, by default the
  // library creates `std::thread::hardware_concurrency()` threads.
  auto subscriber = pubsub::Subscriber(pubsub::MakeSubscriberConnection(
      pubsub::Subscription(std::move(project_id), std::move(subscription_id)),

  std::mutex mu;
  std::condition_variable cv;
  int count = 0;
  auto constexpr kExpectedMessageCount = 4;
  auto handler = [&](pubsub::Message const& m, pubsub::AckHandler h) {
    // This handler executes in the I/O threads, applications could use,
    // std::async(), a thread-pool,
    // google::cloud::CompletionQueue::RunAsync(), or any other mechanism to
    // transfer the execution to other threads.
    std::cout << "Received message " << m << "\n";
      std::lock_guard<std::mutex> lk(mu);
      if (++count < kExpectedMessageCount) return;

  // Create a subscription where up to 8 messages are handled concurrently. By
  // default the library uses `std::thread::hardware_concurrency()` as the
  // maximum number of concurrent callbacks.
  auto session = subscriber.Subscribe(std::move(handler));
    std::unique_lock<std::mutex> lk(mu);
    cv.wait(lk, [&] { return count >= kExpectedMessageCount; });
  auto status = session.get();
  std::cout << "Message count: " << count << ", status: " << status << "\n";


Before trying this sample, follow the Go setup instructions in the Pub/Sub Quickstart Using Client Libraries. For more information, see the Pub/Sub Go API reference documentation.

import (


func pullMsgsConcurrenyControl(w io.Writer, projectID, subID string) error {
	// projectID := "my-project-id"
	// subID := "my-sub"
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %v", err)
	defer client.Close()

	sub := client.Subscription(subID)
	// Must set ReceiveSettings.Synchronous to false (or leave as default) to enable
	// concurrency settings. Otherwise, NumGoroutines will be set to 1.
	sub.ReceiveSettings.Synchronous = false
	// NumGoroutines is the number of goroutines sub.Receive will spawn to pull
	// messages concurrently.
	sub.ReceiveSettings.NumGoroutines = runtime.NumCPU()

	// Receive messages for 10 seconds.
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	var counter int32

	// Receive blocks until the context is cancelled or an error occurs.
	err = sub.Receive(ctx, func(_ context.Context, msg *pubsub.Message) {
		// The message handler passed to Receive may be called concurrently
		// so it's okay to process the messages concurrently but make sure
		// to synchronize access to shared memory.
		atomic.AddInt32(&counter, 1)
	if err != nil {
		return fmt.Errorf("pubsub: Receive returned error: %v", err)
	fmt.Fprintf(w, "Received %d messages\n", counter)

	return nil


Before trying this sample, follow the Java setup instructions in the Pub/Sub Quickstart Using Client Libraries. For more information, see the Pub/Sub Java API reference documentation.

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class SubscribeWithConcurrencyControlExample {
  public static void main(String... args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String subscriptionId = "your-subscription-id";

    subscribeWithConcurrencyControlExample(projectId, subscriptionId);

  public static void subscribeWithConcurrencyControlExample(
      String projectId, String subscriptionId) {
    ProjectSubscriptionName subscriptionName =
        ProjectSubscriptionName.of(projectId, subscriptionId);

    // Instantiate an asynchronous message receiver.
    MessageReceiver receiver =
        (PubsubMessage message, AckReplyConsumer consumer) -> {
          // Handle incoming message, then ack the received message.
          System.out.println("Id: " + message.getMessageId());
          System.out.println("Data: " + message.getData().toStringUtf8());

    Subscriber subscriber = null;
    try {
      // Provides an executor service for processing messages. The default `executorProvider` used
      // by the subscriber has a default thread count of 5.
      ExecutorProvider executorProvider =

      // `setParallelPullCount` determines how many StreamingPull streams the subscriber will open
      // to receive message. It defaults to 1. `setExecutorProvider` configures an executor for the
      // subscriber to process messages. Here, the subscriber is configured to open 2 streams for
      // receiving messages, each stream creates a new executor with 4 threads to help process the
      // message callbacks. In total 2x4=8 threads are used for message processing.
      subscriber =
          Subscriber.newBuilder(subscriptionName, receiver)

      // Start the subscriber.
      System.out.printf("Listening for messages on %s:\n", subscriptionName.toString());
      // Allow the subscriber to run for 30s unless an unrecoverable error occurs.
      subscriber.awaitTerminated(30, TimeUnit.SECONDS);
    } catch (TimeoutException timeoutException) {
      // Shut down the subscriber after 30s. Stop receiving messages.


Before trying this sample, follow the Ruby setup instructions in the Pub/Sub Quickstart Using Client Libraries. For more information, see the Pub/Sub Ruby API reference documentation.

# subscription_name = "Your Pubsub subscription name"
require "google/cloud/pubsub"

pubsub =

subscription = pubsub.subscription subscription_name
# Use 2 threads for streaming, 4 threads for executing callbacks and 2 threads
# for sending acknowledgements and/or delays
subscriber   = subscription.listen streams: 2, threads: {
  callback: 4,
  push:     2
} do |received_message|
  puts "Received message: #{}"

# Let the main thread sleep for 60 seconds so the thread for listening
# messages does not quit
sleep 60