基本概念
- 所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。
- 我们可以通过在程序中创建新线程,来令一个进程同时处理着多个线程。
- 调度以线程为单位,资源分配以进程为单位
- 在使用多线程时,所有线程共用当前进程资源,且都可以访问到进程的全局变量
- 编译时要用
gcc -o abc abc.c -lpthread
即需要多加一个-lpthread
void*
代表了一种无类型的抽象指针,可以表示任意类型的指针,但是解引用之前要确认类型,如*(int*)tmp
线程的标识类型pthread_t
对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程
对于线程而言,每一个进程都有一个唯一对应的tid号来表示该进程,本质是一个pthread_t类型的变量
可以通过pthread_self()
函数获取主线程的线程号,示例程序如下
#include <pthread.h>
#include <stdio.h>
int main()
{
pthread_t tid = pthread_self();//获取主线程的 tid 号
printf("tid = %lu\n",(unsigned long)tid);
return 0;
}
线程的创建pthread_create
可以使用pthread_create函数创建线程:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
其中的输入参数:
pthread_t *thread
为一个pthread_t指针,存放新创建的线程号const pthread_attr_t *attr
表示线程的属性,可传入NULL表示默认属性void *(*start_routine) (void *)
为一个void函数指针,表示线程执行的函数void *arg
表示向执行函数传的参数,不传直接填NULL就行。
示例程序如下
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun(void *arg){
printf("pthread_New = %lu\n",(unsigned long)pthread_self());//打印线程的 tid 号
}
int main(){
pthread_t tid1;
int ret = pthread_create(&tid1,NULL,fun,NULL);//创建线程
if(ret != 0){ //创建失败则ret返回值不为0(成功为0,失败为-1)
printf("pthread_create err!\n");//创建失败则printf一个err
return -1;
}
sleep(1);//主线程sleep一下
return 0;
}
还可以一次性创建多个
pthread_t tid[3];
for(i = 0;i < 3;i++){
int ret = pthread_create(&tid[i],NULL,fun,NULL);
}
线程竞争
- 使用多线程时,各个线程执行顺序随机竞争。
- 主线程结束后,进程直接结束,其余线程也立刻结束(无论执行状态)。
- 可以在主程序末尾return之前,加入
sleep(n)
函数使主线程稍作停顿,以完成所有其他线程的执行
线程的参数传入(单个)
这里有两种传入方式,引用传递或者强制类型转换
传入时,要记得先转成long,再转成void*,因为针对不同位数机器,指针对应位数不同,int有时表示不了,会产生警告
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void *fun1(void *arg){
printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
}
void *fun2(void *arg){
printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
}
int main(){
int a = 50;
pthread_t tid1,tid2;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);//创建新线程1,引用传递a的地址
ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);//创建新线程2,强制类型转换,普通传入a的值
printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);//主线程也printf一下
sleep(1);
return 0;
}
执行结果:
main:a = 50 Add = 0x7ffe9c005b70
func1:arg = 50 Addr = 0x7ffe9c005b70
func2:arg = 50 Addr = 0x32
线程的参数传入(多个)
想传多个利用结构体就可以了,和传单个区别不大,就是把 &a 换成 &结构体名。如下:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
struct Stu{
int Id;
char Name[32];
float Mark;
};
void *fun1(void *arg){
struct Stu *tmp = (struct Stu *)arg;
printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
}
int main(){
pthread_t tid1;
struct Stu stu;
stu.Id = 10000;
strcpy(stu.Name,"ZhangSan");
stu.Mark = 94.6;
int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
sleep(1);
return 0;
}
线程的退出与回收
自己退出pthread_exit( )
#include <pthread.h>
void pthread_exit(void *retval);
用在执行函数fun中,表示结束本线程,并传一个数据给主线程,用指针retval传,不想传就NULL
此传回值,在fun函数中必须加上static作为前缀定义,如static int tmp
,只定义为int tmp
是传不回去的。原因是本身fun里面的参数是个临时变量,不加个static生命周期就结束了。
指定退出pthread_cancel( )
#include <pthread.h>
int pthread_cancel(线程id);
该函数输入参数为线程id,直接使该线程退出
阻塞回收pthread_join( )
#include <pthread.h>
int pthread_join(线程id, void **retval);
第一个参数代表要回收哪个线程,第二个参数代表接收变量
阻塞接收意思是死等,等收到返回值之后自己才返回,不然就一直卡在这一句死等。
非阻塞回收pthread_tryjoin_np( )
#define _GNU_SOURCE
#include <pthread.h>
int pthread_tryjoin_np(线程id, void **retval);
不等,执行到这了立刻去看一眼,回收成功就返回0。
互斥锁
由于多线程共享资源,有时会出现争抢全局变量等行为,导致哪个线程都得不出想要的结果,所以引入互斥锁
使用流程就是加锁->解锁->最后销毁
互斥量初始化pthread_mutex_init( )
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);//成功则返回0
其中的输入参数:
phtread_mutex_t *mutex
表示该互斥量指针,用于存放该互斥量const pthread_mutexattr_t *restrict attr
表示控制互斥量的属性,一般填NULL就行
互斥量阻塞加锁pthread_mutex_lock( )与非阻塞加锁pthread_mutex_trylock( )
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
线程内执行lock后,其余线程运行到lock函数就会阻塞,直到unlock后其余线程再竞争,记得一定unlock不然死锁
阻塞与非阻塞区别:阻塞就卡在lock这句硬要加,非阻塞就尝试一下加锁没加上就算了继续执行下面的了
互斥量解锁pthread_mutex_unlock( )
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);//成功则返回0
互斥量销毁pthread_mutex_destory( )
#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
示例程序
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex;//互斥量变量 一般申请全局变量
int Num = 0;//全局变量
void *fun1(void *arg){
pthread_mutex_lock(&mutex);//加锁 若有线程获得锁,则会阻塞
执行点啥用上Num;
pthread_mutex_unlock(&mutex);//解锁
pthread_exit(NULL);//线程退出 pthread_join 会回收资源
}
void *fun2(void *arg){
pthread_mutex_lock(&mutex);//加锁 若有线程获得锁,则会阻塞
执行点啥用上Num;
pthread_mutex_unlock(&mutex);//解锁
pthread_exit(NULL);//线程退出 pthread_join 会回收资源
}
int main(){
int ret;
pthread_t tid1,tid2;
ret = pthread_mutex_init(&mutex,NULL);//初始化互斥量
ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程 1
ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程 2
pthread_join(tid1,NULL);//阻塞回收线程 1
pthread_join(tid2,NULL);//阻塞回收线程 2
pthread_mutex_destroy(&mutex);//销毁互斥量
return 0;
}
信号量
信号量起通知作用,线程A 在等待某件事,线程 B 完成了这件事后就可以给线程A发信号,实现线程执行顺序可控
信号量初始化sem_init( )
int sem_init(sem_t *sem,int pshared,unsigned int value);
其中的输入参数:
sem_t *sem
表示传入一个sem_t指针,保存信号量int pshared
0-线程控制、1-进程控制unsigned int value
代表信号量初始值,0-阻塞、1-运行
信号量阻塞申请sem_wait( )与非阻塞申请sem_trywait( )
#include <pthread.h>
int sem_wait(sem_t *sem);//成功返回0
int sem_trywait(sem_t *sem);//成功返回0
检测当前信号量资源是否可用,可用则运行当前线程并令信号量值-1(即从可用1改为阻塞0,不让其他线程用了)
信号量释放sem_post( )
#include <pthread.h>
int sem_post(sem_t *sem);
与申请相反,令信号量+1,即从阻塞0改为可用1
信号量销毁sem_destory( )
#include <pthread.h>
int sem_destory(sem_t *sem);
用完信号量最后要记得销毁掉哦,跟锁其实有点像的。
示例程序:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
sem_t sem1,sem2,sem3;//申请的三个信号量变量
void *fun1(void *arg){
sem_wait(&sem1);//因 sem1 本身有资源,所以不被阻塞 获取后 sem1-1=0 下次会会阻塞
printf("%s:Pthread Come!\n",__FUNCTION__);
sem_post(&sem2);// 使得 sem2 获取到资源
pthread_exit(NULL);
}
void *fun2(void *arg){
sem_wait(&sem2);//因 sem2 在初始化时无资源会被阻塞,直至 14 行代码执行 不被阻塞 sem2-1=0 下次会阻塞
printf("%s:Pthread Come!\n",__FUNCTION__);
sem_post(&sem3);// 使得 sem3 获取到资源
pthread_exit(NULL);
}
void *fun3(void *arg){
sem_wait(&sem3);//因 sem3 在初始化时无资源会被阻塞,直至 22 行代码执行 不被阻塞 sem3-1=0 下次会阻塞
printf("%s:Pthread Come!\n",__FUNCTION__);
sem_post(&sem1);// 使得 sem1 获取到资源
pthread_exit(NULL);
}
int main(){
int ret;
pthread_t tid1,tid2,tid3;
ret = sem_init(&sem1,0,1); //初始化信号量 1 并且使其可用
ret = sem_init(&sem2,0,0); //初始化信号量 2 让其阻塞
ret = sem_init(&sem3,0,0); //初始化信号 3 让其阻塞
ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程 1
ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程 2
ret = pthread_create(&tid3,NULL,fun3,NULL);//创建线程 3
/*回收线程资源*/
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
/*销毁信号量*/
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
return 0;
}
条件变量
类型:pthread_cond_t cond
初始化:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
1放条件变量指针,2一般NULL
销毁:int pthread_cond_destroy(pthread_cond_t *cond);
使用:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
示例:
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);// 条件不满足则unlock g_tMutex,条件满足则lock g_tMutex
pthread_mutex_unlock(&g_tMutex);