Gurobi’s environment creation and deletion are not thread-safe. In a multithreaded application, race conditions can occur if multiple threads create or free environments at the same time. The recommended approach is to handle environment creation and deletion on the main thread, passing each additional thread an initialized environment for its exclusive use.
Once an environment is created, subsequent function calls, such as creating a model, optimizing it, and retrieving results, are thread-safe when each thread uses its own environment. Below is an example in the Gurobi C API showing how to safely run four Gurobi instances in parallel while following these rules.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gurobi_c.h"
typedef struct {
GRBenv *env; /* Environment for thread to use */
double objcon; /* Some data used to create a model */
int error; /* Error code to report back */
char errormsg[255];
} WorkerArg;
/* Worker thread: create and solve a simple model in the given environment
*/
static void *worker_main(void *argptr) {
int error = 0;
WorkerArg *arg = (WorkerArg *)argptr;
GRBenv *env = arg->env;
GRBmodel *model = NULL;
error = GRBnewmodel(env, &model, "example", 0, NULL, NULL, NULL, NULL, NULL);
if (error)
goto QUIT;
error = GRBsetdblattr(model, GRB_DBL_ATTR_OBJCON, arg->objcon);
if (error)
goto QUIT;
error = GRBoptimize(model);
if (error)
goto QUIT;
QUIT:
if (error) {
snprintf(arg->errormsg, sizeof(arg->errormsg), "%s", GRBgeterrormsg(env));
}
if (model) {
GRBfreemodel(model);
}
arg->error = error;
return 0;
}
int main(void) {
int error = 0;
int errorenv = 0;
const int N = 4;
char logfile[64];
GRBenv *envs[4] = {NULL};
WorkerArg args[4];
pthread_t threads[4];
/* Create environments for use by worker threads */
for (int i = 0; i < N; i++) {
snprintf(logfile, sizeof(logfile), "worker_%d.log", i);
error = GRBemptyenv(&envs[i]);
if (error) {
errorenv = i;
goto QUIT;
}
error = GRBsetintparam(envs[i], GRB_INT_PAR_LOGTOCONSOLE, 0);
if (error) {
errorenv = i;
goto QUIT;
}
error = GRBsetstrparam(envs[i], GRB_STR_PAR_LOGFILE, logfile);
if (error) {
errorenv = i;
goto QUIT;
}
error = GRBstartenv(envs[i]);
if (error) {
errorenv = i;
goto QUIT;
}
}
/* Start each thread with its own environment */
for (int i = 0; i < N; ++i) {
args[i].env = envs[i];
args[i].objcon = (double)i;
int rc = pthread_create(&threads[i], NULL, worker_main, &args[i]);
if (rc != 0) {
fprintf(stderr, "pthread_create failed for thread %d (rc=%d)\n", i, rc);
return 1;
}
printf("Thread %d started\n", i);
}
/* Join threads and collect errors */
for (int i = 0; i < N; ++i) {
int rc = pthread_join(threads[i], NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join failed for thread %d (rc=%d)\n", i, rc);
return 1;
}
/* Report errors encountered by the thread */
if (args[i].error) {
fprintf(stderr, "ERROR (thread %d) %d: %s\n", i, args[i].error,
args[i].errormsg);
} else {
printf("Thread %d joined, see log file worker_%d.log\n", i, i);
}
}
QUIT:
/* Print error message from the environment that issued it */
if (error) {
printf("ERROR %d: %s\n", error, GRBgeterrormsg(envs[errorenv]));
}
/* Tear down environments */
for (int i = 0; i < N; i++) {
if (envs[i]) {
GRBfreeenv(envs[i]);
}
}
return 0;
}