嵌入式成长轨迹21 【Linux应用编程强化】【Linux下的C编程 下】【多线程编程】
概念补充参见 成长轨迹27 进程管理
一 线程的基本概念
线程运行于进程空间之中,它是进程内部的一个执行单元。同一个进程的多个线程共享该进程所拥有的全部资源,而线程本身基本上不拥有系统资源,只占用一些在运行过程中必不可少的资源,包括程序计数器、寄存器和栈等。
1 多线程的意义
多线程的意义主要在于一个程序之中可以有多个执行单元同时执行,是一种多任务的、并发的工作方式,主要表现在以下几个方面。
1).提高应用程序的响应速度
2).提高多CPU系统的效率
2 线程与进程的比较
线程与进程已经在很多操作系统类教材中进行过详细的比较,笔者在此不再赘述,只说明相对于进程,线程所具有的两个主要优点。
1).线程的开销小,切换快,是一种节俭的多任务操作方式
2).线程之间的通信机制更加高效
3 多线程编程标准与线程库
目前多线程编程标准主要有三种:WIN32、OS/2和POSIX。他们拥有不同的接口,其中前两种都是专用的,只能应用于他们各自的系统平台上,POSIX标准(Portable Operating System Interface of Unix,可移植操作系统接口)最初是为了提高Unix环境下应用程序的可移植性而提出的,现在它已经不局限于Unix系统,其它许多操作系统,如DEC OpenVMS和Microsoft Windows NT,都支持POSIX标准,当然也包括Linux系统。
4 Linux的线程机制
从内核的角度来说,Linux系统并没有线程这个概念,内核也没有准备特别的调度算法或是定义特别的数据结构来表征线程,而是把所有的线程都当作进程来实现。每一个线程都拥有惟一隶属于它的task_struct,在内核中它看起来就是一个普通的进程,只是该进程和其他一些进程共享某一些资源,例如地址空间等。
二 线程的基本操作
P.S. gcc编译的时候注意要在最后加上-lpthread
如 : gcc -o haha haha.c -lpthread
1 线程的创建
extern int pthread_create (pthread_t *__restrict __newthread, __const pthread_attr_t *__restrict __attr, void *(*__start_routine) (void *), void *__restrict __arg);
2 线程的合并
合并两个线程所使用的函数为pthread_join,它的作用是使一个线程等待另一个线程结束,将他们合并在一起。在此过程中,pthread_join函数会阻塞调用它的线程直到指定的线程终止,它类似于前面介绍过的进程用来等待子进程的wait函数。
int pthread_join (pthread_t __th, void *__thread_return);
3 线程的终止
单个线程可以通过以下几种方式来终止:
1).线程自身调用pthread_exit函数
1 /* example3.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 void mythread(void) /* 定义新线程运行的函数 */ 6 { 7 int i; 8 for(i=0; i<3; i++) /* 连续输出字符串 */ 9 { 10 printf("This is a pthread.\n"); 11 } 12 /* 终止当前线程,并返回一个指向字符串的指针 */ 13 pthread_exit("Thank you for the CPU time.\n"); 14 } 15 int main(void) 16 { 17 pthread_t id; /* 定义线程的标识符 */ 18 int i, ret; 19 void *thread_result; /* 定义指针,用来存储线程的返回值 */ 20 ret = pthread_create(&id, NULL, (void *)mythread, NULL); /* 创建新的线程 */ 21 if(ret != 0) 22 { 23 printf ("Create pthread error.\n"); /* 如果线程创建失败,打印错误信息 */ 24 exit (1); 25 } 26 for(i=0;i<3;i++) /* 连续输出字符串 */ 27 { 28 printf("This is the main process.\n"); 29 } 30 /* 主线程阻塞,等待新建线程返回,并将返回值存储在前面定义的thread_result之中 */ 31 pthread_join(id,&thread_result); 32 printf("Thread joined, it returned: %s", (char *)thread_result); /* 输出线程返回的字符串 */ 33 return 0; 34 }
但是这样子可能看不出来线程的交叉执行,因为每个线程在分配到的时间段里已经有足够时间将整个程序执行完毕
要看出效果,就只能延长线程程序的执行时间。可以加大循环,也可以用sleep来做
1 /* example4.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 void mythread(void) /* 定义新线程运行的函数 */ 6 { 7 int i; 8 for(i=0; i<3; i++) /* 连续输出字符串 */ 9 { 10 printf("This is a pthread.\n"); 11 sleep(1); /* 强行引起线程的切换 */ 12 } 13 /* 终止当前线程,并返回一个指向字符串的指针 */ 14 pthread_exit("Thank you for the CPU time.\n"); 15 } 16 int main(void) 17 { 18 pthread_t id; /* 定义线程的标识符 */ 19 int i,ret; 20 void *thread_result; /* 定义指针,用来存储线程的返回值 */ 21 ret=pthread_create(&id,NULL,(void *)mythread, NULL); /* 创建新的线程 */ 22 if(ret != 0) 23 { 24 printf("Create pthread error.\n"); /* 如果线程创建失败,打印错误信息 */ 25 exit(1); 26 } 27 for(i=0; i<3; i++) /* 连续输出字符串 */ 28 { 29 printf("This is the main process.\n"); 30 sleep(1); 31 } 32 /* 主线程阻塞,等待新建线程返回,并将返回值存储在前面定义的thread_result之中 */ 33 pthread_join(id,&thread_result); 34 printf("Thread joined, it returned: %s", (char *)thread_result); /* 输出线程返回的字符串 */ 35 return 0; 36 }
2).其他线程调用pthread_cancel函数
1 /* example5.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 void mythread(void) /* 定义新线程运行的函数 */ 6 { 7 int i,ret; 8 ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); /* 设置线程的取消状态 */ 9 if(ret != 0) 10 { 11 printf("Thread pthread_setcancelsate failed."); /* 如果取消状态未设置成功,打印错误信息 */ 12 exit(1); 13 } 14 ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); /* 设置线程的取消类型 */ 15 if(ret != 0) 16 { 17 printf("Thread pthread_setcanceltype failed."); /* 如果取消类型未设置成功,打印错误信息 */ 18 exit(1); 19 } 20 for(i=0; i<10; i++) /* 连续输出字符串,同时显示运行位置 */ 21 { 22 printf("Thread is running (%d) ...\n",i); 23 sleep(1); 24 } 25 pthread_exit("Thank you for the CPU time.\n"); /* 终止当前线程 */ 26 } 27 int main(void) 28 { 29 pthread_t id; /* 定义线程的标识符 */ 30 int i, ret; 31 void *thread_retult; /* 定义指针,用来存储线程的返回值 */ 32 ret = pthread_create(&id, NULL, (void *)mythread, NULL); /* 创建新的线程 */ 33 if(ret != 0) 34 { 35 printf("Create pthread error.\n"); /* 如果线程创建失败,打印错误信息 */ 36 exit(1); 37 } 38 sleep(3); 39 printf("Canceling thread ...\n"); 40 ret = pthread_cancel(id); /* 取消新建线程 */ 41 if(ret != 0) 42 { 43 printf("Thread cancelation failed.\n"); /* 如果线程取消失败,打印错误信息 */ 44 exit(1); 45 } 46 sleep(2); 47 /* 主线程阻塞,等待新建线程返回,并将返回值存储在前面定义的thread_result之中 */ 48 pthread_join(id,&thread_retult); 49 printf("Thread joined, it returned: %s", (char *)thread_result); /* 输出线程返回的字符串 */ 50 return 0; 51 }
4 线程的属性
前面介绍的线程创建和线程取消操作中,都涉及到线程的属性,这包括取消状态与取消类型,这一小节中我们将对的另外两个比较重要的属性及其操作函数进行介绍。
线程属性的数据结构为pthread_attr_t,它是一个联合体,定义在头文件/usr/include/bits/pthreadtypes之中。
int pthread_attr_init (pthread_attr_t *__attr) __THROW __nonnull ((1));
1).线程的分离状态int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate)
1 /* example6.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 int thread_finished=0; /* 定义一个全局变量,来标志线程是否已经终止 */ 6 void mythread(void) /* 定义新线程运行的函数 */ 7 { 8 printf("Thread is running ...\n"); 9 sleep(3); 10 printf("Thread is exiting now.\n"); 11 thread_finished = 1; /* 线程终止前设置状态标志 */ 12 pthread_exit(NULL); 13 } 14 int main(void) 15 { 16 pthread_t id; /* 定义线程的标识符 */ 17 pthread_attr_t thread_attr; /* 定义线程的属性对象 */ 18 int ret; 19 ret = pthread_attr_init(&thread_attr); /* 对线程的属性对象进行初始化 */ 20 if(ret!=0) /* 如果初始化失败,输出错误信息 */ 21 { 22 printf ("Attribute creation failed.\n"); 23 exit (1); 24 } 25 /* 将线程设置为分离状态 */ 26 ret=pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); 27 if(ret!=0){ 28 printf ("Setting detached attribute failed.\n"); /* 如果属性设置失败,打印错误信息 */ 29 exit (1); 30 } 31 ret=pthread_create(&id, &thread_attr, (void *)mythread, NULL); /* 创建新的线程 */ 32 if(ret!=0){ 33 printf ("Thread creation failed.\n"); 34 exit (1); 35 } 36 while(!thread_finished) /* 检测线程是否已经终止 */ 37 { 38 printf("Waiting for thread finished ...\n"); 39 sleep(1); 40 } 41 printf("Thread finished.\n"); /* 线程已经终止,输出信息 */ 42 return 0; 43 }
2).线程的优先级
线程的优先级使用函数pthread_attr_getschedparam和pthread_attr_setschedparam进行存放,这两个函数的定义也是在/usr/include/pthread.h之中。一般情况下,我们总是先读取优先级,对取得的值进行修改,然后再存放回去。
三 线程的同步
1 互斥量
互斥量的数据类型为
pthread_mutex_t
int pthread_mutex_init (pthread_mutex_t *__mutex,__const pthread_mutexattr_t *__mutexattr)
int pthread_mutex_destroy (pthread_mutex_t *__mutex)
int pthread_mutex_lock (pthread_mutex_t *__mutex)int pthread_mutex_unlock (pthread_mutex_t *__mutex)
1 /* example8.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 pthread_mutex_t mutex; /* 定义互斥量 */ 6 int x; /* 定义全局变量 */ 7 void thread1(void) /* 定义线程1运行的函数,其功能是对全局变量x进行逐减操作 */ 8 { 9 while(x>0) 10 { 11 pthread_mutex_lock(&mutex); /* 对互斥量进行加锁操作 */ 12 printf("Thread 1 is running : x=%d \n",x); 13 x--; 14 pthread_mutex_unlock(&mutex); /* 对互斥量进行解锁操作 */ 15 sleep(1); 16 } 17 pthread_exit(NULL); 18 } 19 void thread2(void) /* 定义线程2运行的函数,功能与thread2相同 */ 20 { 21 while(x>0) 22 { 23 pthread_mutex_lock(&mutex); /* 对互斥量进行加锁操作 */ 24 printf("Thread 2 is running : x=%d \n",x); 25 x--; 26 pthread_mutex_unlock(&mutex); /* 对互斥量进行解锁操作 */ 27 sleep(1); 28 } 29 pthread_exit(NULL); 30 } 31 int main(void) 32 { 33 pthread_t id1,id2; /* 定义线程的标识符 */ 34 int ret; 35 ret = pthread_mutex_init(&mutex,NULL); /* 对互斥量进行初始化,这里使用默认的属性 */ 36 if(ret != 0) 37 { 38 printf ("Mutex initialization failed.\n"); /* 如果初始化失败,打印错误信息 */ 39 exit (1); 40 } 41 x=10; /* 对全局变量赋初值 */ 42 ret = pthread_create(&id1, NULL, (void *)&thread1, NULL); /* 创建线程1 */ 43 if(ret != 0) 44 { 45 printf ("Thread1 creation failed.\n"); 46 exit (1); 47 } 48 ret = pthread_create(&id2, NULL, (void *)&thread2, NULL); /* 创建线程2 */ 49 if(ret != 0) 50 { 51 printf ("Thread2 creation failed.\n"); 52 exit (1); 53 } 54 pthread_join(id1, NULL); /*线程合并 */ 55 pthread_join(id2, NULL); 56 return (0); 57 }
2 条件变量
条件变量的数据类型为
pthread_cond_t
int pthread_cond_init (pthread_cond_t *__restrict __cond, __const pthread_condattr_t *__restrict__cond_attr)
int pthread_cond_destroy (pthread_cond_t *__cond)
int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)int pthread_cond_timedwait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex, __const struct timespec *__restrict__abstime)
int pthread_cond_signal (pthread_cond_t *__cond)
int pthread_cond_broadcast (pthread_cond_t *__cond)
1 /* example9.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 pthread_mutex_t mutex; /* 定义互斥量 */ 6 pthread_cond_t cond; /* 定义条件变量 */ 7 int x; /* 定义全局变量 */ 8 void producer(void) /* 定义生产者线程运行的函数,其功能是对全局变量x进行逐加操作 */ 9 { 10 while(1) 11 { 12 pthread_mutex_lock(&mutex); /* 对互斥量进行加锁操作 */ 13 int i; 14 for(i=0;i<3-x;i++) /* 当x的值小于3时,进行生产,即对x进行递加操作 */ 15 { 16 x++; 17 printf("Producing : x=%d \n",x); 18 sleep(1); 19 } 20 if(x>=3) /* 当x的值等于3时,通知消费者 */ 21 { 22 pthread_cond_signal(&cond); /* 激活等待的消费者线程 */ 23 printf("Producing completed.\n",x); 24 } 25 pthread_mutex_unlock(&mutex); /* 对互斥量进行解锁操作 */ 26 sleep(1); 27 } 28 pthread_exit(NULL); /* 终止当前线程 */ 29 } 30 void consumer(void) /* 定义消费者线程运行的函数,其功能是对全局变量x进行逐减操作 */ 31 { 32 while(1) 33 { 34 pthread_mutex_lock(&mutex); /* 对互斥量进行加锁操作 */ 35 while(x<3) 36 { 37 pthread_cond_wait(&cond,&mutex); /*阻塞消费者线程*/ 38 printf("Start consuming.\n",x); 39 } 40 while(x>0) /* 当x的值大于0时,进行消费,即对x进行递减操作 */ 41 { 42 x--; 43 printf("Consuming : x=%d \n",x); 44 sleep(1); 45 } 46 pthread_mutex_unlock(&mutex); /* 对互斥量进行解锁操作 */ 47 } 48 pthread_exit(NULL); /* 终止当前线程 */ 49 } 50 //53 51 int main(void) 52 { 53 pthread_t id1,id2; /* 定义线程的标识符 */ 54 int ret; 55 ret = pthread_mutex_init(&mutex, NULL); /* 对互斥量进行初始化 */ 56 if(ret != 0) 57 { 58 printf ("Mutex initialization failed.\n"); 59 exit (1); 60 } 61 ret = pthread_cond_init(&cond, NULL); /* 对条件变量进行初始化 */ 62 if(ret != 0) 63 { 64 printf ("Conditions initialization failed.\n"); 65 exit (1); 66 } 67 ret = pthread_create(&id1, NULL, (void *)&producer, NULL); /* 创建生产者线程 */ 68 if(ret != 0) 69 { 70 printf ("Thread Producer creation failed.\n"); 71 exit (1); 72 } 73 ret = pthread_create(&id2, NULL, (void *)&consumer, NULL); /* 创建消费者线程 */ 74 if(ret != 0) 75 { 76 printf ("Thread Consumer creation failed.\n"); 77 exit (1); 78 } 79 pthread_join(id1,NULL); /*线程合并 */ 80 pthread_join(id2,NULL); 81 return (0); 82 }
3 信号量
信号量本质上是一个非负的整数计数器,它被用来控制对共享资源的访问。信号量的数据类型为sem_t,在声明后必须进行初始化,不再需要时应注销以释放系统资源,它的初始化和注销函数分别为sem_init和sem_destroy,
int sem_post (sem_t *__sem)
int sem_wait (sem_t *__sem)
1 /* example10.c */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <pthread.h> 6 #include <semaphore.h> /* 注意:要使用信号量,必须包含该头文件 */ 7 sem_t sem; /* 定义信号量 */ 8 char buffer[256]; /* 定义一个缓冲区,用于存放键盘输入的数据 */ 9 void mythread(void) /* 新线程运行的函数,主要实现字符个数的统计 */ 10 { 11 sem_wait(&sem); /* 阻塞当前线程 */ 12 while(strncmp("exit",buffer,4)!=0) /* 统计字符个数,直至输入的字符为“exit” */ 13 { 14 printf("You input %d characters.\n", strlen(buffer)-1); 15 sem_wait(&sem); 16 } 17 pthread_exit(NULL); /* 终止当前线程 */ 18 } 19 int main(void) 20 { 21 pthread_t id; /* 定义线程的标识符 */ 22 int ret; 23 ret = sem_init(&sem,0,0); /* 对信号量进行初始化 */ 24 if(ret != 0) 25 { 26 printf ("Semaphare initialization failed.\n"); 27 exit (1); 28 } 29 ret = pthread_create(&id, NULL, (void *)&mythread, NULL); /* 创建新的线程 */ 30 if(ret != 0) 31 { 32 printf ("Thread creation failed.\n"); 33 exit (1); 34 } 35 printf ("Input some text. Enter '.' to finish.\n"); 36 while(strncmp("exit",buffer,4)!=0) /* 获取键盘输入的字符串,将其存入缓冲区 */ 37 { 38 fgets(buffer,256,stdin); 39 sem_post(&sem); /* 激活阻塞在信号量上的线程 */ 40 } 41 pthread_join(id, NULL); /* 合并线程*/ 42 printf("Thread joined.\n"); 43 sem_destroy(&sem); /* 注销信号量 */ 44 return (0); 45 }
四 常见面试题
常见面试题1:什么是线程?相比于进程,主要有哪些优点?
常见面试题2:实现线程间同步主要有哪几种方法?
五 小结
这次,我们对多线程编程的相关内容进行了较为全面的介绍,这包括线程的基本概念,线程的操作,以及线程之间的同步,同时也给大家提供了一些多个线程的程序示例。多线程编程是一项非常实用的技术,但它的设计和调试相对来说比较复杂,往往会遇到很多问题。要真正掌握多线程程序设计,需要对Linux系统线程机制进行深入的研究和大量的编程实践。