早期unix系统中,没有线程概念,后来才引入线程
linxu 为了迎合 windows引入了线程 ,linux 上进程是非常优秀了,linux 上用线程和进程的区别不大,老程序都是用进程
gdb不支持线程,因为gdb比线程出现了早
区别:
线程: 有独立的pcb. 没有独立的进程地址空间,线程在进程内部,共享进程地址空间
进程: 有独立的进程 地址空间,有独立的pcb
进程是分配资源的最小单位,如何理解: * 如果一个进程A有3个线程,那么cpu会把A进程当作4个进程,cpu分配时间片个A进程 给优先
是不是线程越多进程优先级越高: 但线程数量达到一定程度的时候,优先级会降低
进程是分配资源的最小单位, 线程是最小的执行单位
fork() 原理, 底层调用clone() 克隆一份, 70% 的的东西从父进程中复制过来,
自己改一下 ID ,
线程共享 进程的数据 , 多个线程都指向同一个页目录
pcb 三级页表: 多个线程 指向 页表, mmu , 最终指向的物理内存 地址是相同的, 所以多个线程共享进程资源
线程是最小执行单位,进程最小分配空间单位 , cpu 调度
4.
LWP号: 线程的号 ,cpu根据线程号分配轮片 火狐浏览器就是用多线程开发的 ,使用线程池机制
线程id: 进程中区分不通过线程的
ps -Lf pid : 查看进程下有哪些线程
05_线程优缺点和共享资源
优点:
1. 线程共享:
2. 文件描述符表
3. 信号处理方式
4. 当前工作目录
5. 用户id 和组 ID
6. 内存空间地址
缺点:
非共享: 线程ID , 栈 , 栈 (每个线程独立栈)、 errno变量 、 信号屏蔽字、
线程优点:
提供程序并发:一个进程有多个线程, 可以多争取cpu
开销下, 每一个进程都要开辟0-4G虚拟空间,线程不要
缺点:
线程锁函数都是第三方库函数, 稳定性不好,
不支持gdb
读信号不支持不好,信号首先出来,线程后面出来
建议: 用线程了就别用进程了,否则负责度很大
本章总结 线程进程函数总结对比
* pthread_create() fork()
* pthread_self() getpid()
* pthread_exit() exit()
* pthread_join() wait/waitpid
* pthread_cancle() kill
* pthread_detach()
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void* tfun(void* args){
int* j=(int *)args;
printf("son thread.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),*j);
// printf("---%d---",*j);
return NULL;
}
int main0000014(){
/**
* 线程共享资源:
* 1. 文件描述符表
* 2. 当前工作目录
* 3. 用户ID 和组 ID
* 4. 内存地址空间(.text/.data/.bss/堆/共享库)[共享全局变量]
* 非共享:
*1. 线程id
*2. 栈
* 优点: 效率高
*/
// lwp 标识线程身份,cpu调度区分 线程id: 进程内部不同线程区分
pthread_t tid;
// 线程id,线程属性,回调函数,回调函数参数
// 打印主线程
printf("main thread..pid = %d ... pthread %lu \n",getpid(),pthread_self());
int j=1000;
int ret = pthread_create(&tid,NULL,tfun,&j); // 返回线程id
sleep(1); //子线程还没有输出,父进程已经死了,避免
if(ret == -1){
perror("pthread_create fail");
exit(-1);
}
// gcc single.c -o single.o -lpthread
// 第三方库要加 -lpthread
return 0;
}
2.1.1线程传递问题
void* tfun(void* args){
int* j=(int *)args;
printf("son thread.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),*j);
// printf("---%d---",*j);
return NULL;
}
/**
* 循环创建多个线程
*/
int main0000015() {
pthread_t tid;
int ret=0;
for (int i = 0; i < 10; i++) {
// 错误
/**
* son thread.... pid = 3521 ... 140422606616320-args--2
son thread.... pid = 3521 ... 140422589830912-args--5
son thread.... pid = 3521 ... 140422598223616-args--3
son thread.... pid = 3521 ... 140422615009024-args--1
son thread.... pid = 3521 ... 140422581438208-args--7
son thread.... pid = 3521 ... 140422564652800-args--7
son thread.... pid = 3521 ... 140422573045504-args--7
son thread.... pid = 3521 ... 140422556260096-args--8
son thread.... pid = 3521 ... 140422479345408-args--9
son thread.... pid = 3521 ... 140422470952704-args--10
出现重复数据,
为什么: for循环不断执行当,但i=5的时候在栈中,子线程回调才第一次执行,才切换成内核态取出i=5,实际的需要的是0
解决方法: 直接传递值
*/
ret= pthread_create(&tid, NULL, tfun, (void*) &i); // 返回线程id
if (ret !=0 ) {
perror("pthread_create fail");
exit(-1);
}
}
sleep(1);
return 0;
}
void* tfun1(void* args){
int j=(int )args;
printf("son thread...tfun1.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),j);
// printf("---%d---",*j);
return NULL;
}
int main0000016() {
pthread_t tid;
int ret=0;
for (int i = 0; i < 10; i++) {
ret= pthread_create(&tid, NULL, tfun1, (void*) i); // 返回线程id
if (ret !=0 ) {
perror("pthread_create fail");
exit(-1);
}
}
sleep(1);
return 0;
}
/**
* exit: 退出进程
* pthred_exit 退出线程
*/
void* tfun3(void* args){
int j=(int )args;
if(j==2){
// return NULL; // 返回到函数调用者那里去 , 退出以后不会往下走了
pthread_exit(NULL);
}
printf("son thread...tfun1.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),j);
// printf("---%d---",*j);
return NULL;
}
int main0000017() {
pthread_t tid;
int ret=0;
for (int i = 0; i < 10; i++) {
ret= pthread_create(&tid, NULL, tfun3, (void*) i); // 返回线程id
if (ret == -1) {
perror("pthread_create fail");
exit(-1);
}
}
sleep(1);
return 0;
}
/**
* pthread_join 阻塞回收子线程 相当有 wait
*/
void* tfun4(void* args){
return (void*)1100;
}
int main0000018() {
pthread_t tid;
// 成功返回0
int ret= pthread_create(&tid, NULL, tfun4, NULL); // 返回线程id
if(ret !=0){
perror("pthread_create erro");
exit(-1);
}
int *retval;
// 参数类型void**
// 参数1 线程id 参数2 回调函数返回值 tfun4
ret = pthread_join(tid,(void**)&retval);
if(ret!=0){
perror("pthread_create erro");
exit(-1);
}
// 返回值类型 -- return (void*)1100;
printf("resutl---%d",retval);
return 0;
}
设计指针的时候处理方式1:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct
{
char ch;
int var;
char str[64];
}exit_t;
/**
* pthread_join 阻塞回收子线程 相当有 wait
*/
void* tfun4(void* args){
exit_t* retvar = (exit_t*) malloc(sizeof(exit_t));
retvar->ch='m';
retvar->var=3000;
strcpy(retvar->str,"my thread\n");
pthread_exit((void*)retvar);
}
int main() {
pthread_t tid;
// 成功返回0
int ret= pthread_create(&tid, NULL, tfun4, NULL); // 返回线程id
if(ret !=0){
perror("pthread_create erro");
exit(-1);
}
exit_t *retval;
// 参数类型void**
// 参数1 线程id 参数2 回调函数返回值 tfun4
ret = pthread_join(tid,(void**)&retval);
if(ret!=0){
perror("pthread_create erro");
exit(-1); // 等价于 return -1
}
// 返回值类型 -- return (void*)1100;
printf("ch=%c, var=%d, str=%s\n", retval->ch,retval->var,retval->str);
free(retval);
pthread_exit((void*)1);
// return 0;
}
方式2:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct
{
char ch;
int var;
char str[64];
}exit_t;
/**
* pthread_join 阻塞回收子线程 相当有 wait
*/
void* tfun4(void* args){
//exit_t* retvar = (exit_t*) malloc(sizeof(exit_t));
exit_t* retvar= (exit_t*)args;
retvar->ch='m';
retvar->var=3000;
strcpy(retvar->str,"my thread\n");
pthread_exit((void*)retvar);
}
int main() {
pthread_t tid;
exit_t *retval = malloc(sizeof(exit_t));
// 成功返回0
int ret= pthread_create(&tid, NULL, tfun4, (void*) retval); // 返回线程id
if(ret !=0){
perror("pthread_create erro");
exit(-1);
}
// 参数类型void**
// 参数1 线程id 参数2 回调函数返回值 tfun4
ret = pthread_join(tid,(void**)&retval);
if(ret!=0){
perror("pthread_create erro");
exit(-1);
}
// 返回值类型 -- return (void*)1100;
printf("ch=%c, var=%d, str=%s\n", retval->ch,retval->var,retval->str);
free(retval);
pthread_exit((void*)1);
// return 0;
}
2.4.1 回收多个线程
void* tfun5(void* args){
return args;
}
int main0000019() {
pthread_t tid[10];
int ret=0;
for (int i = 0; i < 10; i++) {
ret= pthread_create(&(tid[i]), NULL, tfun5, (void*) i); // 返回线程id
if (ret == -1) {
perror("pthread_create fail");
exit(-1);
}
}
int *retval;
sleep(1);
for(int j=0;j<10;j++){
pthread_join(tid[j],(void**)&retval);
printf("-sleep---%d----%d\n",tid[j],retval);
}
return 0;
}
功能: 杀死线程
void* tfun11(void* args){
while(1){
int j=(int )args;
/**
* 调用pthread_cancel 以后如果子线程不进入系统调用比如
* 没有调用printf、sleep函数那么无法杀死子线程
* 如果没有上面函数,自己添加取消点:
* 调用: pthread_testcancel();
*/
// printf("son thread...tfun1.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),j);
// sleep(1);
pthread_testcancel();
}
return NULL;
}
/**
* pthread_cancel 相当于kill
*/
int main0000020() {
pthread_t tid;
int ret=0;
ret= pthread_create(&tid, NULL, tfun11, (void*) 1999); // 返回线程id
if (ret !=0 ) {
perror("pthread_create fail");
exit(-1);
}
int *retval;
// 杀死线程
/**
* 调用pthread_cancel 以后如果
*/
pthread_cancel(tid);
// 如果线程被杀死了,返回-1
pthread_join(tid,&retval);
printf("return value----- %d",retval);
//sleep(10);
return 0;
}
功能: 线程主动与主控线程断开关系, 线程结束后,不会产生僵尸线程, 自动释放,网络,多线程常用
void* tfun12(void* args){
int j=(int )args;
printf("son thread...tfun1.... pid = %d ... %lu-args--%d \n",getpid(),pthread_self(),j);
sleep(1);
return NULL;
}
/***
* pthread_detach
*/
int main0000021() {
pthread_t tid;
int ret= pthread_create(&tid, NULL, tfun12, (void*) 1999); // 返回线程id
if (ret !=0 ) {
fprintf(stderr,"pthread_create fail--%s \n",strerror(ret));
exit(1);
}
printf("---%d\n",ret);
ret= pthread_detach(tid); //设置线pthread_join程分离,0表示成功
printf("---%d\n",ret);
// 分离开以后,线程执行完毕会被自动回收tid,没有了,后面pthread_join 来回收会出错
if(ret!=0){
fprintf(stderr,"pthread_detach fail--%s \n",strerror(ret));
exit(1);
}
sleep(3);
// 功能判断是否分离开,分离,已经回收返回-1
int ret3 = pthread_join(tid,NULL);
printf("---%d\n",ret3);
// #define EINVAL 22 /* Invalid argument */
if(ret3!=0){
// strerror 无效,没有内容????
printf("%s\n",strerror(ret3));
}
return 0;
}
线程默认属性已经够了,如果要用到
1. 线程分离
2. 线程栈大小, 一个进程默认8M, 2个线程, 每个4M
一般可以创建500 多个线程,一个进程
/***
* 线程属性设置
* 例子:通过线程属性设置线程自动detach
* 设置线程 内存大小,必须是 4K 的倍数, 最下是 16K , ios 下
*/
int main() {
pthread_t pid;
pthread_attr_t attr,newattr;
// 获取线程属性
int ret = pthread_attr_init(&attr);
if(ret!=0){
printf("pthread_attr_init--%s",strerror(ret));
exit(1);
}
// 宏 PTHREAD_CREATE_DETACHED 分离状态
ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // 设置线程分离属性
if(ret!=0){
printf("pthread_attr_init--%s",strerror(ret));
exit(1);
}
pthread_t tid;
ret = pthread_create(&tid,&attr,tfun12,NULL);
if(ret!=0){
printf("pthread_create--%s",strerror(ret));
exit(1);
}
printf("main ..... pid:%d,tid=%lu\n",getpid(),pthread_self());
// 获取线程属性
// pthread_attr_getdetachstate(&newattr,PTHREAD_CREATE_DETACHED);
// 去掉上面修改的,返回默认状态线程属性的
ret = pthread_attr_destroy(&attr);
if(ret!=0){
printf("pthread_attr_destroy--%s",strerror(ret));
exit(1);
}
sleep(5);
// 验证设置是否有效,出错表示有效
printf("----pthread_join 1---%d",ret);
// 这里没有返回 -1 , pthread_cancle的时候返回-1
// 这里放回 22 , 不合法参数异常 strerror
ret = pthread_join(tid,NULL);
printf("----pthread_join 2---%d",ret);
if(ret!=0){
printf("pthread_join--%s",strerror(ret));
exit(1);
}
return 0;
}
案例2: 修改在堆上申请内存 线程创建
查看项目库版本:getconf GNU_LIBPTHREAD_VERSION, 查看线版本, 有时候版本不一致可能错误
线程使用注意:
1. 主线程退其他线程不退出,主线程应调用pthread_exit
避免僵尸线程方法
1. pthread_join 回收
2. pthread_detach 通过函数分离
3. pthread_creat 设置属性分离
被 join 线程可能在join函数放回前就释放自己的所有内存资源, 所以不应当返回被回收的线程的值
3. malloc 和 mmap 申请的内存肯恩被其他线程释放
4. 避免在多线中fork , 否则 , 子线程中只有调用fork的线程存在,其他线程在子线程中均被
pthread_exit
5. 信号的复杂语义很难和多线程共存,应避免多线程中使用信号
线程同步: 多个动作,这个动作完成,才下一个动作 ,有序进行
多个线程访问共享数据的时候有次序,避免数据混乱
如何避免公共数据、堆数据多线程访问混乱:
所有线程应该访问公共数据前先拿到锁在访问,锁
线程1访问资源1 , 线程1拿到锁访问中,线程2过来了没有锁,排队,
线程1释放锁,线程2持有锁, 才访问
线程3来了,线程3是可以访问资源的,
所以linux的锁不是强制的, 线程3可以访问访问资源 , Linux的叫做建议锁
比如填充下面
111111111 线程1失去cpu 线程3来了获取cpu
8888888888888888 线程3失去cpu ,线程1获取cpu
8888888881111111 线程1从原来的位置填充
最终内存数据错乱
案例1: 子线程打印小写的helloworld 主线程打印大写的HELLOWORLD ,打印错乱
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void* tfn(void * arg){
srand(time(NULL));
while(1){
printf("hello ");
sleep(rand()%3); //
printf("world\n");
sleep(rand()%3); //
}
}
int main() {
pthread_t tid;
srand(time(NULL));
int ret=pthread_create(&tid,NULL,tfn,NULL);
if(ret !=0){
printf("%s\n",strerror(ret));
exit(1);
}
while(1){
printf("HELLLO ");
sleep(rand()%3);
printf("WORLD\n");
sleep(rand()%3);
}
pthread_join(tid,NULL);
return 0;
}
上面问题解决,加锁:
锁在程序中如何表现出来, mutex叫做 , 初始化互斥锁,可以理解初始值为1
// 加锁--1 0
// 解锁 ++1 +1
如果锁已经被A线程占用了(mutex=0),B线程去拿,那么B线程会阻塞
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
pthread_mutex_t mutex; // 1.定义互斥锁 , 使用方式,按照序号
void* tfn(void * arg){
srand(time(NULL));
while(1){
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand()%3); //
printf("world\n");
pthread_mutex_unlock(&mutex);
sleep(rand()%3); //
}
}
int main(){
pthread_t tid;
srand(time(NULL));
pthread_mutex_init(&mutex,NULL); //3. 初始化互斥锁,可以理解初始值为1
// init 完了, mutex=1 , mutex是一个结构体,完了方便理解,当做1
int ret=pthread_create(&tid,NULL,tfn,NULL);
if(ret !=0){
printf("%s\n",strerror(ret));
exit(1);
}
while(1){
pthread_mutex_lock(&mutex); // 4.加锁--1 0,如果锁被其他线程占用了,那么会阻塞在这里,等到其他线程释放锁
// try_lock 上面api会阻塞,尝试加锁,加锁失败直接返回错误号,不阻塞
printf("HELLLO ");
sleep(rand()%3);
printf("WORLD\n");
pthread_mutex_unlock(&mutex); //5. 解锁 ++1 +1
sleep(rand()%3);
}
pthread_join(tid,NULL); // 子线程死循环,杀不死,pthread_cancle(tid)
pthread_mutex_destroy(&mutex); // 6. 释放锁, 用完了,销毁掉
return 0;
}
为什么会产生死锁:
1. 对同一份资源锁2次
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
pthread_mutex_t mutex; // 1.定义互斥锁 , 使用方式,按照序号
void* tfn(void * arg){
srand(time(NULL));
while(1){
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand()%3); //
printf("world\n");
pthread_mutex_lock(&mutex); // 对同一个互斥量加锁2次
sleep(rand()%3); //
}
}
int main(){
pthread_t tid;
srand(time(NULL));
pthread_mutex_init(&mutex,NULL); //3. 初始化互斥锁,可以理解初始值为1
// init 完了, mutex=1 , mutex是一个结构体,完了方便理解,当做1
int ret=pthread_create(&tid,NULL,tfn,NULL);
if(ret !=0){
printf("%s\n",strerror(ret));
exit(1);
}
while(1){
pthread_mutex_lock(&mutex); // 4.加锁--1 0,如果锁被其他线程占用了,那么会阻塞在这里,等到其他线程释放锁
// try_lock 上面api会阻塞,尝试加锁,加锁失败直接返回错误号,不阻塞
printf("HELLLO ");
sleep(rand()%3);
printf("WORLD\n");
pthread_mutex_unlock(&mutex); //5. 解锁 ++1 +1
sleep(rand()%3);
}
pthread_join(tid,NULL); // 子线程死循环,杀不死,pthread_cancle(tid)
pthread_mutex_destroy(&mutex); // 6. 释放锁, 用完了,销毁掉
return 0;
}
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand()%3); //
printf("world\n");
pthread_mutex_lock(&mutex); // 对同一个互斥量加锁2次 , 不释放, 卡死了
如何避免 : 第二次加锁的时候首先解锁,在加锁
2. 产生条件2, 有2把锁, 有2个共享数据,
线程 t1 持有A 资源, 线程 t2 持有 B 资源, t2 当 拿着 B锁去请求A , t1拿着A去请求 B
就像给给钱给东西, 首先给钱,给东西, 首先给东西,给钱
解决: 第二次访问第二把锁的时候, 不调用lock,调用try-lock ,加锁失败, 等着,放弃已经有的锁,那么别人可以访问你了 (首先都释放,在互相访问)
上面是互斥锁 , 读写锁是一把锁, 既能以读的方式对变量加,又能以为写的方式对变量加,效率ga
原则: 读共享、 写读占、 锁只有一把、写优先级高
读锁: 读方式加锁
写锁:写方式加锁
掌握三条原则:
上图T1 加锁成功, 正在访问, T2 T3 T4 来了, T2 T4 可以加锁,读共享,写独占
上图 : T1 写独占, t2,t3, t4来了,T1 独占 T1写完了,释放内存, T3 首先访问, 写优先级高, 访问完才 T2 T4 读访问
代码实现读写锁:
pthread_rwlock_rdlock: 读锁
pthread_rwlock_wrlock : 写锁
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int counter;
pthread_rwlock_t rwlock; // 读写锁
void* th_write(void* arg){
int t;
int i= (int)arg;
while(1){
pthread_rwlock_wrlock(&rwlock); // 写锁
t= counter;
sleep(1);
printf("======write %d: %lu : counter= %d ++counter=%d\n",i,pthread_self(),t,++counter);
pthread_rwlock_unlock(&rwlock);
sleep(5);
}
}
void * th_read(void* arg){
int i= (int)arg;
while(1){
pthread_rwlock_rdlock(&rwlock); // 读锁
printf("======read %d: %lu : counter= %d \n",i,pthread_self(),counter);
pthread_rwlock_unlock(&rwlock);
sleep(2);
}
}
int main(){
int i;
pthread_t tid[8];
pthread_rwlock_init(&rwlock,NULL);
// 3个写线程
for(i=0;i<3;i++){
pthread_create(&tid[i],NULL,th_write,(void*)i);
}
// 5个读线程
for(i=0;i<5;i++){
pthread_create(&tid[i+3],NULL,th_read,(void*)i);
}
for(i=0;i<8;i++){
pthread_join(tid[i],NULL);
}
// 只有一把锁,直接释放
pthread_rwlock_destroy(&rwlock);
return 0;
}
日志:
1. 对一个锁反复lock
lock mutex=0, 那么 在调用 lock的时候,阻塞,0不能减, 在即没有时间调用解锁了,阻塞在这里
2. 2个线程,各持有一把锁,去请求另外一把
代码实现????
条件 变量相关函数:
pthread_cond_init 函数 初始化 pthread_cond_t
pthread_cond_destory 函数 上面 pthread_cond_t用完了, destory销毁掉
pthread_cond_wait 函数
pthread_cond_wait(pthread_con_t , 互斥锁 );
有3个作用:
1. 等待条件变量满足, 如果条件变量不满足,那么一直在这里等,
2. 释放互斥锁 phread_mutex_unlock
3. 条件变量满足,唤醒, 解除阻塞并重新申请获取互斥锁
pthread_cond_timedwait 函数
阻塞5s, 5s没有唤醒,返回了
pthread_cond_signal 函数
条件变量满足,唤醒阻塞在条件变量的线程,唤醒一个
pthread_cond_broadcast函数
广播,唤醒所有 ,和上面一样
上面6个函数 返回值都是 : 成功 0 失败放回错误号
4.1.1 一个生产者一个消费者
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 链表作为共享数据,需被互斥保护 */
struct msg{
struct msg* next;
int num;
};
struct msg *head;
int all=0;
/* 静态初始化一个条件变量 */
pthread_cond_t has_product=PTHREAD_COND_INITIALIZER;
/* 一个互斥变量 */
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
//静态初始化,等于pthread_cond_init() 这个是动态初始化
// 消费者
void * consumer(void* p){
struct msg* mp;
for(;;){
pthread_mutex_lock(&lock);
// while(head==NULL){
// pthread_cond_wait(&has_product,&lock);
// }
// mp = head;
// head= mp->next;
if(all==0){
// 参数: 条件变量和互斥锁
// 1.阻塞等待条件变量,释放互斥锁lock , 让生产者生产
pthread_cond_wait(&has_product,&lock);
// pthread_cond_timedwait() 设置超是阻塞
// 2. 条件满足了->pthread_cond_signal 唤醒的时候,解除阻塞,唤醒互斥锁
sleep(3);
}
all--;
pthread_mutex_unlock(&lock);
printf("-Consume %lu---- %d\n",pthread_self(),all);;
// free(mp);
sleep(rand()%5);
}
}
void * producer(void* p){
struct msg* mp;
for(;;){
// mp= malloc(sizeof(struct msg));
// mp->num= rand()%100 +1; // 模拟生产一个产品
// printf("---Product ---------------%d\n",mp->num);
//
//
pthread_mutex_lock(&lock);
// mp->next = head;
// head= mp;
all++;
printf("---Product ---------------%d\n",all);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
// 唤醒阻塞在条件变量的至少一个线程
//pthread_cond_broadcast(); 唤醒阻塞在条件变量的所有线程
sleep(rand()%4);
}
}
int main(){
pthread_t pid,cid,cid1;
srand(time(NULL));
pthread_create(&pid,NULL,producer,NULL);
// 多个消费者
pthread_create(&cid,NULL,consumer,NULL);
// pthread_create(&cid1,NULL,consumer,NULL);
pthread_join(pid,NULL);
pthread_join(cid,NULL);
// pthread_join(cid1,NULL);
return 0;
}
4.1.2 一个生产者多个消费者
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 链表作为共享数据,需被互斥保护 */
struct msg{
struct msg* next;
int num;
};
struct msg *head;
int all=0;
/* 静态初始化一个条件变量 */
pthread_cond_t has_product=PTHREAD_COND_INITIALIZER;
/* 一个互斥变量 */
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; //静态初始化,等于pthread_mutex_t
void * consumer(void* p){
struct msg* mp;
for(;;){
pthread_mutex_lock(&lock);
// while(head==NULL){
// pthread_cond_wait(&has_product,&lock);
// }
// mp = head;
// head= mp->next;
if(all==0){
// 参数: 条件变量和互斥锁
// 1.阻塞等待条件变量,释放互斥锁lock
pthread_cond_wait(&has_product,&lock);
// pthread_cond_timedwait() 设置超是阻塞
// 2. 条件满足了->pthread_cond_signal 唤醒的时候,解除阻塞,唤醒互斥锁
sleep(3);
}
all--;
pthread_mutex_unlock(&lock);
printf("-Consume %lu---- %d\n",pthread_self(),all);;
// free(mp);
sleep(rand()%5);
}
}
void * producer(void* p){
struct msg* mp;
for(;;){
// mp= malloc(sizeof(struct msg));
// mp->num= rand()%100 +1; // 模拟生产一个产品
// printf("---Product ---------------%d\n",mp->num);
//
//
pthread_mutex_lock(&lock);
// mp->next = head;
// head= mp;
all++;
printf("---Product ---------------%d\n",all);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
// 唤醒阻塞在条件变量的至少一个线程
//pthread_cond_broadcast(); 唤醒阻塞在条件变量的所有线程
sleep(rand()%4);
}
}
int main(){
pthread_t pid,cid,cid1;
srand(time(NULL));
pthread_create(&pid,NULL,producer,NULL);
// 多个消费者
pthread_create(&cid,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_join(pid,NULL);
pthread_join(cid,NULL);
pthread_join(cid1,NULL);
return 0;
}
出现错误数据:
why 为什么会出现错误数据? 分析
改成If问题: 条件 ( if(all==0) )
如果有多个消费者,那么多个消费值同时执行这段代码,
消费者A,pthread_mutex_lock(&lock); 获取锁, 消费者B阻塞在这把锁上,
wait 释放锁,生产者获取锁生产 all=1, 释放锁
消费A型号获取锁,B阻塞, all=0 , 释放锁,B获取锁, all=0 , pthread_cond_wait 逻辑正确
错误逻辑:
此时消费者B获取锁,那么all=1, all--; pthread_mutex_unlock(&lock); 执行以后,all=0,
在if中的消费者A, 正确的,while(all=0) 继续阻塞等待
那么如果if=0, 往下走 all--, 出现错误数据
4.1.3.正确代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 链表作为共享数据,需被互斥保护 */
struct msg{
struct msg* next;
int num;
};
struct msg *head;
int all=0;
/* 静态初始化一个条件变量 */
pthread_cond_t has_product=PTHREAD_COND_INITIALIZER;
/* 一个互斥变量 */
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; //静态初始化,等于pthread_mutex_t
void * consumer(void* p){
struct msg* mp;
for(;;){
pthread_mutex_lock(&lock);
// while(head==NULL){
// pthread_cond_wait(&has_product,&lock);
// }
// mp = head;
// head= mp->next;
while(all==0){ // 把这里的if改成while,重复检测
// 参数: 条件变量和互斥锁
// 1.阻塞等待条件变量,释放互斥锁lock
pthread_cond_wait(&has_product,&lock);
// pthread_cond_timedwait() 设置超是阻塞
// 2. 条件满足了->pthread_cond_signal 唤醒的时候,解除阻塞,唤醒互斥锁
sleep(3);
}
all--;
pthread_mutex_unlock(&lock);
printf("-Consume %lu---- %d\n",pthread_self(),all);;
// free(mp);
sleep(rand()%5);
}
}
void * producer(void* p){
struct msg* mp;
for(;;){
// mp= malloc(sizeof(struct msg));
// mp->num= rand()%100 +1; // 模拟生产一个产品
// printf("---Product ---------------%d\n",mp->num);
//
//
pthread_mutex_lock(&lock);
// mp->next = head;
// head= mp;
all++;
printf("---Product ---------------%d\n",all);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
// 唤醒阻塞在条件变量的至少一个线程
//pthread_cond_broadcast(); 唤醒阻塞在条件变量的所有线程
sleep(rand()%4);
}
}
int main(){
pthread_t pid,cid,cid1;
srand(time(NULL));
pthread_create(&pid,NULL,producer,NULL);
// 多个消费者
pthread_create(&cid,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_join(pid,NULL);
pthread_join(cid,NULL);
pthread_join(cid1,NULL);
return 0;
}