所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享内存空间,使得系统资源消耗减少。
pthread_t
对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程,而对于线程而言,也有一个“类似于进程的PID号”,名为tid,其本质是一个pthread_t类型的变量。线程号与进程号是表示线程和进程的唯一标识,但是对于线程号而言,其仅仅在其所属的进程上下文中才有意义。
获取线程号
#include
pthread_t pthread_self(void);
成功:返回线程号
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
实例
#include
#include
#include
#include
void *fun1(void *arg)
{
while(1){
printf("fun1:arg = %d Addr = %p\n",*(int *)arg,arg);
sleep(1);
}
}
void *fun2(void *arg)
{
while(1){
printf("fun2:arg = %d Addr = %p\n",(int)(long)arg,arg);
sleep(1);
}
}
int main()
{
pthread_t tid1,tid2;
int a = 50;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
if(ret != 0){
perror("pthread_create");
return -1;
}
//sleep(1);
ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
if(ret != 0){
perror("pthread_create");
return -1;
}
while(1){
a++;
sleep(1);
printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);
}
return 0;
}
对于第四个参数向线程传入数据,在线程处理回调函数中,直接将void*数据转化为int类型即可,本质上是在传递变量a的值。
上述两种方法均可得到所要的值,但是要注意其本质,一个为地址传递,一个为值的传递。当变量发生改变时候,传递地址后,该地址所对应的变量也会发生改变,但传入变量值的时候,即使地址指针所指的变量发生变化,但传入的为变量值,不会受到指针的指向的影响。
pthread_exit
#include
void pthread_exit(void *retval);
pthread_exit函数为线程退出函数,在退出时候可以传递一个 void* 类型的数据带给主线程,若选择不传出数据,可将参数填充为NULL。
pthread_cancel
#include
int pthread_cancel(pthread_t thread);
该函数传入一个tid号,会强制退出该tid所指向的线程,若成功执行会返回0。
pthread_join
#include
int pthread_join(pthread_t thread, void **retval);
该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的tid号,第二个参数为线程回收后接受线程传出的数据,可以指定NULL。
pthread_tryjoin_np
#define _GNU_SOURCE
#include
int pthread_tryjoin_np(pthread_t thread, void **retval);
该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回0,其余参数与pthread_join一致。
当多个线程在运行过程中,去操作公共资源,如全局变量的时候,可能会发生彼此“矛盾”现象。
例如线程1企图想让变量自增,而线程2企图想要变量自减,两个线程存在互相竞争的关系导致变量难以达到期望值。
为了解决上述对临界资源的竞争问题,pthread线程引出了互斥锁来解决临界资源访问。通过对临界资源加锁来保护资源只被单个线程操作,待操作结束后解锁,其余线程才可获得操作权。
当多个线程出现后,会遇到同时操作临界公共资源的问题,当线程操作公共资源时需要对线程进行保护加锁,防止其与线程在此线程更改变量时同时更改变量,待逻辑执行完毕后再次解锁,使其余线程再度开始竞争。
#include
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
该函数初始化一个互斥量,第一个参数是改互斥量指针,第二个参数为控制互斥量的属性,一般为NULL。当函数成功后会返回0,代表初始化互斥量成功。
宏模块初始化互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
lock函数与unlock函数分别为加锁解锁函数,只需要传入已经初始化好的pthread_mutex_t互斥量指针。成功后会返回0。
当某一个线程获得了执行权后,执行lock函数,一旦加锁成功后,其余线程遇到lock函数时候会发生阻塞,直至获取资源的线程执行unlock函数后。unlock函数会唤醒其他正在等待互斥量的线程。
特别注意的是,当获取lock之后,必须在逻辑处理结束后执行unlock,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。
在使用互斥量的时候,尤其要注意使用pthread_cancel函数,防止发生死锁现象!
#include
int pthread_mutex_trylock(pthread_mutex_t *mutex);
该函数同样也是一个线程加锁函数,但该函数是非阻塞模式通过返回值来判断是否加锁成功,用法与上述阻塞加锁函数一致。
#include
int pthread_mutex_destory(pthread_mutex_t *mutex);
该函数是用于销毁互斥量的,传入互斥量的指针,就可以完成互斥量的销毁,成功返回0。
示例
#include
#include
#include
#include
#include
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *fun1(void *arg);
void *fun2(void *arg);
int num = 0;
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
if(pthread_create(&tid1 ,NULL ,fun1 ,NULL) != 0){
perror("func1 create: ");
exit(-1);
}
if(pthread_create(&tid2, NULL, fun2, NULL) != 0){
perror("func2 create: ");
exit(-1);
}
pthread_join(tid1 ,NULL);
pthread_join(tid2 ,NULL);
pthread_mutex_destroy(&lock);
return 0;
}
void *fun1(void *arg)
{
int t = 5;
pthread_mutex_lock(&lock);
while(t--){
printf("%s : num = %d\n",__FUNCTION__,num++);
}
pthread_mutex_unlock(&lock);
pthread_exit(0);
}
void *fun2(void *arg)
{
int t = 4;
pthread_mutex_lock(&lock);
while(t--){
printf("%s : num = %d\n",__FUNCTION__,num--);
}
pthread_mutex_unlock(&lock);
pthread_exit(0);
}
当多个线程出现后,同时会遇到无序执行的问题。有时候需要对线程的执行顺序做出限定,变引入了信号量,通过PV操作来控制线程的执行顺序。
信号量起通知作用,线程A在等待某件事,线程B完成了这件事后就可以给线程A发信号。
#include
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem_t *sem传入sem_t类型指针;
int pshared传入0代表线程控制,否则为进程控制;
unsigned int value表示信号量的初始值,0代表阻塞,1代表运行。
待初始化结束信号量后,若执行成功会返回0。
#include
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_wait函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行“sem-1”的操作。所谓的“sem-1”是与上述初始化函数中第三个参数值一致,成功执行会返回0。
sem_post函数会释放指定信号量的资源,执行“sem+1”操作。
通过以上2个函数可以完成所谓的PV操作,即信号量的申请与释放,完成对线程执行顺序的控制。
#include
int sem_trywait(sem_t *sem);
此函数是信号量申请资源的非阻塞函数,功能与sem_wait一致,唯一区别在于此函数为非阻塞。
#include
int sem_destory(sem_t *sem);
该函数为信号量销毁函数,执行过后可将信号量进行销毁。
示例
#include
#include
#include
#include
sem_t sem1,sem2,sem3;
void *func1(void *arg);
void *func2(void *arg);
void *func3(void *arg);
int main()
{
pthread_t tid1,tid2,tid3;
if(sem_init(&sem1 , 0 , 1) != 0){
//初始化时,直接给sem1 信号资源
perror("fail sem1 init :");
return -1;
}
if(sem_init(&sem2 , 0 , 0) != 0){
perror("fail sem1 init :");
return -1;
}
if(sem_init(&sem3 , 0 , 0) != 0){
perror("fail sem1 init :");
return -1;
}
if(pthread_create(&tid1 , NULL , func1 , NULL) != 0){
perror("fail func1 create :");
return -1;
}
if(pthread_create(&tid2 , NULL , func2 , NULL) != 0){
perror("fail func2 create :");
return -1;
}
if(pthread_create(&tid3 , NULL , func3 , NULL) != 0){
perror("fail func2 create :");
return -1;
}
pthread_join(tid1 , NULL);
pthread_join(tid2 , NULL);
pthread_join(tid3 , NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}
void *func1(void *arg)
{
sem_wait(&sem1);
printf("%s : Hello Come!\n",__FUNCTION__);
sem_post(&sem2);
pthread_exit(NULL);
}
void *func2(void *arg)
{
sem_wait(&sem2);
printf("%s : Hello Come!\n",__FUNCTION__);
sem_post(&sem3);
pthread_exit(NULL);
}
void *func3(void *arg)
{
sem_wait(&sem3);
printf("%s : Hello Come!\n",__FUNCTION__);
pthread_exit(NULL);
}