Multithreading

The pthread_create() function

In Unix, each process has a main thread and additional threads can be launched through pthread_create(), each executing the code of a function passed as a parameter.

This function pthread_create() takes 4 parameters:

The function returns 0 if everything went well and a non-null value if there was a problem in creating the thread.

#include <stdio.h>
#include <pthread.h>		// for the function pthread_create

int a=10;

void* thread_function(void* arg) {
    printf("The secondary thread: a is initially %d\n",a);
	a=20;
	sleep(1);
	printf("The secondary thread: a is next %d\n",a);
	sleep(1);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int status;

    status = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (status != 0) {
        printf("thread creation error");
		return(1);
    }
	printf("The main thread: a is initially %d\n",a);
	a=30;
	sleep(1);
	printf("The main thread: a is next %d\n",a);
	sleep(1);
	return(0);
}
	

Note: At link-time, it is necessary to specify the use of the pthread library. For example, the program thread1.c can be compiled with the following command:

gcc thread1.c -o thread1 -lpthread

Sharing sockets

When creating a thread, the code and data of the process are shared between the two tasks running in parallel. As only one process manages all the sockets in parallel, we will be limited by the number of entries that a process can have in its file descriptor table.

Example

Here is an example of a server that echoes back to the client what it receives.

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// for the function bzero
#include <arpa/inet.h>	// for the function inet_addr

#include <pthread.h>		// for the function pthread_create


void *server_dialog(void *arg) {
	int fd;
	int len;
	char buf[100];

	fd= *((int*) arg);
	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);

	shutdown(fd, SHUT_RDWR);
	close(fd);
	printf("Client disconnected by server!\n");
	return NULL;
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;
	pthread_t thread_id;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Socket creation error!\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Invalid IPv4 address!\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Server port access error!\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Queue creation error!\n");
		exit(1);
	}
	printf("Server launched!\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connection accepted!\n");
			err = pthread_create(&thread_id, NULL, server_dialog, &fd_con);

		}
	}
	return 0;
}
	

Avoiding Memory Leak

With the above code, for each new connection, a thread is created and then terminated but is not automatically destroyed after use. You can see this by consulting the file /proc/pid/status where pid is the process number of the server and observing the progressive increase in memory used by the process.

To avoid this memory leak, by automatically releasing the memory used by the thread when it ends, we add the instruction pthread_detach(thread_id); after the instruction err = pthread_create(...); (on line 68).

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// for the function bzero
#include <arpa/inet.h>	// for the function inet_addr

#include <pthread.h>		// for the function pthread_create


void *server_dialog(void *arg) {
	int fd;
	int len;
	char buf[100];

	fd= *((int*) arg);
	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);

	shutdown(fd, SHUT_RDWR);
	close(fd);
	printf("Client disconnected by server!\n");
	return NULL;
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;
	pthread_t thread_id;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Socket creation error!\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Invalid IPv4 address!\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Server port access error!\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Queue creation error!\n");
		exit(1);
	}
	printf("Server launched!\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connection accepted!\n");
			err = pthread_create(&thread_id, NULL, server_dialog, &fd_con);
			pthread_detach(thread_id);
		}
	}
	return 0;
}