在Linux中,进程控制系统几乎所有的活动,是系统很重要的组成部分。Linux系统同时支持通过进程创建新的进程,但是创建新进程的代价较高,因为新进程有自己的数据空间、环境和文件描述符。因此,如果想让一个进程同时做两件事件,那就需要线程发挥作用。
线程是一个进程内部的控制序列,所有进程都至少有一个执行线程。当在进程中创建一个新的线程时,新的执行线程将拥有自己的栈,但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。
》》线程的优点
让程序并行执行;改善程序性能;线程的切换需要操作系统做的工作较少,因此多个线程对资源的需要远小于多个进程。
》》线程的缺点
多线程程序设计复杂;多线程程序高度难度较大;对于大量计算且在单核计算机线程运行的效率不高。
编写多线程程序需要包含头文件pthread.h,并且在编译程序时需要用选项-lpthread链接线程库。除此之外还需要定义宏_REENTRANT告诉编译器程序需要可重入功能。_REENTRANT的定义位于任何#include语句之前,完成以下功能:
》》对部分函数重新定义它们的可安全重入版本,这些函数的名字一般不会发生改变,只是会在函数名后面添加_r字符串
》》stdio.h中原来以宏的形式实现的一些函数将变成可安全重入的函数
》》在error.h中定义的变量error将成为一个函数调用,能够以一种多线程安全的方式获取真正的errno的值
下面介绍几个与线程管理相关的函数:
》》创建线程
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
创建线程时可以指定线程的属性、启动执行的函数和传递给该函数的参数。
函数调用成功时返回0,失败则返回错误代码。
》》终止线程
void pthread_exit(void *retval);
作用:终止调用它的线程并返回一个指向某个对象的指针。
》》pthread_join
int pthread_join(pthread_t th, void **thread_return);
作用:相当于进程中用来收集子进程信息的wait函数。
函数调用成功时返回0,失败时返回错误代码。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h> void *thread_function(void *arg); char message[] = "Hello World"; int main() { int res; pthread_t a_thread; void *thread_return; res = pthread_create(&a_thread, NULL, thread_function, (void *)message);//创建线程 if(res != 0) { perror("Thread creation falied\n"); exit(EXIT_FAILURE); } printf("Waiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_return); if(res != 0) { perror("Thread join failed\n"); exit(EXIT_FAILURE); } printf("Thread joined, it returned %s\n", (char *)thread_return); printf("Message is now %s\n", message); return 0; } void *thread_function(void *arg) //线程启动执行函数 { printf("thread_function is running. Argumnet was %s\n", (char *)arg); strcpy(message, "Bye!"); pthread_exit("Thank you for the CPU time"); }
》》信号量
信号量是一种特殊类型的变量,它可以被增加或减少,但对其的关键访问必须被保证是原子操作,即使在一个多线程程序中也是如此。这意味着如果一个程序中有两个或更多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。因此信号量可以被用来实现线程同步。信号量通过sem_init函数创建。
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); //pshared 参数控制信号量的类型,值为0表示这个信号量是当前进程的局部信号量,否则这个信号量可以在多个进程之间共享。 int sem_wait(sem_t *sem); int sem_post(sem_t *sem); int sem_destroy(sem_t sem); //上述函数调用成功时返回0,失败时返回错误代码。
创建信号量后,通过调用sem_wait和sem_post控制信号量的值。sem_post函数的作用是以原子操作的方式给信号量的值加1。sem_wait函数则以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。使用完信号后使用sem_destroy对它进行清理。
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <semaphore.h> void *thread_function(void *argc); sem_t bin_sem; #define WORK_SIZE 1024 char work_area[WORK_SIZE]; int main() { int res; pthread_t a_thread; void *thread_return; res = sem_init(&bin_sem, 0, 0); //创建信号量 if(res != 0) { perror("Semaphore initialization failed\n"); exit(EXIT_FAILURE); } res = pthread_create(&a_thread, NULL, thread_function, NULL); //创建线程 if(res != 0) { perror("Thread creation failed\n"); exit(EXIT_FAILURE); } printf("Input some text. Enter 'end' to finish\n"); while(strncmp("end", work_area, 3) == 0) //输入end时结束 { if(strncmp(work_area, "FAST", 4) != 0) { sem_post(&bin_sem); //信号量加1(说明有输入) strcpy(work_area, "Wheeee..."); } else { fgets(work_area, WORK_SIZE, stdin); } } printf("\nWaiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_return); if(res != 0) { perror("Thread join failed\n"); exit(EXIT_FAILURE); } printf("Thread joined\n"); sem_destroy(&bin_sem); //清理信号量 return 0; } void *thread_function(void *arg) //线程启动执行函数 { sem_wait(&bin_sem); //信号量非零时减1(说明输入缓冲区有数据,取出数据并输出) while(strncmp("end", work_area, 3) != 0) { printf("You input %d characters: %s\n", strlen(work_area) - 1, work_area); sem_wait(&bin_sem); //信号量非零时减1(说明输入缓冲区有数据,取出数据并输出) } pthread_exit(NULL); //退出线程 }》》互斥量
互斥量允许程序锁住某个对象,使得每次只能有一个线程访问它。为了控制关键代码的访问,必须在进入代码段之前锁住一个互斥量,然后在完成操作后解锁。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex); //上述函数调用执行成功时返回0,失败时返回错误代码,但是不设置errno下面的示例程序是对信号量程序的改进。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <semaphore.h> void *thread_function(void *arg); pthread_mutex_t work_mutex; #define WORK_SIZE 1024 char work_area[WORK_SIZE]; int time_to_exit = 0; int main() { int res; pthread_t a_thread; void *thread_result; // 创建互斥量 res = pthread_mutex_init(&work_mutex, NULL); if(res != 0) { perror("Mutex initialization failed\n"); exit(EXIT_FAILURE); } // 创建线程 res = pthread_create(&a_thread, NULL, thread_function, NULL); if(res != 0) { perror("Thread creation failed\n"); exit(EXIT_FAILURE); } // 互斥量加锁(代码段) pthread_mutex_lock(&work_mutex); printf("Input some text. Enter 'end' to finish\n"); while(!time_to_exit) { fgets(work_area, WORK_SIZE, stdin); pthread_mutex_lock(&work_mutex); while(1) { // 变量加锁 pthread_mutex_lock(&work_mutex); if(work_area[0] != '\0') { pthread_mutex_unlock(&work_mutex); sleep(1); } else { break; } } } pthread_mutex_unlock(&work_mutex); printf("\nWaiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_result); if(res != 0) { perror("Thread join failed\n"); exit(EXIT_FAILURE); } printf("Thread joined\n"); pthread_mutex_destroy(&work_mutex); return 0; } void *thread_function(void *arg) { sleep(1); pthread_mutex_lock(&work_mutex); //互斥量加锁 while(strncmp("end", work_area, 3) != 0) { printf("You input %d characters: %s\n", strlen(work_area) - 1, work_area); work_area[0] = '\0'; pthread_mutex_unlock(&work_mutex); //互斥量解锁 sleep(1); pthread_mutex_lock(&work_mutex); //互斥量加锁 while(work_area[0] == '\0') { pthread_mutex_unlock(&work_mutex); //互斥量解锁 sleep(1); pthread_mutex_lock(&work_mutex); //互斥量加锁 } } time_to_exit = 1; work_area[0] = '\0'; pthread_mutex_unlock(&work_mutex); //互斥量解锁 pthread_exit(0); }
在创建线程之前可以通过设置线程的发生改变所创建线程的行为。线程有很多属性可以设置,比如可取消的状态、可取消类型、并发度,通过改变这些属性可以更精确地控制线程的运行。
一个线程可以要求另一个线程终止,可能通过pthread_cancel实现。接收到终止消息的线程可以调用pthread_setcancelstate设置自己的取消状态。如果取消请求被接受,线程可以调用pthread_setcanceltype设置取消类型。取消类型有两种,PTHREAD_CANCEL_ASYNCHRONOUS使得在接收到取消请求后立即采取行动,PTHREAD_CANCEL_DEFERRED使得在接收到取消请求后,一直等待直到线程执行了pthread_join、pthread_cond_wait、pthread_cond_timewait、pthread_testcancel、sem_wait、sigwait中的任意一个函数后才采取行动。
#include <pthread.h> int pthread_cancel(pthread_t thread); int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type, int *oldtype);在下面的程序示例中,主线程向它创建的线程徉一个取消请求,接收请求的线程设置为可取消状态并将取消类型调用为PTHREAD_CANCEL_DEFERRED。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> void *thread_function(void *arg); int main() { int res; pthread_t a_thread; void *thread_result; res = pthread_create(&a_thread, NULL, thread_function, NULL); //创建线程 if(res != 0) { perror("Thread creation failed\n"); exit(EXIT_FAILURE); } sleep(3); printf("Canceling thread...\n:"); res = pthread_cancel(a_thread); //取消创建的线程 if(res != 0) { perror("Thread cancelation failed\n"); exit(EXIT_FAILURE); } printf("Waiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_result); //合并线程 if(res != 0) { perror("Thread join failed\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } void *thread_function(void *arg) { int i, res; res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //设置线程为可取消状态 if(res != 0) { perror("Thread pthread_setcancelstate failed\n"); exit(EXIT_FAILURE); } res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); //设置线程取消类型 if(res != 0) { perror("Thread pthread_setcanceltype failed\n"); exit(EXIT_FAILURE); } printf("thread_function is running\n"); for(i = 0; i < 20; i++) { printf("Thread is still running (%d)...\n", i); sleep(1); } pthread_exit(0); //退出线程 }
程序的主执行线程可以创建多个线程,并对这些线程进行控制,使得程序能够并发执行。在编写多线程程序时,需要精确地控制线程的同步,才能得到预期的结果。下面的程序创建了六个线程并以任意顺序执行。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #define NUM_THREADS 6 void *thread_function(void *arg); int main() { int i, res; pthread_t a_threads[NUM_THREADS]; void *thread_result; for(i = 0; i < NUM_THREADS; i++) //创建NUM_THREADS个线程 { res = pthread_create(&(a_threads[i]), NULL, thread_function, (void *)i); if(res != 0) { perror("Thread creation failed\n"); exit(EXIT_FAILURE); } } printf("Waiting for threads to finish...\n"); for(i = NUM_THREADS - 1; i >= 0; i--) { res = pthread_join(a_threads[i], &thread_result); //合并线程 if(res == 0) printf("Picked up a thread\n"); else perror("pthread_join failed\n"); } printf("All done\n"); exit(EXIT_SUCCESS); } void *thread_function(void *arg) //线程启动执行函数 { int num = (int)arg, ran; printf("thread_function is running. Argumnet was %d\n", num); ran = 1 + (int)(9.0 * rand() / (RAND_MAX + 1.0)); sleep(ran); printf("Bye from %d\n", num); pthread_exit(0); }
本文主要介绍如何在一个进程中创建多个执行线程,以及线程对关键代码和数据的两个访问控制方法——信号量和互斥量。并介绍了如何在创建线程时改变线程的属性从而改变线程执行状态,最后介绍取消线程以及多线程程序设计方面的知识。