线程与进程的区别
进程:正在运行的程序,资源分配的最小单位。
线程:进程中的执行路径,调度的最小单位。
linux中线程的实现
linux实现线程的机制非常特殊,从内核的角度来说,它没有线程这个概念。linux把所有线程当作进程来实现。内核并没有定义特殊的调度算法或是定义特别的结构体来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都有自己的PCB,所以在内核中,它看起来像是一个普通的进程(只是和其他进程共享某些资源,如地址空间)。
多线程
所需头文件:
#include<pthread.h>
接口函数:
int pthread_create( pthread_t *thread,const pthread_attr_t *attr,void*(*start_routine)(void*) ,void* arg):创建线程
thread:创建线程的id
attr:线程的属性信息,一般写NULL
start_routine:线程函数
arg:线程函数的参数
void pthread_exit( void* retval ):退出线程并返回退出信息
retval:退出信息
int pthread_join( pthread* thread,void **retval ):等待指定线程thread退出
thread:线程id
retval:接收thread退出时返回的指定信息
举例:
①主线程打印10次main,子线程打印10次thread
②主线程打印10次main,子线程打印5次thread
结论:子线程结束并不会影响到主线程的运行
③主线程打印5次main,子线程打印10次thread
结论:主线程结束会影响到子线程的运行
如何解决主线程结束导致子线程未运行完毕的问题?
解决方法:调用pthread_exit和pthread_join方法
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<pthread.h>
void *fun(void *arg) //线程函数
{
int i=0;
for(i;i<10;i++)
{
printf("thread\n");
sleep(1);
}
//线程结束,并返回结束信息
pthread_exit("子线程结束");
}
int main()
{
pthread_t id;
//创建线程
pthread_create(&id,NULL,fun,NULL);
int i=0;
for(i;i<5;i++)
{
printf("main\n");
sleep(1);
}
char *s=NULL;
pthread_join(id,(void**)&s); //等待id线程结束,结束之前会阻塞
printf("main over : %s\n",s);
exit(0);
}
运行结果:
并发访问同一内存的问题
问题:当我们同时创建5个线程,每个线程都对int x = 0进行自加操作,并打印每次的值
预期结果:最后打印出5000
实际结果:(不一定每次都是4999,也可能是5000,也可能是4997...)
为什么会出现这样的结果:
如何解决线程不同步的问题,我们引入了以下4种方法来进行线程同步。
线程同步
信号量
所需头文件:
#include<semaphore.h>
接口函数:
int sem_init( sem_t *sem,int pshare,unsigned int value ):创建并初始化信号量
sem:信号量变量
pshare:0代表用于多线程同步,>0代表其他进程也能共享这一信号量
value:信号量初始值
int sem_wait( sem_t *sem ):p操作
sem:信号量变量
int sem_post( sem_t *sem ):v操作
sem:信号量变量
int sem_destory( sem_t *sem ):销毁信号量
sem:信号量变量
代码实现:使用信号量解决并发访问同一内存的问题(上述结果打印不是5000的问题)
运行结果:
互斥锁
在进入每个线程时加锁,当此线程运行结束时解锁,这样就可以做到每次只有一个线程访问同一个资源。
所需头文件:
#include<pthread.h>
接口函数:
int pthread_mutex_init( pthread_mutex_t *mutex,pthread_mutexattr_t *attr ):创建互斥锁
mutex:锁变量
attr:互斥锁属性,一般给NULL
int pthread_mutex_lock( pthread_mutex_t *mutex ):对互斥锁进行加锁
mutex:锁变量
int pthread_mutex_unlock( pthread_mutex_t *mutex ):对互斥锁进行解锁
mutex:锁变量
int pthread_mutex_destory( pthread_mutex_t *mutex ):销毁互斥锁
mutex:锁变量
代码实现:使用互斥锁解决并发访问同一内存的问题(上述结果打印不是5000的问题)
运行结果:
读写锁
在多线程访问同一块资源时,往往是读操作多,写操作少。并且在读操作时,其他线程也可以进行读操作(如果我们使用互斥锁,那么其他线程就不能进行读操作),但是当一线程进行写操作时,其他线程不能进行读操作。为了实现这样的情况,引入了读写锁。
所需头文件:
#include<pthread.h>
接口函数:
int pthread_rwlock_init( pthread_rwlock_t *rwlock,pthread_rwlockattr_t *attr ):创建并初始化读写锁
rwlock:读写锁变量
attr:读写锁属性设置(一般写NULL)
int pthread_rwlock_rdlock( pthread_rwlock_t *rwlock ):加读锁
rwlock:读写锁变量
int pthread_rwlock_wrlock( pthread_rwlock_t *rwlock ):加写锁
rwlock:读写锁变量
int pthread_rwlock_unlock( pthread_rwlock_t *rwlock ):解锁(读和写)
rwlock:读写锁变量
举例:创建4个线程,两个线程同时进行读操作,两个线程进行写操作(预期结果:读操作可以同时进行;在读时不能写,反之也是;写操作也不能同时操作)
代码实现:
运行结果:
条件变量
(注意:条件变量需要和互斥锁一起使用)
所需头文件
#include<pthread.h>
接口函数:
int pthread_cond_init( pthread_cond_t* cond,pthread_condattr_t* attr ):创建条件变量
cond:条件变量
attr:条件变量属性(一般给NULL)
int pthread_cond_wait( pthread_cond_t* cond,pthread_mutex_t* mutex ):将线程加入等待队列中阻塞
cond:条件变量
mutex:互斥锁(辅助条件变量,使其能正常进行)
注意:在将线程放入等待队列中时,需要使用互斥锁
原因:
wait方法原理:
例如此段代码:
在进入wait函数前先加锁(保证此时只有这个进程在进行入队列的操作)
调用wait函数时,在函数内部会先解锁,将此线程的信息保存在一个容器中,当线程被唤醒时会再加锁。
wait结束被唤醒时时我们需要再解锁一次,才能进行其他的操作
int pthread_cond_signal( pthread_cond_t* cond ):唤醒一个线程工作
cond:条件变量
int pthread_cond_broadcast( pthread_cond_t* cond ):唤醒所有线程工作
cond:条件变量
int pthread_cond_destory( pthread_cond_t* cond ):销毁条件变量
cond:条件变量
举例:我们创建2个线程,主线程负责向一块空间写入数据(写入完成输入回车时,唤醒线程(如果输入end:唤醒并结束所有线程;其他输入只唤醒某一个线程)),两个子线程负责读取此空间写入的数据。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<pthread.h>
pthread_cond_t cond; //定义条件变量
pthread_mutex_t mutex; //定义互斥锁
char buff[128]={0};
void* work_fun(void* arg) //线程函数
{
char* s=(char*)arg;
while(1)
{
pthread_mutex_lock(&mutex); //加锁
pthread_cond_wait(&cond,&mutex); //线程加入等待队列
pthread_mutex_unlock(&mutex); //解锁
if(strncmp(buff,"end",3) == 0)
{
printf("%s 结束\n",s);
break;
}
printf("%s %s",s,buff);
}
}
int main()
{
pthread_cond_init(&cond,NULL); //创建条件变量
pthread_mutex_init(&mutex,NULL); //创建互斥锁
pthread_t id1,id2;
//创建线程
pthread_create(&id1,NULL,work_fun,"线程1:");
pthread_create(&id2,NULL,work_fun,"线程2:");
while(1)
{
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0 )
{
pthread_cond_broadcast(&cond); //end->唤醒所有线程
break;
}
pthread_cond_signal(&cond); //其他->唤醒某个线程
}
//等待线程结束
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_mutex_destroy(&mutex); //销毁互斥锁
pthread_cond_destroy(&cond); //销毁条件变量
printf("main over\n");
exit(0);
}
运行结果:
线程安全
概念:在多线程中,无论调度顺序是什么样,最后都能得到正确且一致的结果。
线程不安全的例子:我们创建一个线程,让主线程和子线程同时使用strtok去分割两个不同的数组,并打印出来
(预期结果:两个数组都被完全打印)
代码:
造成这样结果的原因:
结论:
strtok这个函数其本身就不能在多线程中使用,属于线程不安全函数(不可重入函数),而除了strtok还有很多函数都是线程不安全的
(可重入函数:在调用此函数未结束时,再调用此函数,依然可以得到正确的结果,那么就称之为可重入函数)
解决方法:
调用线程安全版的strtok_r函数:
比普通的strtok函数多了一个二级指针来保存上一次分割完的地址
代码:
运行结果:
线程安全总结:
如何确保线程安全:(1)线程同步[ 信号量,互斥锁,读写锁,条件变量 ](2)使用线程安全函数
多线程中执行fork
多线程中某个线程执行fork后,子进程中的线程个数?
结论:多线程中某个线程执行fork后,子进程中只会有执行fork的这一条线程
进程fork前有锁,fork后子进程中锁的状态?
举个例子:让线程先进性加锁(fork前先睡眠1秒),确保线程加锁且没有解锁时(加锁后睡眠5秒再解锁),主线程进行fork,并打印出加锁解锁状态
代码:
解决方法:
pthread_atfork函数
所需头文件:
#include<pthread.h>
函数定义:
int pthread_atfork( void (*prepare)(void),void (*parent)(void),void (*child)(void) ):确保在fork执行之前锁已经空闲没有其他人使用(确定锁的状态)
prepare:在fork前执行,用来获取父进程的锁(对其进行加锁操作)
parent:在fork后且在fork返回之前执行,用来释放prepare中的锁(对其进行解锁操作)
child:和parent一样,在程序中可以用同一个函数
用oprhread_atfork解决之前的问题:
代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/wait.h>
pthread_mutex_t mutex;
void prepare(void)
{
pthread_mutex_lock(&mutex);
}
void after(void)
{
pthread_mutex_unlock(&mutex);
}
void* fun(void* arg)
{
printf("fun lock\n");
pthread_mutex_lock(&mutex);
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
pthread_t id;
pthread_atfork(prepare,after,after);
pthread_mutex_init(&mutex,NULL);
pthread_create(&id,NULL,fun,NULL);
sleep(1); //确保线程已经加锁
pid_t pid = fork();
if(pid == 0)
{
printf("child start to lock\n");
pthread_mutex_lock(&mutex);
printf("child start to unlock\n");
pthread_mutex_unlock(&mutex);
exit(0);
}
wait(NULL);
pthread_join(id,NULL);
return 0;
}
运行结果: