概念
- 在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。
- 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;
- 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
优点
- 启动一个线程所花费的空间远远小于启动一个进程所花费的空间,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间
- 同一进程下的线程之间共享数据空间,线程间方便的通信机制
- 提高应用程序响应。
- 操作系统会保证当线程 数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
LinuxThreads实现机制
LinuxThreads是目前Linux平台上使用最为广泛的线程库。LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建”线程”,表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。它所实现的就是基于核心轻量级进程的”一对一”线程模型,在核外实现的线程又可以分为”一对一”、”多对一”两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,而后者则完全在核外实现多线程,调度也在用户态完成。线程之间的管理在核外函数库中实现。”一对一”模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。
编译需要注意:
在编译C的多线程时候,Linux系统下的多线程遵循POSIX线程接口,称为pthread。一方面必须指定Linux C语言线程库多线程库pthread,连接时需要使用库libpthread.a,才可以正确编译(例如:gcc test.c -o test -lpthread);另一方面要包含有关线程头文件#include <pthread.h>
。
Linux下使用eclipse时,Makefiel文件可以由eclipse自动生成,可以通过修改它的工程配置来改变Makefile的参数
如:在使用线程操作时,需要添加-lpthread才能编译通过,在Link flags处添加编译选项:-lpthread。
多线程基本操作
1、线程的创建与等待
-
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
作用:用来创建一个线程
thread为指向线程标识符的指针,attr用来设置线程属性,start_routine是线程运行函数的起始地址,arg是运行函数的参数。
当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。
void function(void * t); //函数的传入参数为 void* 类型
pthread_t id; // 定义一个线程id
retv = pthread_create(&id, NULL, (void*)&function_name, (void*)t); //线程属性为默认值(后面分析),运行函数起始地址类型必须是 void* ,函数function传入参数 t 的类型也必须是 void *,传入多个数据要用结构体
if(retv != 0)
{ 线程创建失败! }
-
int pthread_join(pthread_t thread, void ** thread_return);
作用:用来等待一个线程的结束
thread为被等待的线程标识符,thread_return为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
/*有返回值*/
void * ret;
pthread_join(id,&ret);
/*无返回值*/
pthread_join(id,NULL);
-
void pthread_exit(void * retval);
函数结束了,调用它的线程也就结束了。唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。
void * function (void)
{
.....
pthread_exit((void*)retval);
}
pthread.c:
# include<stdio.h>
# include<unistd.h> // 包含sleep();
# include<stdlib.h>
# include<pthread.h>
# include<sched.h> //包含调度函数
void pthread1(void);
void* pthread2(void);
void pthread_3(void*);
int main(void)
{
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_attr_t id1_attr; //定义属性变量
int retv = 0;
long int t = 55;
void * ret;
pthread_attr_init(&id1_attr);
pthread_attr_setscope(&id1_attr, PTHREAD_SCOPE_SYSTEM);
retv = pthread_create(&id1,&id1_attr,(void*)&pthread1,NULL); //pthread_create创建线程成功返回0,失败返回-1。
if(0 != retv)
{
printf("Create pthread error!\n");
return -1;
}
pthread_create(&id2,NULL,(void*)&pthread2,NULL);
pthread_join(id1,NULL);
pthread_join(id2,&ret); //等待线程id2结束,用ret接收返回值
pthread_create(&id3,NULL, (void*)&pthread_3,(void *)t); //创建线程id3,向线程函数传参数t
pthread_join(id3,NULL);
printf("Hello,i am the main process!\n");
printf("return the id2 is: %lu\n", (long int)ret);
return 0;
}
void pthread1(void)
{
printf("I am the first thread!\n");
return;
}
void* pthread2(void)
{
long int i = NULL;
while(i<10)
{
i++;
}
return (void*)i;
//pthread_exit((void*)i); //线程退出结束
}
void pthread_3(void * val)
{
printf("t的值为:%lu\n", (long int)val); //void*类型不能直接打印,8字节
return;
}
2、线程的属性
-
pthread_attr_init(&attr);
作用:初始化线程属性
-
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
作用:设置线程绑定状态
attr是指向属性结构的指针,第二个参数是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
示例:
pthread_attr_t attr;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
-
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
作用:修改线程的分离状态属性
-
int pthread_attr_getdetazchstate(const pthread_attr_t * attr,int *detachstate);
作用:获取线程的分离状态属性
第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程) -
int pthread_attr_setschedparam(pthread_attr_t *attr,struct sched_param *param);
作用:设置线程的调度参数
-
int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);
作用:得到线程的调度参数
线程的优先级,它存放在结构sched_param中,结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。(系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到)
注:如果不是编写实时程序,不建议修改线程的优先级。因为,调度策略是一件非常复杂的事情,如果不正确使用会导致程序错误,从而导致死锁等问题。如:在多线程应用程序中为线程设置不同的优先级别,有可能因为共享资源而导致优先级倒置。 -
int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);
作用:得到线程的调度策略
-
int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);
作用:设置线程的调度策略
policy为调度策略或指向调度策略的指针。调度策略可能的值是先进先出(SCHED_FIFO)、轮转法(SCHED_RR),或其它(SCHED_OTHER)。
SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。
SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR
策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时,运行的线程将被抢占以便准备好的线程可以执行。
当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等待或等待加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级 的SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥量,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。 -
int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);
作用:获得线程的继承性
-
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
作用:设置线程的继承性
继承性决定调度的参数是从创建的进程中继承还是使用在 schedpolicy和schedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程 的调度策略和参数,必须先设置该属性。继承性的可能值是PTHREAD_INHERIT_SCHED,表示新现成将继承创建线程的调度策略和参数;或者PTHREAD_EXPLICIT_SCHED(缺省),表示使用在schedpolicy和schedparam属性中显式设置的调度策略和参数。
pthread2.c:
# include<stdio.h>
# include<pthread.h>
void function(void * data)
{
int i;
for(i=0; i<=2; i++)
if(i == 2)
printf("I'm thread th%ld!\n", (long int)data);
}
int main(void)
{
pthread_t th1;
pthread_t th2;
/*线程属性 */
pthread_attr_t attr_th1;
pthread_attr_t attr_th2;
long int a=1, b=2;
printf("I'm the main prosser!\n");
pthread_attr_setscope(&attr_th1, PTHREAD_SCOPE_PROCESS); //非绑定
pthread_attr_setscope(&attr_th2, PTHREAD_SCOPE_SYSTEM); //绑定
/*线程属性初始化*/
pthread_attr_init(&attr_th1);
pthread_attr_init(&attr_th2);
/*创建线程*/
pthread_create(&th1, &attr_th1, (void*)&function, (void *)a);
pthread_create(&th2, &attr_th2, (void*)&function, (void *)b);
/*等待线程结束*/
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
3、互斥锁
-
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
作用:用来生成一个互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //生成静态锁
pthread_mutex_init(); 一般用来生成动态锁 -
pthread_mutexattr_init(pthread_mutexattr_t * mattr);
作用:声明特定属性的互斥锁,须调用函数
-
pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);
作用:设置属性pshared
-
pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared);
作用:获取锁的范围
它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。默认是后者,表示进程内使用锁 -
pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
作用:设置锁的类型
-
pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type);
作用:获取锁的类型
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 -
pthread_mutex_lock();
作用:声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止
-
pthread_mutex_trylock();
作用:它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。
-
int pthread_mutex_unlock(pthread_mutex_t *mutex);
作用:解锁
总结:
1) 只能用于”锁”住临界代码区域
2) 一个线程加的锁必须由该线程解锁.
锁几乎是我们学习同步时最开始接触到的一个策略,也是最简单, 最直白的策略.
4、条件变量
-
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
作用:初始化一个条件变量
结构pthread_condattr_t
是条件变量的属性结构,用它来设置条件变量是进程内可用还是进程间可用, 默认值是PTHREAD_ PROCESS_PRIVATE
,即此条件变量被同一进程内的各个线程使用;PTHREAD_PROCESS_SHARED
则为多个进程间各线程公用。 -
int pthread_cond_destroy(pthread_cond_t *cond);
作用:注销条件变量
-
int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
作用:条件等待 ,mutex用于保护条件
注:受signal激活后,wait先解锁mutex,pthread_cond_wait执行最后一个操作:重新锁定mutex;苏醒之前不会消耗CPU周期 -
int pthread_cond_signal(pthread_cond_t * cond);
作用:条件激活
注:在条件满足时,给正在等待的线程发送信息,唤醒该线程,wait的线程继续执行。
pthread3.c:
#include<stdio.h>
# include<pthread.h>
#include<unistd.h>
# include<malloc.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node {
int n_number;
struct node *n_next;
} *head = NULL;
static void *thread_func(void)
{
struct node *p = NULL;
while (1)
{
pthread_mutex_lock(&mtx);
while (head == NULL)
{
pthread_cond_wait(&cond, &mtx);
}
p = head;
head = head->n_next;
printf("Got %d from front of queue\n", p->n_number);
free(p);
p = NULL;
pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
}
return 0;
}
int main(void)
{
pthread_t tid;
int i;
struct node *p;
pthread_create(&tid, NULL, (void*)&thread_func, NULL);
for (i = 0; i < 10; i++)
{
p = (struct node *)malloc(sizeof(struct node));
p->n_number = i;
pthread_mutex_lock(&mtx);
p->n_next = head;
head = p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx); //解锁
sleep(1);
}
printf("head'data is %d\n", head->n_number);
printf("thread 1 wanna end the line.So cancel thread 2.\n");
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("p'data is %d.\n", p->n_number);
printf("All done -- exiting\n");
return 0;
}
5、信号量
信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中。同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁。但是,信号的效率不如互斥锁高。缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的。使用头文件#include <semaphore.h>
-
int sem_init (sem_t * sem, int pshared, unsigned int value);
作用:对由sem指定的信号量进行初始化。
pshared为0时,表示这个信号量只是当前进程中的信号量(该进程内的 所有线程使用),不为0,这个信号量可能可以在两个进程中共享;value给出了信号量的初始值
注:
(1)多个线程决不能初始化同一个信号。
(2)不得对其他线程正在使用的信号重新初始化。 -
int sem_wait(sem_t *sem);
作用:用于接受信号
当sem>0时就能接受到信号,然后将sem– ;sem为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。 -
int sem_post(sem_t *sem);
作用:给信号量的值加1 。注:如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。
-
int sem_destroy(sem_t *sem);
作用:销毁与sem所指示的未命名信号相关联的任何状态。释放内存空间
pthread4.c:
#include<stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <semaphore.h>
#define BUFSIZE 4
#define NUMBER 8
int sum_of_number=0;
/* 可读 和 可写资源数*/
sem_t write_res_number;
sem_t read_res_number;
/* 循环队列 */
struct recycle_buffer
{
int buffer[BUFSIZE];
int head,tail;
}re_buf;
/* 用于实现临界区的互斥锁,我们对其初始化*/
pthread_mutex_t buffer_mutex=PTHREAD_MUTEX_INITIALIZER;
static void producer(void)
{
//int * num_of_w = NULL;
int i;
for(i=0;i<=NUMBER;i++)
{
/* 减少可写的资源数 */
sem_wait(&write_res_number);
/* 进入互斥区 */
pthread_mutex_lock(&buffer_mutex);
/*将数据复制到缓冲区的尾部*/
re_buf.buffer[re_buf.tail]=i;
re_buf.tail=(re_buf.tail+1)%BUFSIZE;
printf("procuder %lu write %d.\n", pthread_self(),i); //pthread_self();获得线程自身的ID。pthread_t的类型为unsigned long int
/*离开互斥区*/
pthread_mutex_unlock(&buffer_mutex);
/*增加可读资源数*/
sem_post(&read_res_number);
//sem_getvalue(&write_res_number, num_of_w);
//printf("%d\n", *num_of_w);
}
/* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/
return;
}
static void consumer(void)
{
int i,num;
for(i=0;i<=NUMBER;i++)
{
/* 减少可读资源数 */
sem_wait(&read_res_number);
/* 进入互斥区*/
pthread_mutex_lock(&buffer_mutex);
/* 从缓冲区的头部获取数据*/
num = re_buf.buffer[re_buf.head];
re_buf.head = (re_buf.head+1)%BUFSIZE;
printf("consumer %lu read %d.\n", pthread_self(),num);
/* 离开互斥区*/
pthread_mutex_unlock(&buffer_mutex);
sum_of_number+=num;
/* 增加可写资源数*/
sem_post(&write_res_number);
}
/* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/
return;
}
int main(void)
{
/* 用于保存线程的线程号 */
pthread_t p_tid;
pthread_t c_tid;
int i;
re_buf.head=0;
re_buf.tail=0;
for(i=0;i<BUFSIZE;i++)
re_buf.buffer[i] =0;
/* 初始化可写资源数为循环队列的单元数 */
sem_init(&write_res_number,0,BUFSIZE); // 这里限定了可写的bufsize,当写线程写满buf时,会阻塞,等待读线程读取
/* 初始化可读资源数为0 */
sem_init(&read_res_number,0,0);
/* 创建两个线程,线程函数分别是 producer 和 consumer */
/* 这两个线程将使用系统的缺省的线程设置,如线程的堆栈大小、线程调度策略和相应的优先级等等*/
pthread_create(&p_tid,NULL,(void*)&producer,NULL);
pthread_create(&c_tid,NULL,(void*)&consumer,NULL);
/*等待两个线程完成退出*/
pthread_join(p_tid,NULL);
pthread_join(c_tid,NULL);
printf("the head is : %d\n", re_buf.head);
printf("the tail is : %d\n", re_buf.tail);
printf("The sum of number is %d\n",sum_of_number);
return 0;
}
下面是一个死锁的例子:
# include<stdio.h>
# include<pthread.h>
# include<unistd.h>
static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *function1(void *val)
{
int i = 1;
while(i<3)
{
pthread_cond_wait(&cond, &mutex1);
pthread_mutex_lock(&mutex1);
i++;
printf("%lu\n", (long int)val);
pthread_mutex_unlock(&mutex1);
}
pthread_exit(NULL);
}
void *function2(void *val)
{
int i = 0;
while(i<3)
{
pthread_mutex_lock(&mutex1);
printf("%lu\n", (long int)val);
pthread_cond_signal(&cond);
i++;
pthread_mutex_unlock(&mutex1);
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
pthread_t id1;
pthread_t id2;
long int t1 = 55;
long int t2 = 66;
pthread_create(&id1, NULL, function1, (void*)t1);
pthread_create(&id2, NULL, function2, (void*)t2);
pthread_join(id1, NULL);
return 0;
}