一、线程
什么是线程?
- 线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。由于每个线程占用的CPU时间是由系统分配的,因此可以把线程看作操作系统分配CPU时间的基本单位。线程是交替进行的。
线程的优点:
- 在多进程情况下,每个进程都有自己独立的地址空间,而在多线程情况下,同一进程内在线程共享进程的地址空间。因此,创建一个新的进程时就要耗费时间来为其分配系统资源,而创建一个线程花费的时间要少的多。
- 在系统调度方面,由于进程地址空间独立而线程共享地址空间,线程间的切换速度要远快过进程间的切换速度。
- 在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统。而同一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统。因此线程间的通信更加方便省时。
- 线程可以提高应用程序的响应速度。在图像界面程序中,如果有一个非常耗时的操作,她会导致其他操作不能进行而等待这个操作,此时界面响应用户操作的速度会变得很慢。多线程环境下可以将这个非常耗时的操作由一个单独的线程来完成。这个线程在用完操作系统分配给他的时间片后,让出CPU,这样其他的操作便有机会执行了。
- 可以提高多处理器的效率。现在许多计算机都是采用多核技术,在这种情况下,可以让多个线程在不同的处理器上同时运行,从而大大提高程序执行速度。因此,多线程更能发挥硬件的潜力。
- 可以改善程序的结构。对于要处理多个命令的应用程序,可以将对每个命令的处理设计成一个线程,从而避免设计成大程序时造成的程序结构复杂。
二、创建线程
-
线程创建函数pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t *attr,
(void*)(*start_routine)(void*),void *arg);
- thread:该参数是一个指针,当线程创建成功时,用来返回创建的线程ID。
- attr:该参数用于指定线程的属性,NULL表示使用默认属性,稍后将介绍该数据结构。
- start_routine:该参数为一个函数指针,指向线程创建后要调用的函数。这个被线程调用的函数也称为线程函数。
- arg:该参数指向传递给线程函数的参数。
- 线程创建成功时,pthread_create函数返回0,若不为0则说明创建线程失败。线程创建成功后,新创建的线程开始运行第三个参数所指向的函数,原来的线程继续运行。
-
pthread.h中其他的系统调用
函 数 | 说 明 |
pthread_t pthread_self() | 获取本线程的线程ID |
int pthread_equal(pthread_t thread1,pthread_t thread2) | 判断两个线程ID是否指向同一线程 |
int pthread_once(pthread_once_t*once_control,void(*init_routine)()) |
用来保证init_routine线程函数在进程中仅执行一次 |
下面是创建线程的过程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int* thread(void*arg)
{
pthread_t newthid;
newthid = pthread_self();
printf("this is a new thread,thread ID = %lu\n",newthid);
return NULL;
}
int main()
{
pthread_t thid;
printf("main thread,ID is %lu\n",pthread_self());
if(pthread_create(&thid, NULL, (void*)thread, NULL) != 0){
printf("thread creation failed\n");
exit(1);
}
sleep(1);
exit(0);
}
编译运行的结果:
main thread,ID is 140200697169728
this is a new thread,thread ID = 140200688666368
程序首先打印主线程的ID,然后打印新创建的线程的ID。
某些情况下函数执行次数要被限制为一次,这时要用pthread_once函数。下面的例子创建两个线程,分别通过pthread_once调用同一个函数,结果只执行了一次:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_once_t once = PTHREAD_ONCE_INIT;
void run()
{
printf("Fuction run is running in thread %lu\n",pthread_self());
}
void * thread1(void*arg)
{
pthread_t thid = pthread_self();
printf("Current thread's ID is %lu\n",thid);
pthread_once(&once,run);
printf("thread1 ends\n");
}
void * thread2(void*arg)
{
pthread_t thid = pthread_self();
printf("Current thread's ID is %lu\n",thid);
pthread_once(&once,run);
printf("thread2 ends\n");
}
int main()
{
pthread_t thid1,thid2;
pthread_create(&thid1,NULL,thread1,NULL);
pthread_create(&thid2,NULL,thread2,NULL);
sleep(3);
printf("main thread exit! \n");
exit(0);
}
运行结果如下:
Current thread's ID is 140174946285312
Fuction run is running in thread 140174946285312
thread2 ends
Current thread's ID is 140174954678016
thread1 ends
main thread exit!
-
线程属性
typedef struct
{
int detachstate;
int schedpolicy;
struct sched param schedparam;
int inheritsched;
int scope;
size_t guardsize;
int stackaddr_set;
void * stackaddr;
size_t stacksize;表示堆栈的大小。
}pthread_attr_t;
- detachstate:表示新创建的线程是否与进程中其他的线程脱离同步。detachstate的缺省值为PTHREAD_CREATE_JOINABLE状态,这个属性也可以用函数pthread_detach()来设置。如果将detachstate设置为PTHREAD_CREATE_DETACH状态,则detachstate不能再恢复到PTHREAD_CREATE_JOINABLE状态。
- schedpolicy:表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对root有效。
- schedparam:一个struct sched_param结构,其中有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,缺省为0/
- inheritsched:有两种值可供选择,PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程显示指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
- scope:表示线程间竞争CPU的范围,也就是说,线程优先级的有效范围。POSIX的标准中定义了两个值,PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU,后者表示仅与同进程中的线程竞争CPU。
- guardsize:警戒堆栈的大小。
- stackaddr_set:堆栈地质集。
- stacksize:堆栈的大小。
三、线程终止
-
线程终止的两种方式
-
通过return从线程函数返回。
-
通过调用函数pthread_exit()使线程退出,该函数包含在pthread.h头文件中
#include <pthread.h>
void pthread_exit(void * retval);
- 有两种特殊情况要注意:
- 一种是,在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时进程中所有的线程也终止,因此在主线程中不能过早的从main函数返回;
- 另一种是如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束。
线程终止最重要的问题是资源释放问题以,特别是一些临界资源。临界资源在一段时间内只能被一个线程所拥有,当线程要使用临界资源时需提出请求,如果该资源未被使用则申请成功,否则等待。临界资源使用完毕后要释放以便其他线程可以使用。
-
为此Linux系统提供了两个函数自动释放资源
-
#include <pthread.h> #definr pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer; \ _pthread_cleanup_push(&buffer,(routine),(arg)); #define pthread_cleanup_pop \ _pthread_cleanup_pop(&buffer,(exeute));}
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。线程终止时另外一个要注意的问题是线程间的同步问题。一般情况下,进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其他线程,终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程拥有。正如进程之间可以使用wait()系统调用来等待其他进程结束一样,线程也有类似的函数:pthread_join()函数。
-
#include <pthread.h> void pthread_exit(void* retval); int pthread_join(pthread_t th,void* thread_return); int pthread_detach(pthread_t th);
该函数用来等待一个线程的结束。他的调用者将被挂起并等待th线程终止,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许一个线程使用该函数等待他的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。DETACHED状态是指对某个线程执行pthread_detach()后其所处的状态。处于DETACHED状态的线程无法由pthread_join同步。一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止时,要么已被设为DETACHED,要么使用pthread_join()来回收资源。
一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join()的线程返回错误代码ESRCH。
下面是一个线程终止的例子:
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void assisthread(void*arg)
{
printf("I am helping to do some jobs\n");
sleep(3);
pthread_exit(0);
}
int main()
{
pthread_t assistthid;
int status;
pthread_create(&assistthid,NULL,(void*)assisthread,NULL);
pthread_join(assistthid,(void*)&status);
printf("assistthread's exit is caused %d\n",status);
return 0;
}
运行结果为:
I am helping to do some jobs
assistthread's exit is caused 0
从结果上看pthread_join会阻塞主线程,等待线程assisthread结束。pthread_exit结束时的退出码为0,pthread_join得出的status也为0.