一、线程概述
前面讲到,进程是系统中程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等操作时需要有比较复杂的上下文切换等动作。为了进一步减少处理机的空转时间,支持多处理器以及减少上下文切换开销,进程在演化过程中出现了另一个概念——线程。它是进程内独立的一条运行路线、处理器调度的最小单元,也可以称之为轻量级进程。线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。因此线程的上下文切换开销比创建进程小很多。
一个进程可以有多个线程,也就是多个线程共享一个进程的资源和地址空间。因此,任何线程对系统资源的操作都会给其他线程带来影响。所以,在多线程编程中同步是非常重要的。
二、线程编程
1.线程基本编程
(1)函数说明
(1) pthread_create():创建线程 |
(2) pthread_exit():线程退出 |
(3) pthread_join():将当前线程挂起并等待线程结束,线程结束时资源被收回 |
(4) pthread_cancel():用于同一进程内一个线程要终止另一个线程,被终止的线程要调用以下两个函数 |
(5) pthread_setcancel():设置线程是否可以被其他线程调用pthread_cancel函数取消/终止。 |
(6) pthread_setcanceltype():设置当前线程的“可取消类型”,并且将先前的类型返回到oldtype引用中。 |
(2)函数格式
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | int pthread_create((pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)),void *arg) |
函数传入值 | thread:线程标识符 |
attr:线程属性设置,通常取为NULL | |
start_routine:线程函数的起始地址,是一个以指向void的指针作为参数和返回值的函数指针 | |
arg:传递给start_routine的参数 | |
函数返回值 | 成功:0 |
出错:返回错误码 |
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | void pthread_exit(void *retval) |
函数传入值 | retval:线程结束时的返回值,可由其他函数如pthread_join()来获取 |
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | int pthread_join((pthread_t th,void **thread_return)) |
函数传入值 | th:等待线程的标识符 |
thread_return:用户定义的指针,用来存储被等待线程结束时的返回值(不为NULL时) | |
函数返回值 | 成功:0 |
出错:返回出错码 |
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | int pthread_cancel(pthread_t th) |
函数传入值 | th:要取消的线程的标识符 |
函数返回值 | 成功:0 |
出错:返回错误码 |
(3)使用实例
/* pthread.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define PTHREAD_NUM 3
#define TASK_NUM 5
#define DELAY_MAX 10.0
void *pthread_func(void *arg)
{
int i,pth_num = (int)arg;
int delay_time=0;
printf("pthread %d is strating...\n",pth_num);
for(i=0;i<TASK_NUM;i++)
{
delay_time = (int)(rand() * DELAY_MAX / (RAND_MAX)) + 1;
sleep(delay_time);
printf(" PTHREAD %d:JOB %d:DELAY %d\n",pth_num,i,delay_time);
}
printf("PTHREAD %d finished.\n",pth_num);
pthread_exit(NULL);
}
void main(void)
{
pthread_t pthread_no[PTHREAD_NUM];
int no=0,ret;
void *retval;
srand(time(NULL));
for(no=0;no<PTHREAD_NUM;no++)
{
ret = pthread_create(&pthread_no[no],NULL,pthread_func,(void*)no);
if(ret != 0)
{
printf("pthread_create %d failed.\n",no);
exit(1);
}
}
printf("pthread_create succeed,waiting for tasks...\n");
for(no=0;no<PTHREAD_NUM;no++)
{
ret = pthread_join(pthread_no[no],&retval);
if(!ret)
{
printf("pthread %d joined.\n",no);
}
else
{
printf("pthread %d join failed\n",no);
}
}
exit(0);
}
2.互斥锁线程控制
(1)函数说明
互斥锁是一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,上锁和解锁,可以把互斥锁看作某种意义上的全局变量。在同一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他资源希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。互斥锁可以保证让每个线程对共享资源按顺序进行原子操作。
(1) pthread_mutex_init():互斥锁初始化 |
(2) pthread_mutex_lock():互斥锁上锁 |
(3) pthread_mutex_trylock():互斥锁判断上锁 |
(4) pthread_mutex_unlock():互斥锁解锁 |
(5) pthread_mutex_destroy():取消互斥锁 |
互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁:
(1) 快速互斥锁:指调用线程会阻塞直至拥有互斥锁的线程解锁为止,默认属性为快速互斥锁 |
(2) 递归互斥锁:能够成功地返回,并且增加调用线程在互斥上加锁次数 |
(3) 检错互斥锁:快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息 |
(2)函数格式
所需头文件 | #include <pthread.h> | |
---|---|---|
函数原型 | int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexaddr_t *mutexaddr) | |
函数传入值 | mutex:互斥锁 | |
mutexaddr | PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁 | |
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁 | ||
PTHREAD_ERRORCHECK_MUTEX_INITIALITER_NP:创建检错互斥锁 | ||
函数返回值 | 成功:0 | |
出错:返回错误码 |
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | int pthread_mutex_lock(pthread_mutex_t *mutex,) int pthread_mutex_trylock(pthread_mutex_t *mutex,) int pthread_mutex_unlock(pthread_mutex_t *mutex,) int pthread_mutex_destroy(pthread_mutex_t *mutex,) |
函数传入值 | mutex:互斥锁 |
函数返回值 | 成功:0 |
出错:-1 |
(3)使用实例
/* pthread_mutex_lock.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define PTHREAD_NUM 3
#define TASK_NUM 5
#define DELAY_MAX 10.0
pthread_mutex_t mutex; //全局变量
void *pthread_func(void *arg)
{
int i,pth_num = (int)arg;
int delay_time=0;
int ret;
ret = pthread_mutex_lock(&mutex); //上锁
if(ret != 0)
{
printf("PTHREAD %d:pthread_mutex_lock failed.\n",pth_num);
pthread_exit(NULL); //上锁失败,线程退出
}
printf("pthread %d is strating...\n",pth_num);
for(i=0;i<TASK_NUM;i++)
{
delay_time = (int)(rand() * DELAY_MAX / (RAND_MAX)) + 1;
sleep(delay_time);
printf(" PTHREAD %d:JOB %d:DELAY %d\n",pth_num,i,delay_time);
}
printf("PTHREAD %d finished.\n",pth_num);
ret = pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
void main(void)
{
pthread_t pthread_no[PTHREAD_NUM];
int no=0,ret;
void *retval;
srand(time(NULL));
ret = pthread_mutex_init(&mutex,NULL); //初始化互斥锁
if(ret != 0)
{
printf("pthread_mutex_init failed.\n");
exit(1);
}
for(no=0;no<PTHREAD_NUM;no++)
{
ret = pthread_create(&pthread_no[no],NULL,pthread_func,(void*)no);
if(ret != 0)
{
printf("pthread_create %d failed.\n",no);
exit(1);
}
}
printf("pthread_create succeed,waiting for tasks...\n");
for(no=0;no<PTHREAD_NUM;no++)
{
ret = pthread_join(pthread_no[no],&retval);
if(!ret)
{
printf("pthread %d joined.\n",no);
}
else
{
printf("pthread %d join failed\n",no);
}
}
pthread_mutex_destroy(&mutex); //消除互斥锁
exit(0);
}
3.信号量线程控制
(1)信号量说明
信号量的工作原理:PV原子操作是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于等于零时,该进程(或线程)就将阻塞到信号量sem的值大于等于0为止。
信号量主要用于进程(或线程)的同步和互斥两种典型情况。当信号量用于互斥时,几个进程(或线程)往往只设置一个信号量sem;当信号量用于同步时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行。
(2)函数说明
(1) sem_init():用于创建一个信号量,并初始化它的值。 |
(2) sem_wait()和sem_trywait():相当于P操作,在信号量大于0时它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait()将会阻塞进程,而sem_trywait()则会立即返回。 |
(3) sem_post():相当于V操作,它将信号量值加一同时发出信号来唤醒等待的进程。 |
(4) sem_getvalue():用于得到信号量的值。 |
(5) sem_destory():用于删除信号量 |
(3)函数格式
所需头文件 | #include <semaphore.h> |
---|---|
函数原型 | int sem_init(sem_t *sem,int pshared,unsigned int value) |
函数传入值 | sem:信号量指针 |
pshared:决定信号量能否在几个进程间共享。由于目前Linux还没有实现进程间共享信号量,所以这个值只能够取0,就表示这个信号量是当前进程的局部信号量。 | |
value:信号量初始值 | |
函数返回值 | 成功:0 |
出错:-1 |
所需头文件 | #include <pthread.h> |
---|---|
函数原型 | int sem_wait(sem_t *sem) int sem_trywait(sem_t *sem) int sem_post(sem_t *sem) int sem_getvalue(sem_t *sem) int sem_destroy(sem_t *sem) |
函数传入值 | sem:信号量指针 |
函数返回值 | 成功:0 |
出错:-1 |
(4)使用实例
使用三个信号量实现三个进程之间的同步执行:
/* thread_sem.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define PTHREAD_NUM 3
#define TASK_NUM 3
#define DELAY_MAX 10
sem_t sem[PTHREAD_NUM];
void *pthread_func(void *arg)
{
int i,pth_num = (int)arg;
int delay_time=0;
int ret;
printf("pthread %d is strating...\n",pth_num);
ret = sem_wait(&sem[pth_num]);//P
if(ret != 0)
{
printf("pthread %d:sem_wait failed.\n",pth_num);
pthread_exit(NULL);
}
for(i=0;i<TASK_NUM;i++)
{
delay_time = (int)(rand() * DELAY_MAX / (RAND_MAX)) + 1;
sleep(delay_time);
printf(" PTHREAD %d:JOB %d:DELAY %d\n",pth_num,i,delay_time);
}
printf("PTHREAD %d finished.\n",pth_num);
pthread_exit(NULL);
}
void main(void)
{
pthread_t pthread_no[PTHREAD_NUM];
int no=0,ret;
void *retval;
srand(time(NULL));
for(no=0;no<PTHREAD_NUM;no++)
{
ret = sem_init(&sem[no],0,0);
if(ret!=0)
{
printf("pthread %d:sem_init failed.\n",no);
exit(1);
}
ret = pthread_create(&pthread_no[no],NULL,pthread_func,(void*)no);
if(ret != 0)
{
printf("pthread_create %d failed.\n",no);
exit(1);
}
}
printf("pthread_create succeed,waiting for tasks...\n");
sem_post(&sem[0]);//V
for(no=0;no<PTHREAD_NUM;no++)
{
ret = pthread_join(pthread_no[no],&retval);
if(!ret)
{
printf("pthread %d joined.\n",no);
}
else
{
printf("pthread %d join failed\n",no);
}
//sem_post(&sem[(no+PTHREAD_NUM-1)%PTHREAD_NUM]);//V
sem_post(&sem[(no+1+PTHREAD_NUM) % PTHREAD_NUM]);
}
for(no=0;no<PTHREAD_NUM;no++)
{
sem_destroy(&sem[no]);
}
exit(0);
}
三、进程信号量编程和线程信号量编程的区别
信号量 | 进程信号量编程 | 线程信号量编程 |
---|---|---|
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
#include <semaphore.h> |
创建信号量 | semget()获取信号量并使用semctl(IPC_SETVAL)来设置信号量的初始值 | sem_init()创建一个信号量,并初始化它的值 |
P操作 | smeop()和struct sembuf结构体实现P操作 | sem_wait()或sem_trywait()实现P操作 |
V操作 | smeop()和struct sembuf结构体实现V操作 | sem_post()实现V操作 |
获取信号量值 | semctl(IPC_GETVAL)获取信号量值 | sem_getvalue()获取信号量值 |
删除信号量 | semctl(IPC_RMID)删除信号量 | sem_destory()删除信号量 |