目录
本文会讲解UNIX下的线程,线程是开发中会经常用到的并行技术,比如你打开QQ,同时开好几个聊天窗口,这就是用线程实现的。
1 并行的概念
执行代码必须需要内存和CPU。内存可分,CPU不可以分。真正意义的代码的并行是不存在的。视觉、听觉等都是需要时间段的。CPU使用CPU时间片机制实现伪并行,把CPU的执行时间分成极小的小片,比如1ms,每个CPU时间片可以拥有CPU的1ms执行时间,执行完毕后让给其他线程执行。多线程可以提升代码的效率,因此应用较广。
2 线程的定义
线程是一种轻量级的代码并行技术,是程序执行流的最小单元,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
线程隶属于某个进程,进程内部可以使用多线程,线程的内部也可以使用多线程。线程共享进程的资源,不需要太多的额外资源,每个线程只需要额外建一个栈区即可。同一个进程内部的多进程之间互相独立,又互相影响。
每个进程至少有一个线程,即主线程(main()函数),主线程退出时,所有线程跟着结束。
3 UNIX关于线程的设计与实现
Linux和Unix都在POSIX规范有了定义,主要是一个头文件+一个动态库。头文件是pthread.h,共享库是libpthread.so。所有线程相关的函数/结构/类型基本都是以pthread_开头,编译链接时,需要加-pthread/-lpthread。所有的线程的函数,都不使用errno,错误码直接返回。函数strerror()可以把错误码转换成错误信息。
4 线程相关函数
1、pthread_create()
这个函数产生一个线程,线程随即开始执行。
int pthread_create(pthread_t *id , pthread_attr_t *attr , void*(*fa)(void*) , void* p );
参数解释
id是一个传出参数,用于传出线程的ID,线程ID是线程的唯一标识,它是一个非常大的整数,unsigned long int。
attr是线程的属性,默认给0即可。
fa是一个函数指针,用来传入线程执行的函数,该函数原型为:
void* fd (void *);
p就是参数fd的传入参数,以此来给线程传入参数。
返回:成功返回0,失败需要用strerror()转换错误信息。
2、pthread_self()
这个函数可以获取当前线程的线程id。
#include <pthread.h>
pthread_t pthread_self(void);
参数解释
返回:返回当前线程的线程id。
3、pthread_join()
该函数的作用是让一个线程以阻塞的方式等待另外一个线程的结束,同时可以获取线程的返回值。
#include<pthread.h>
int pthread_join(pthread_t thread , void **retval);
参数解释
thread是被等待的线程的id,即哪个线程的唯一标识
retval是一个传出参数,用来传出被等待线程的返回值,如果不需要返回值,给0或NULL即可。
返回:成功返回0,失败则返回错误号,用strerror()查看。
5 线程的退出
和进程一样,线程也有自己的退出方式
1、线程执行了return语句。
2、线程使用pthread_exit(void*)退出线程,并把返回值做参数。
3、线程可能被其他线程取消(非正常退出),使用pthread_cancel()可以取消一个线程,前提是这个线程允许你去取消(了解就可以了,用的不多)。
注:exit()和pthread_exit()的区别是,后者是退出线程的,只会结束当前线程,而exit()是退出进行,会结束所有的线程。
6、线程资源的释放和回收
1、普通线程的资源回收,不是十分的确定,没有一个强制的机制。
2、pthread_join()进来的进程,资源会在join函数结束后回收。
3、处于分离状态的线程在一结束的同时就回收资源,不考虑其他线程是否有关系。函数pthread_detach()可以设置线程为分离状态。
#include<pthread.h>
int pthread_detach(pthread_t id);
参数解释
id是你要设为分离模式的线程的id
返回:成功返回0,失败返回错误号,用函数strerror()查看错误信息。
7 线程同步
多线程之间共享进程的资源,因此有可能出现数据的冲突,在访问共享数据,多线程之间需要协调访问,这种技术叫线程同步。线程同步技术的核心思想就是在访问共享资源时,并行访问改为串行访问。
Unix系统的线程同步技术主要包括:互斥量、信号量、条件变量,本文只涉及前两种。
7.1 互斥量
互斥量有固有的用法和步骤:
1、声明一个互斥量
pthread_mutex_t lock;
2、初始化互斥量(宏定义初始化、函数初始化)
可以在声明的同时使用宏初始化,或者在声明以后使用函数来初始化。
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
或:
pthread_mutex_init(&lock,0);
3、给访问的共享资源上锁
pthread_mutex_lock(&lock);
4、访问共享资源
5、访问完毕解锁
pthread_mutex_unlock(&lock);
6、如果不再使用互斥量,可以回收资源(即销毁互斥锁)
pthread_mutex_destroy(&lock);
注意:互斥量使用不当将会造成死锁,导致程序崩溃。
7.2 信号量
信号量是一个计数器,用于控制访问共享资源的并行线程总数,在POSIX规范中,信号量也可以用于进程的控制,但Linux并没有实现。信号量的原理和信号量集中信号量的原理是一样的,但就代码实现来说,它们没有任何关系。
信号量定义在semaphore.h中,信号量的使用步骤和互斥量基本类似,但是信号量不是定义在pthread中,信号量使用步骤:
1、声明信号量
sem_t sem;
2、初始化信号量
sem_init(&sem,0,10);
3、信号量计数减一
sem_wait(&sem);
4、访问共享资源
5、访问完毕,信号量计数加一
sem_post(&sem);
6、回收资源
sem_destory(&sem);
函数解释
int sem_init(sem_t* sem,int which,int value);
参数解释
sem是要初始化的信号量的指针
which 代表控制进行还是控制线程(linux没有实现控制进程),0代表控制线程,非0代表控制进程。
value就是信号量的最大值。
8 信号量控制线程的代码
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#include<time.h>
sem_t sem;
void* task(void*p){
int i=(int)p;
printf("第%d个线程申请连接数据库\n",i);
sem_wait(&sem);
printf("第%d个线程申请成功\n",i);
srand(time(0));
sleep(rand()%10);
sem_post(&sem);
printf("第%d个线程释放连接\n",i);
}
int main(){
sem_init(&sem,0,10);
pthread_t id;
int i;
for(i=0;i<20;i++){
pthread_create(&id,0,task,(void*)(i+1));
pthread_detach(id);
}
sleep(19);
sem_destory(&sem);
return 0;
}