Pthread multithreaded programming

Pthread多线程编程

POSIX Threads (English: POSIX Threads, commonly abbreviated as Pthreads) is the POSIX thread standard that defines a set of APIs for creating and manipulating threads.

In the pthread thread library, we commonly use the following:

Prototype for the function that creates a thread in the Pthread library:

1
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*func)(void*),void *arg)

Here is a simple multithreading code. I will gradually summarize the knowledge used in multithreading by analyzing several pieces of code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define NUM 5
void *print(void *m){
char *cp=(char*)m;
for(int i=0;i<NUM;++i){
printf("%s\n",cp);
// Clear the input buffer
fflush(stdout);
sleep(1);
}
return NULL;
}
int main(int argc,char* argv[])
{
pthread_t t1,t2;
pthread_create(&t1, NULL, print, (void *)"t1 thread");
pthread_create(&t2, NULL, print, (void *)"t2 thread");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}

Note: The -lpthread parameter must be used when compiling. For example: clang++ -o pthread.exe pthread.cc -lpthread

Using a makefile is more convenient, here is the makefile I used:

1
2
3
4
5
6
7
8
9
10
11
12
# compile pthread.c
CC=clang
CBSJ=pthread.c
EXXX=pthread.exe
CANSHU=-std=c99
LPTHREAD=-lpthread
WARING=-W

start:$(OBJS)
$(CC) -o $(EXXX) $(CBSJ) $(CANSHU) $(LPTHREAD) $(WARING)
clean:
rm -f $(EXXX)

The compilation result is:

Next, let’s analyze several functions used from the pthread thread library:

pthread_create Create a new thread
Header File pthread.h
Function Prototype int pthread_create(pthread_t thread,pthread_attr_t attr,void *(*func)(void*),void *arg)
Parameter Analysis thread——Pointer to a pthread_t type variable
attr——Pointer to a pthread_attr_t type variable, or NULL
func——Pointer to the function to be run by the new thread
arg——Argument passed to func
Return Value 0——Successful return
errcode (non-zero error code)——Error

The pthread_create function creates a new execution thread, which calls func(arg) within the new thread. The new thread’s attributes can be specified by the attr parameter. If attr is NULL, the thread uses default attributes. func is a function that takes a pointer as an argument and returns a pointer upon completion. The parameters and return values are defined as pointers of type void, allowing them to point to any type of value.

pthread_join Wait for a thread to terminate
Header File pthread.h
Function Prototype int pthread_join(pthread_t thread,void **retval)
Parameters thread——The thread to wait for
retval——Pointer to a variable to store the thread’s return value
Return Value 0——Successful return
errcode (non-zero error code)——Error

pthread_join causes the calling thread to suspend until the thread specified by the thread parameter terminates. If retval is not NULL, the return value of the thread will be stored in the variable pointed to by retval.

When the thread terminates, the pthread_join function returns 0. If an error occurs, a non-zero error code is returned. It will result in an error code if a thread tries to wait for a non-existent thread, multiple threads try to wait for the same thread, or a thread tries to wait for itself.

Inter-process communication can occur via pipes, sockets, signals, exits, and the runtime environment. Inter-thread communication is also straightforward. Multiple threads run in a single process and share global variables; therefore, they can communicate by setting and reading those global variables. However, accessing shared memory can be both useful and dangerous for threads.

Consider the following code, where two threads operate on a global variable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define NUM 5
// Create a global variable for multiple threads to access
int counter=0;
void *print_counter(void* m){
for (int i = 0; i < NUM; ++i){
// Output the value of counter
printf("count = %d\n",counter);
sleep(1);
}
return NULL;
}
int main(int argc,char* argv[])
{
pthread_t t1;
// Create a thread that calls print_counter function without passing a meaningful value
pthread_create(&t1, NULL, print_counter, NULL);
// Access to counter in the main thread
for (int i = 0; i < NUM; ++i){
// Increment counter
counter++;
sleep(1);
}
pthread_join(t1, NULL);
return 0;
}

In the code above, two threads are used. The main thread executes a loop to increment the counter by 1 every second. Before entering the loop, the main thread creates a new thread, which runs a function to print the value of the counter. The main function and print_counter run in the same process, thus both have access to the counter.

The order of execution of the threads is unpredictable. This can be seen from the output results.

It can be observed that sometimes it executes in the expected way, but other times it does not.

This is because the scheduling between threads does not guarantee that the main thread will execute before the created thread. It is possible that we may execute the created thread t1 multiple times before the main thread starts, or after the main thread has entered its loop, hence the output order is not certain.

When not using sleep, the execution result looks like this:

Here is another piece of code that uses two threads to count the number of words in two files:

Sample statistics:

1
2
3
4
5
// test1.txt is 14 words
I need to follow my heart.
In order to see more of the world.
// test2.txt is 4 words
My name is zhalipeng.

The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <ctype.h>

void* count_words(void *f);

// Counter
int total_words=0;
// Create a mutex to ensure total_words is safely read and written by both threads.
pthread_mutex_t counter_lock=PTHREAD_MUTEX_INITIALIZER;
int main(int argc,char* argv[])
{
pthread_t t1,t2;
if(argc!=3){
printf("usage:%s file1 file2\n",argv[0]);
exit(1);
}
pthread_create(&t1, NULL, count_words,(void*)argv[1]);
pthread_create(&t2,NULL,count_words,(void*)argv[2]);
pthread_join(t1, NULL);
pthread_join(t2,NULL);
printf("%5d total words\n",total_words);
return 0;
}

void* count_words(void *f){
char *filename=(char*)f;
FILE *fp;
int c,prevc='\0';
if((fp=fopen(filename, "r"))!=NULL){
while((c=getc(fp))!=EOF){
// Encountering non-letter and non-digit characters after letters and numbers treats this letter or number as the end of a word
if(!isalnum(c) && isalnum(prevc)){
pthread_mutex_lock(&counter_lock);
total_words++;
pthread_mutex_unlock(&counter_lock);
}
// printf("%c",c);
prevc=c;
}
fclose(fp);
}else{
perror(filename);
}
// printf("\n");
return NULL;
}

The running result:

You can also add data to test1.txt or test2.txt:

1
2
3
4
// test2.txt is 11 words
My name is zhalipeng.
I love my family.
I love programming!

If you output the order in which threads read the two files, similar to the previous code, it will also be unordered, haha. Why do I keep paying attention to these things.

In the code above, several new pthread interfaces are used: pthread_mutex_lock and pthread_mutex_unlock, to ensure that the shared global variable can be read and written by one thread at a time.

A simple example: In an airport or bus station’s public storage locker, it remains unlocked unless someone is storing something inside. When someone stores something and takes the key, no one else can open that locker until that person returns the key and unlocks it. Similarly, if two threads need to safely share a common counter, they also need a method to lock the variable, so only one thread can read from or write to the counter at any time.

The threading system contains a variable known as a mutex, which allows better cooperation between threads and avoids conflicts in accessing variables, functions, and resources.

pthread_mutex_lock Wait for the mutex to unlock and then lock the mutex
pthread_mutex_unlock Unlock the mutex
Header File pthread.h
Function Prototype int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
Parameters mutex: Pointer to the mutex object
Return Value 0: Successful return
errcode (non-zero error code): Error

Firstly, define a global variable of type pthread_mutex_t called counter_lock, and assign it an initial value. Then, operations on the global counter total_words can be wrapped between calls to pthread_mutex_lock and pthread_mutex_unlock.

1
2
3
pthread_mutex_lock(&counter_lock);
total_words++;
pthread_mutex_unlock(&counter_lock);

Now two threads can safely share the counter. When one thread calls pthread_mutex_lock, if another thread has already locked this mutex, that thread will be blocked waiting for the lock to be released before it can operate on the counter. Each thread unlocks the mutex after manipulating the counter so the shared data can be operated on by other threads.

Any number of threads can be suspended waiting for the mutex to unlock. When a thread unlocks the mutex, the system grants control to one of the waiting threads.

Upgrade code:

  • Can accept an arbitrary number of files
  • Determines how many threads and struct objects (to store file information) to create based on the number of arguments obtained via arg
  • Uses struct to record the number of words in each individual file (each file has its own separate counter)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <pthread.h>
#include <ctype.h>
#include <stdlib.h>

void* count_words(void *f);

// Create a struct to store file information
struct arg_set{
// Store filename and counter
char *filename;
int total_words;
};

pthread_mutex_t counter_lock=PTHREAD_MUTEX_INITIALIZER;
int main(int argc,char* argv[])
{
const int NUM=argc-1; // Number of files accepted
// Create NUM threads (number of files received)
pthread_t thread[NUM];
// Create NUM struct objects for the received files
struct arg_set argSet[NUM];
for (int i = 0; i < NUM; ++i){
argSet[i].filename=argv[i+1];
argSet[i].total_words=0;
pthread_create(&thread[i], NULL, count_words,(void*)&argSet[i]);
pthread_join(thread[i],NULL);
printf("File %s in %d words\n",argSet[i].filename,argSet[i].total_words);
}
int sumWords=0;
for (int i = 0; i < NUM; ++i){
sumWords+=argSet[i].total_words;
}
printf("The total word count is %d\n",sumWords);
return 0;
}

void* count_words(void *f){
struct arg_set *args=(struct arg_set*)f;
FILE *fp;
int c,prevc='\0';
if((fp=fopen(args->filename, "r"))!=NULL){
while((c=getc(fp))!=EOF){
// Encountering non-letter and non-digit characters after letters and numbers treats this letter or number as the end of a word
if(!isalnum(c)&&isalnum(prevc)){
pthread_mutex_lock(&counter_lock);
args->total_words++;
pthread_mutex_unlock(&counter_lock);
}
// printf("%c",c);
prevc=c;
}
fclose(fp);
}else{
perror(args->filename);
}
// printf("\n");
return NULL;
}

Let’s test it:

Here, we use four files as parameters for the program, the contents of test1.txt and test2.txt are the same as above, and the contents of test3.txt and test4.txt are as follows:

1
2
3
4
5
// test3.txt is 10 words
commitment to excellence.
Courage is holding on to your dream.
// test4.txt is 3 words
Just for fun!

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:Pthread multithreaded programming
Author:LIPENGZHA
Publish Date:2016/06/04 17:08
Word Count:5.6k Words
Link:https://en.imzlp.com/posts/58408/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!