背景知识:
在前一个实训中我们介绍了进程,但有时人们认为用fork调用来创建新进程的代价太高。在这种情况下,如果能让一个进程同时做零件事情或至少看起来是这样将会非常有用。而且,你可能希望能有两件或更多的事情以一种非常紧密的方式同时发生。这就是需要线程发挥作用的时候了。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
1、创建线程
任务描述:
- 在主线程中创建一个新线程
- 在新线程中输出运行信息,在结束时返回主线程
相关知识:
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数说明:
thread:指向pthread_create类型的指针,用于引用新创建的线程。
attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。
*(*start_routine)(void *):传递新线程所要执行的函数地址。
arg:新线程所要执行的函数的参数。
thread_exit函数
原型:void pthread_exit(void *retval)
功能:使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
说明:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来检索获取。如果在线程中使用exit()函数退出,那么整个的进程将会退出,那么如果此时你还有一些其它需要做的事情没有完成呢,这并不是我们所希望的。
pthread_join函数
原型:extern int pthread_join __P ((pthread_t __th, void **__thread_return));
功能:函数pthread_join用来等待一个线程的结束。这个函数是一个线程阻塞的函数,调用它的线程将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了
说明:第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用 pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。
编译和链接:
gcc –o thread1 thread1.c –lpthread
main.c:
#include<pthread.h> #include<stdio.h> #include<stdlib.h> void print_message_function(void *ptr) { int i; for(i=0;i<5;i++) printf("%s:%d\n",(char *)ptr,i); pthread_exit("success"); } int main(void) { int tmp1; void *retval; //pthread_t用于声明线程ID pthread_t thread1; char *message1="thread1"; int ret_thrd1; ret_thrd1=pthread_create(&thread1,NULL,(void *)&print_message_function,(void *)message1); if(ret_thrd1!=0) printf("fail to create thread1\n"); else printf("success to create thread1\n"); //挂起并等待thread1线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值 tmp1=pthread_join(thread1,&retval); printf("%s\n",(char *)retval); if(tmp1!=0){ printf("can‘t join with thread1\n");return -1;}printf("thread1 end\n"); return0;}
2、在两个线程之间实现交替执行输出
任务描述:
- 主线程先输出“This is a thread0”,然后新线程输出”this is thread2“,一直交替到结束
- 同时访问全局变量,修改变量的值,并打印
相关知识:
如果电脑是单核的,按照操作系统理论严格来说,多线程并不是多个线程一起运行的。因为多线程实际上是多个线程之间轮流执行的,就是将一个时间段分成若干个时间片,每个线程只运行一个时间片,由于时间片极短,而且电脑运行极快,线程之间切换也极快,几乎可以看做是并行运行的,也就是说可以看成是同时运行的.但实际却不是的同时运行的。
main.c:
#include<stdio.h> #include<stdlib.h> #include<pthread.h> int flag=0,a=0; void *thread() { int i=0; while(i<10){ if(flag==1){ i++;a++; printf("This is a pthread,a=%d\n",a); flag=0; } } pthread_exit(NULL); } int main(void) { pthread_t id; int i=0,ret; ret=pthread_create(&id,NULL,thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit(1); } while(i<10){ if(flag==0){ i++; a++; printf("This is the main process,a=%d\n",a); flag=1; } } pthread_join(id,NULL); return 0; }
3、使用信号量进行线程同步
任务描述:
- 主线程输入字符,输入end结束
- 副线程统一字符个数,使用信号量进行同步
相关知识:
①信号量创建
信号量通过sem_init函数创建,它的定义如下所示:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明:
sem:信号量对象。
pshared:控制信号量的类型,0表示这个信号量是当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享。
value:信号量的初始值。
这个函数初始化由sem指向的信号量对象,设置它的共享选项,并给它一个初始的整数值。pshared参数控制信号量的类型。
②信号量控制
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都以一个指针为参数,该指针指向的对象是由sem_init调用初始化的信号量。
sem_post的作用是以原子操作的方式给信号量的值加1。所谓原子操作是指,如果两个线程企图同时给一个信号量加1,它们之间不会互相干扰,而不像如果两个程序同时对同一个文件进行读取、增加、写入操作时可能会引起冲突。
sem_wait的作用是以原子操作的方式给信号量的值减1,但它会等到信号量非0时才会开始减法操作。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有线程增加了该信号量的值使其不再为0。
还有另外一个信号量函数sem_trywait,它是sem_wait的非阻塞版本。
③信号量销毁
#include <semaphore.h>
int sem_destory(sem_t *sem);
与前几个函数一样,这个函数也以一个信号量指针为参数。并清理该信号量所拥有的资源。如果试图清理的信号量正被一些线程等待,就会收到一个错误。
与大多数Linux函数一样,这些函数在成功时都返回0。
#include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #include <string.h> //线程函数 void *thread_func(void *msg); sem_t sem;//信号量 #define MSG_SIZE 512 void* thread_func(void *msg) { //此时信号量为0,子线程阻塞自身等待主线程信号量+1 sem_wait(&sem); char *ptr = msg; while(strcmp("end\n", msg) != 0){ int i = 0; for(; ptr[i] != ‘\0‘; ++i); printf("You input %d characters\n", i-1); //此时信号量为1,把信号量减1 sem_wait(&sem); } //退出线程 pthread_exit(NULL); } int main(void) { int res = -1; pthread_t thread; void *thread_result = NULL; char msg[MSG_SIZE]; //初始化信号量,其初值为0 res = sem_init(&sem, 0, 0); if(res == -1){ perror("semaphore intitialization failed\n"); exit(EXIT_FAILURE); } //创建线程,并把msg作为线程函数的参数 res = pthread_create(&thread, NULL, thread_func,(void *)msg); if(res != 0){ perror("pthread_create failed\n"); exit(EXIT_FAILURE); } //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n” printf("Input some text. Enter ‘end‘to finish...\n"); while(strcmp("end\n", msg) != 0){ fgets(msg, MSG_SIZE, stdin); //把信号量加1,让子线程执行 sem_post(&sem); } printf("Waiting for thread to finish...\n"); //等待子线程结束 res = pthread_join(thread, &thread_result); if(res != 0){ perror("pthread_join failed\n"); exit(EXIT_FAILURE); } printf("Thread joined\n"); //清理信号量 sem_destroy(&sem); exit(EXIT_SUCCESS); return 0; }
4、用互斥量实现同步
任务描述:
- 模拟实现一个简单的火车售票系统,两个线程分别打印出票号,票号不能重复
相关知识:
另一种用在多线程程序中同步访问的方法是使用互斥量。它允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁它。
用于互斥量的基本函数和用于信号量的函数非常相似,它们的定义如下所示:
#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,所以必须对函数的返回代码进行检查。
与信号量类似,这些函数的参数都是一个先前申明过的对象的指针。对互斥量来说,这个对象的类型为pthread_mutex_t.
pthread_mutex_init函数中的属性参数允许我们设置互斥量的属性,而属性控制着互斥量的行为。属性类型默认为fast,但它有一个缺点:如果程序试图对一个已经加了锁的互斥量调用pthread_mutex_lock,程序就会被阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以互斥量就永远也不会被解锁了,程序也就进入死锁状态。这个问题可以通过改变互斥量的属性来解决。
main.c:
#include <unistd.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <string.h> //声明线程函数和互斥量 void* thread_func(void *msg); pthread_mutex_t mutex; #define MSG_SIZE 512 int a=0,flag=0; void* thread_func(void *msg) { int i = 1; char *ptr = msg; while(i<10){ //把互斥量mutex加锁,以确保同一时间只有该线程可以访问 pthread_mutex_lock(&mutex); if(flag==0){ i++; a++; printf("This is the main process,a=%d\n",a); flag=1; sleep(1); } //把互斥量mutex解锁,让其他的线程可以访问 pthread_mutex_unlock(&mutex); } //退出线程 pthread_exit(NULL); } int main(void) { int res = -1,i=1; pthread_t thread; void *thread_result = NULL; char msg[MSG_SIZE] = {‘\0‘}; //初始化互斥量,使用默认的互斥量属性 res = pthread_mutex_init(&mutex, NULL); if(res != 0){ perror("pthread_mutex_init failed\n"); exit(EXIT_FAILURE); } //创建子线程,并把msg作为线程函数的参数传递给thread_func res = pthread_create(&thread, NULL, thread_func, msg); if(res != 0){ perror("pthread_create failed\n"); exit(EXIT_FAILURE); } while(i<10){ //把互斥量mutex加锁,以确保同一时间只有该线程可以访问 pthread_mutex_lock(&mutex); if(flag==1){ i++;a++; printf("This is a pthread,a=%d\n",a); flag=0; sleep(1); } //把互斥量mutex解锁,让其他的线程可以访问 pthread_mutex_unlock(&mutex); } printf("\nWaiting for thread finish...\n"); //等待子线程结束 res = pthread_join(thread, &thread_result); if(res != 0){ perror("pthread_join failed\n"); exit(EXIT_FAILURE); } printf("Thread joined\n"); //清理互斥量 pthread_mutex_destroy(&mutex); exit(EXIT_SUCCESS); return 0; }
5、线程属性设置
任务描述:
- 创建一个脱线线程,即该线程不需要返回主线程.可以通过修改线程属性来设置
相关知识:
在前面的示例中,我们都在程序退出之前用pthread_join对线程再次进行同步,如果我们想让线程向创建它的线程返回数据就需要这样做。但有时也会有这种情况,我们既不需要第二个线程向主线程返回信息,也不想主线程等待它结束。
我们可以创建这一类型的线程,它们被称为脱离线程(detached thread)。可以通过修改线程属性或调用pthread_detach的方法来创建它们。
第一种方法需要用到的最重要的函数是pthread_attr_init,它的作用是初始化一个线程属性对象。
下面来介绍一些常用函数
(1) int pthread_attr_init (pthread_attr_t* attr);
功能:对线程属性变量的初始化。
attr:线程属性。
函数返回值:成功:0,失败:-1
(2) int pthread_attr_setscope (pthread_attr_t* attr, int scope);
功能:设置线程绑定属性。
attr:线程属性。
scope:PTHREAD_SCOPE_SYSTEM(绑定);PTHREAD_SCOPE_PROCESS(非绑定)
函数返回值:成功:0,失败:-1
(3) int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
功能:设置线程分离属性。
attr:线程属性。
detachstate:PTHREAD_CREATE_DETACHED(分离);PTHREAD_CREATE_JOINABLE(非分离)
函数返回值:成功:0,失败:-1
(4) int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
功能:设置创建线程的调度策略。
attr:线程属性;
policy:线程调度策略:SCHED_FIFO、SCHED_RR和SCHED_OTHER。
函数返回值:成功:0,失败:-1
(5) int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
功能:设置线程优先级。
attr:线程属性。
param:线程优先级。
函数返回值:成功:0,失败:-1
(6) int pthread_attr_destroy (pthread_attr_t* attr);
功能:对线程属性变量的销毁。
attr:线程属性。
函数返回值:成功:0,失败:-1
(7)其他
int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);//设置新创建线程栈的保护区大小。
int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);//决定怎样设置新创建线程的调度属性。
int pthread_attr_setstack(pthread_attr_t* attr, void* stackader,size_t stacksize);//两者共同决定了线程栈的基地址以及堆栈的最小尺寸(以字节为单位)。
int pthread_attr_setstackaddr(pthread_attr_t* attr, void* stackader);//决定了新创建线程的栈的基地址。
int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);//决定了新创建线程的栈的最小尺寸(以字节为单位)。
main.c:
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h> char message[]="hello world"; int thread_finished=0; void *thread(void *arg) { printf("thread is running\n"); sleep(5); thread_finished=1; pthread_exit(NULL); } int main(void) { int res; pthread_t a_thread; pthread_attr_t thread_attr; //初始化线程属性对象 res=pthread_attr_init(&thread_attr); if(res!=0){ perror("Attribute creation failed\n"); exit(EXIT_FAILURE); } res=pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED); if(res!=0){ perror("Setting detached attribute failed\n"); exit(EXIT_FAILURE); } res=pthread_create(&a_thread,&thread_attr, thread,(void *)message); if (res!=0){ perror("Thread creation failed\n"); exit(EXIT_FAILURE); } (void)pthread_attr_destroy(&thread_attr); while(!thread_finished){ sleep(1); printf("Waiting for thread to finish...\n"); } printf("thread finished,bye\n"); exit(EXIT_SUCCESS); return 0; }
6、取消一个线程
任务描述:
- 在主线程中取消创建的线程
相关知识:
有时,我们想让一个线程可以要求另一个线程终止,线程有方法做到这一点,与信号处理一样,线程可以在被要求终止时改变其行为。
先来看用于请求一个线程终止的函数:
int pthread_cancel(pthread_t thread);
这个函数简单易懂,提供一个线程标识符,我们就可以发送请求来取消它。
线程可以用pthread_setcancelstate设置自己的取消状态。
int pthread_setcancelstate(int state, int *oldstate);
参数说明:
state:可以是PTHREAD_CANCEL_ENABLE允许线程接收取消请求,也可以是PTHREAD_CANCEL_DISABLE忽略取消请求。
oldstate:获取先前的取消状态。如果对它没兴趣,可以简单地设置为NULL。如果取消请求被接受了,线程可以进入第二个控制层次,用pthread_setcanceltype设置取消类型。
int pthread_setcanceltype(int type, int *oldtype);
参数说明:
type:可以取PTHREAD_CANCEL_ASYNCHRONOUS,它将使得在接收到取消请求后立即采取行动;另一个是PTHREAD_CANCEL_DEFERRED,它将使得在接收到取消请求后,一直等待直到线程执行了下述函数之一后才采取行动:pthread_join、pthread_cond_wait、pthread_cond_timedwait、pthread_testcancel、sem_wait或sigwait。
oldtype:允许保存先前的状态,如果不想知道先前的状态,可以传递NULL。
默认情况下,线程在启动时的取消状态为PTHREAD_CANCEL_ENABLE,取消类型是PTHREAD_CANCEL_DEFERRED。
main.c:
#include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h> void *thread() { int i,res; res=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); if(res!=0){ perror("Thread pthread_seetcancelstate failed\n"); exit(EXIT_FAILURE); } res=pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); if(res!=0){ perror("Thread pthread_seetcanceltype failed\n"); exit(EXIT_FAILURE); } for(i=0;i<10;i++){ printf("Thread is running ...\n"); sleep(1); } pthread_exit(0); } int main(void) { int res; pthread_t a_thread; void *thread_result; res=pthread_create(&a_thread, NULL, thread, 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); } printf("thread finished\n"); exit(EXIT_SUCCESS); return 0; }
7、创建多线程
任务描述:
- 要求这些线程以随机的顺序结束
- 在每个创建的线程下打印线程产生顺序,例如:如果是第三个产生的线程,打印"我是第三个线程".在线程结束的时候打印"第三个线程结束了"
main.c:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<stdlib.h> #include<pthread.h> #define NUM_THREADS 6 void *thread_function(void *arg) { int my_number = *(int *)arg; int rand_num; printf("I‘m numner %d thread\n", my_number); rand_num=1+(int)(9.0*rand()/(RAND_MAX+1.0)); sleep(rand_num); printf("number %d thread end\n",my_number); pthread_exit(NULL); } int main(void) { int res; pthread_t a_thread[NUM_THREADS]; void *thread_result; int lots_of_threads; for(lots_of_threads = 0;lots_of_threads<NUM_THREADS;lots_of_threads++) { res=pthread_create(&(a_thread[lots_of_threads]),NULL, thread_function, (void *)&lots_of_threads); if (res != 0){ perror("Thread creation failed"); exit(EXIT_FAILURE); } sleep(1); } printf("Waiting for threads to finish...\n"); for(lots_of_threads = NUM_THREADS - 1; lots_of_threads >= 0;lots_of_threads--){ res = pthread_join(a_thread[lots_of_threads], &thread_result); if(res != 0) { perror("pthread_join failed\n"); } } printf("All done\n"); exit(EXIT_SUCCESS); return 0; }