线程:进程内部的一条执行路径,调度执行的基本单位
进程:一个正在运行的程序,资源分配的基本单位
同步:信号量,互斥锁,读写锁,条件变量
Linux线程的实现,用户,内核,混合
查看线程的ID:ps -L 线程ID
线程的同步
1.信号量
信号量的相关函数
//初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
//P
int sem_wait(sem_t *sem);
//v
int sem_post(sem_t *sem);
//销毁信号量
int sem_destroy(sem_t *sem);
2.互斥锁
互斥锁相关函数
//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.读写锁
//初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);
//读锁加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写锁加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//读写锁销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4.条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
int pthread_cond_destroy(pthread_cond_t *cond);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
char buff[128]={0};
void* funa(void* arg)
{
while(1)
{
//等待条件可用
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
if( strncmp(buff,"end",3) == 0)
{
break;
}
//打印BUFF
printf("funa buff = %s\n",buff);
}
}
void* funb(void* arg)
{
while(1)
{
//等待条件可用
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
if( strncmp(buff,"end",3)==0)
{
break;
}
printf("funb buff = %s\n",buff);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_t ida,idb;
pthread_create(&ida,NULL,funa,NULL);
pthread_create(&idb,NULL,funb,NULL);
while(1)
{
//键盘获取数据
fgets(buff,128,stdin);
//通知线程
if( strncmp(buff,"end",3)==0)
{
//唤醒所有等待的进程,然后退出
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
break;
}
else
{
//唤醒一个进程
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
pthread_join(ida,NULL);
pthread_join(idb,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
5.利用信号量和互斥锁实现生产者和消费者模型
//生产者消费者模型
//缓冲区的大小
#define BUFF_MAX 10
//生产者的个数
#define SC_NUM 2
//消费者的个数
#define XF_NUM 3
//缓冲区中存在几个空位
sem_t empty;
//缓冲区中存在几个资源
sem_t full;
pthread_mutex_t mutex;
//缓冲区,同一时刻只允许一个生产者或消费者对缓冲区进行访问
int buff[BUFF_MAX];
//生产者生产一个资源后加1,in = (in+1)%BUFF_MAX
int in=0;
//消费者消费一个资源后加1,out = (out+1)%BUFF_MAX
int out=0;
//生产者线程
void* sc_thread(void* arg)
{
//此index用来生命传入的值,用以区别
int index=(int)arg;
for(int i=0;i<30;i++)
{
sem_wait(&empty);
pthread_mutex_lock(&mutex);
buff[in] = rand()%100;
printf("第%d个生产者,下标为%d的生产的%d\n",index,in,buff[in]);
in = (in +1)%BUFF_MAX;
pthread_mutex_unlock(&mutex);
sem_post(&full);
int n=rand()%10;
sleep(n);
}
}
//消费者线程
void* xf_thread(void *arg)
{
int index=(int)arg;
for(int i=0;i<20;i++)
{
sem_wait(&full);
pthread_mutex_lock(&mutex);
printf("第%d个消费者,下标为%d的消费的值为%d\n",index,out,buff[out]);
out = (out+1)%BUFF_MAX;
pthread_mutex_unlock(&mutex);
sem_post(&empty);
int n=rand()%10;
sleep(n);
}
}
int main()
{
//初始化信号量
sem_init(&empty,0,BUFF_MAX);
sem_init(&full,0,0);
//初始化互斥锁
pthread_mutex_init(&mutex,NULL);
srand((int)time(NULL));
pthread_t sc_id[SC_NUM];
pthread_t xf_id[XF_NUM];
for(int i=0;i<SC_NUM;i++)
{
//创建生产者线程
pthread_create(&sc_id[i],NULL,sc_thread,(void*)i);
}
for(int i=0;i<XF_NUM;i++)
{
//创建消费者线程
pthread_create(&xf_id[i],NULL,xf_thread,(void*)i);
}
for(int i=0;i<SC_NUM;i++)
{
pthread_join(sc_id[i],NULL);
}
for(int i=0;i<XF_NUM;i++)
{
pthread_join(xf_id,NULL);
}
//销毁信号量
sem_destroy(&empty);
sem_destroy(&full);
//销毁互斥锁
pthread_mutex_destroy(&mutex);
printf("main over\n");
return 0;
}
6.读写锁的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
//读写锁
//使用场景:少量的写+大量的读
//加锁规则:
//写的情况 一次只有一个线程可以占用写模式的读写锁,一个执行流在进行写的时候,其他执行流既不能写,也不能读,只能陷入阻塞状态
//读的情况:多个读取进程可以同时占用读模式下的读写锁,在读写锁的内部有一个引用计数器
//引用计数:标示者有多少以读模式打开的读写锁线程
//初始化读写锁
pthread_rwlock_t rwlock;
void* read_fun1(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read1 start\n");
sleep(1);
printf("read1 end\n");
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
void* read_fun2(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read2 start\n");
sleep(1);
printf("read2 end\n");
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
void* write_fun(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
printf("write start\n");
sleep(3);
printf("write end\n");
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
int main()
{
//初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
pthread_t idr1,idr2;
pthread_t idw;
//创建读者线程和写着线程
pthread_create(&idr1,NULL,read_fun1,NULL);
pthread_create(&idr2,NULL,read_fun2,NULL);
pthread_create(&idw,NULL,write_fun,NULL);
pthread_join(idr1,NULL);
pthread_join(idr2,NULL);
pthread_join(idw,NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
线程安全
1.strtok函数不能在多线程中使用,因为它非线程安全函数,只能在单线程使用
2.线程安全:多线程程序无论调度顺序如何,都能得到正确的一致的效果
3.线程安全函数:
strtok() 非线程安全函数
strtok_r 线程安全函数
4.解决线程安全问题方法:同步,使用线程安全的函数
5.非线程安全函数由什么原因导致?
在函数内部使用全局变量或静态变量
多线程+fork()
多线程中执行fork(),会将整个进程进行复制,但是在子进程中只启用一条执行路径(fork所在的那条执行路径)
1.多线程 + fork() 不加锁
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
//多线程+fork 不加锁
void* fun(void* arg)
{
for(int i=0;i<5;i++)
{
printf("fun run pid=%d\n",getpid());
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
fork();
for(int i=0;i<5;i++)
{
printf("main run pid=%d\n",getpid());
sleep(1);
}
pthread_join(id,NULL);
return 0;
}
2.多线程 + fork() 加锁
子进程复制了父进程的mutex,包含其状态,(加锁或未加锁)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/wait.h>
//多线程+fork()+锁
//fork()会复制锁,也就是进程和子进程各用一把锁,另外还会复制锁的状态
pthread_mutex_t mutex;
void* fun(void* arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&mutex,NULL);
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid = fork();
if(pid == -1)
{
exit(0);
}
if(pid == 0)
{
printf("child will lock\n");
pthread_mutex_lock(&mutex);
printf("child lock\n");
pthread_mutex_unlock(&mutex);
printf("child unlock\n");
exit(0);
}
wait(NULL);
}
会阻塞,因为fork时将已经加锁的锁复制了,所以在子进程中加锁失败,就会阻塞到pthread_mutex_lock(&mutex)
解决方案:
pthread_atfork的使用说明:
pthread_atfork(void (*prepare)(void),void (*parent)(void), void(*child)(void))
prepare在父进程fork创建子进程之前调用,这里可以获取父进程定义的所有锁;
child fork返回之前在子进程环境中调用,在这里unlock prepare获得的锁;
parent fork创建了子进程以后,但在fork返回之前在父进程的进程环境中调用的,在这里对prepare获得的锁进行解锁;
1、使用场景
当父进程有多线程时,子进程继承父进程所有的互斥量、读写锁和条件变量的状态,子进程只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁(任一线程),子进程同样占有这些锁。如果父进程马上调用exec,老的地址空间被丢弃,所以锁的状态无关紧要。否则,则要清除锁的状态。
原文链接:https://blog.csdn.net/codinghonor/article/details/43737869
//多线程+fork()+锁
//fork()会复制锁,也就是进程和子进程各用一把锁,另外还会复制锁的状态
pthread_mutex_t mutex;
void after_fork()
{
pthread_mutex_unlock(&mutex);
}
void before_fork()
{
pthread_mutex_lock(&mutex);
}
void* fun(void* arg)
{
pthread_mutex_lock(&mutex);
printf("fun lock\n");
sleep(5);
pthread_mutex_unlock(&mutex);
printf("fun unlock\n");
}
int main()
{
//初始化互斥锁
pthread_mutex_init(&mutex,NULL);
pthread_atfork(before_fork,after_fork,after_fork);
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
sleep(1);
pid_t pid = fork();
if(pid == -1)
{
exit(0);
}
if(pid == 0)
{
printf("child will lock\n");
pthread_mutex_lock(&mutex);
printf("child lock\n");
pthread_mutex_unlock(&mutex);
printf("child unlock\n");
exit(0);
}
wait(NULL);
}