线程简要概述:
1.编译连接的时候使用 -pthread
2.LWP:是线程号,标识线程身份,交给CPU用的,划分时间轮片
线程ID:是进程对自己内部线程加以区别的标识。两个进程中,线程号允许相同
typedef unsigned long int pthread_t;
1.pthread_create()
1.int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create()用于创建线程
thread: 接收创建的线程的 ID
attr: 指定线程的属性 不设置写NULL是默认,不是说没有
start_routine: 指定线程函数
arg: 给线程函数传递的参数 (不设置写NULL) 就是参数3函数的参数
成功返回 0, 失败返回错误码
2.pthread_exit()
将单个线程退出
void pthread_exit(void *retval); 参数:retval 表示线程退出状态,通常传 NULL
3.pthread_self ()
获取线程 ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void);返回值:成功:0; 失败:无!
线程 ID:pthread_t 类型,本质:在 Linux 下为无符号整数(%lu),其他系统中可能是结构体实现
线程 ID 是进程内部,识别标志。(两个进程间,线程 ID 允许相同)
注意:不应使用全局变量 pthread_t tid,在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用pthread_self。
4.pthread_join()
阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
参数:thread:线程 ID (【注意】:不是指针);retval:存储线程结束状态。
对比记忆:
进程中:main 返回值、exit 参数-->int;等待子进程结束 wait 函数参数-->int *
线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **
5.pthread_detach()
实现线程分离
int pthread_detach(pthread_t thread); 成功:0;失败:错误号
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而
直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资
源仍存于系统中,导致内核认为该进程仍存在。
也可使用 pthread_create 函数参 2(线程属性)来设置线程分离。
6.pthread_cancle()
杀死(取消)线程 其作用,对应进程中 kill() 函数。
int pthread_cancel(pthread_t thread); 成功:0;失败:错误号
注意:cancel 它有时候可以杀死线程,有些情况却不能。
cancel 终止线程 必须要到达一个取消点才行,不像之前进程里面的信号,信号的优先级比程序执行的优先级高
必须要先去处理信号。现在cancle 杀死线程,必须要有一个契机进内核,也就是必须要到达一个取消点。
不管是printf、还是sleep ,都有底层系统调用,一调用就进内核了,也就是说到达了自己的取消点。
有了这个取消点就可以把这个线程杀死。
取消点(保存点):是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat,open,pause,close,read,write..... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
(printf、sleep )
可粗略认为一个系统调用(进入内核)即为一个取消点 。如线程中没有取消点,可以通过调用 pthread_testcancel()函数自行设置一个取消点。
被取消的线程, 退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。因此当我们对一个已经被取消的线程使用 pthread_join
回收时,得到的返回值为-1
控制原语对比
进程 线程
fork pthread_create
exit pthread_exit
wait pthread_join
kill pthread_cancel
getpid pthread_self 命名空间
演示:循环创建多个子进程
注意:
1.在创建线程时,若连续多次创建线程,并且要将特定参数传给线程时,可能会出现地址上的旧值还未成功赋予,地址上的旧值就被新值代替
#怎么解决?
#在创建线程后等待一段时间,确保值被成功赋予2.线程之间共享全局变量
3.线程中,禁止使用 exit 函数,会导致进程内所有线程全部退出。使用 pthread_exit 函数 体面退出
#exit 是退出进程,直接就把进程给结束了
#return 是返回到函数调用者那里去 (如果在主线程用则相当于终止进程)4.各个线程退出(包括主线程),不影响其他线程
pthread.cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define SIZE 5
int val=0;
void*Pthread_fun(void*arg)
{
int i=*((int*)arg)+1;
if(i==2) { printf("2号线程退出辣 !\n"); pthread_exit(NULL);} // 1. use: exit(0) ? 2. use : return ?
printf("I'am pthread :pthreadid is %u and pid is %d :number is %d\n",pthread_self(),getpid(),i);
switch(i){ // test allround variable
case 1: { val=100;printf("val is %d\n",val);} break;
case 2: { val=200;printf("val is %d\n",val);} break;
case 3: { val=300;printf("val is %d\n",val);} break;
case 4: { val=400;printf("val is %d\n",val);} break;
}
}
int main(int argc,char*argv[])
{
printf("本程序用于演示循环创建多个子线程 \n\n");
printf("init val :%d \n",val);
int i=0;
char*reval=NULL;
pthread_t pthreadid;
for(;i<SIZE;i++) //create 5 pthread
{
if( pthread_create(&pthreadid,NULL,Pthread_fun,&i) != 0 ) {printf("thread error\n"); return 0; }
sleep(1); //this where must sleep
}
printf("I am main_pthread pid is %d pthreadid is %u \n",getpid(),pthreadid);
printf("laster val :%d \n",val);
return 0;
}
疑问:(指针变量不能直接用?? 必须要有空间,用malloc申请,才可以存数据?)
演示:终止线程的方式 和 retval 非空用法
终止线程方式:
总结:终止某个线程而不终止整个进程,有三种方法:
1. 从线程主函数 return。这种方法对主控线程不适用,从 main 函数 return 相当于调用 exit。
2. 一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。
3. 线程可以调用 pthread_exit 终止自己retval 非空用法:
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止。thread 线程以不同的方法终止,通过 pthread_join
得到的终止状态是不同的,总结如下:
1. 如果 thread 线程通过 return 返回,retval 所指向的单元里存放的是 thread 线程函数的返回值。
2. 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉,retval 所指向的单元里存放的是常数PTHREAD_CANCELED。
3. 如果 thread 线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。
4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL 给 retval 参数
pthread_eixt_join.cpp
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include <unistd.h>
void* Pthread_fun_1(void*arg) // return 退出
{
printf("Pthread_fun_1 :我正在奔跑\n");
return (void*)111;
}
void* Pthread_fun_2(void*arg) //pthread_exit 退出
{
printf("Pthread_fun_2 :我正在奔跑\n");
pthread_exit((void*)222);
}
void* Pthread_fun_3(void*arg) //被 pthread_cancel 杀死
{
while(1)
{
printf("Pthread_fun_3 :我正在奔跑\n");
sleep(1);
//void pthread_testcancel(void);自己加取消点
}
return (void*)666;
}
int main()
{
printf("\n本程序演示用不同的退出方式测试join参数retval的内容 \n");
printf("未判断各个函数的返回值 \n\n");
pthread_t pthreadid=-1;
void*tret=0;
//这里的类型转换 没有错 ? 8字节的指针 转成 4字节的 int型 ?
pthread_create(&pthreadid,NULL,Pthread_fun_1,NULL);
pthread_join(pthreadid,&tret);
printf("Pthread_fun_1 exit tret : %d \n",(int)tret); //tret: 111
pthread_create(&pthreadid,NULL,Pthread_fun_2,NULL);
pthread_join(pthreadid,&tret);
printf("Pthread_fun_2 exit tret : %d \n",(int)tret); // tret: 222
pthread_create(&pthreadid,NULL,Pthread_fun_3,NULL); sleep(3); // 3 秒后杀死它
pthread_cancel(pthreadid);
pthread_join(pthreadid,&tret); //被cancle 杀死的进程任然可以回收
printf("Pthread_fun_3 exit tret : %d \n",(int)tret); // tret:默认值: PTHREAD_CANCELED。(-1)
return 0;
}
疑问:类型转换有问题
演示:线程分离 和 处理线程的返回值
使用strerror(int errnum):它会直接errnum 进行翻译,返回一个字符串给你,让你知道具体的含义
在处理返回值时,之前一直都是用 perror(),实际上它在底层会访问errornumber,把errornumber全局变量进行翻译,翻译成字符串,在和str拼接。
但是在线程里面这些没用了。
线程是怎么做的?
线程直接把错误号返回回来了
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void * Pthread_fun(void*arg)
{
printf("I,am runing \n");
printf("pthread:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
return NULL;
}
int main()
{
printf("\n本程序用于测试pthread_detach 和 处理返回值\n\n");
pthread_t pthreadid=-1;
int ret=-1;
ret=pthread_create(&pthreadid,NULL,Pthread_fun,NULL);
if(ret!=0) { fprintf(stderr,"pthread_create error: %s\n",strerror(ret)); exit(0); } //这样处理会知道数字代表什么含义
pthread_detach(pthreadid); // 设置线程分离,线程终止,会自动清理pcb
sleep(1);
ret=pthread_join(pthreadid,NULL); //pthreadid 被回收了 ,所以显示 无效的参数
printf("join ret=%d \n",ret); // cat ret :22
if(ret!=0) { fprintf(stderr,"pthread_detach error: %s\n",strerror(ret)); exit(0); } // Invalid argument
printf("main:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
pthread_exit(NULL);
}
线程属性
之前我们讨论的线程都是采用 线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需 要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
最主要的作用:设置线程的分离状态
虽然detach也可以设置分离,但是每次创建线程,多了,回收比较麻烦,一开始就把它的分离属性给修改。
线程属性的初始化
注意:应先初始化线程属性,再 pthread_create 创建线程
初始化线程属性
int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号
销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号
线程属性的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当 pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该
根据自己的需要,选择适当的分离状态。
线程分离状态的函数:
设置线程属性,分离 or 非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
获取程属性,分离 or 非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数: attr:已初始化的线程属性
detachstate: PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
演示:设置线程属性,来设置线程分离
pthread_set_attr.cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void * Pthread_fun(void*arg)
{
printf("I,am runing \n");
printf("pthread:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
return NULL;
}
int main()
{
printf("\n本程序用于演示设置线程属性,来设置线程分离\n\n");
pthread_t pthreadid=-1;
int ret=-1;
pthread_attr_t attr;
ret=pthread_attr_init(&attr); //初始化attr
if(ret != 0) { fprintf(stderr,"pthread_attr_init error :%s \n",strerror(ret)); exit(0); }
ret=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //设置线程属性为分离状态
if(ret!=0) { fprintf(stderr,"pthread_attr_setdetachstate error: %s\n",strerror(ret)); exit(0); }
ret=pthread_create(&pthreadid,&attr,Pthread_fun,NULL); //创建线程
if(ret!=0) { fprintf(stderr,"pthread_create error: %s\n",strerror(ret)); exit(0); }
ret=pthread_attr_destroy(&attr); //销毁 attr
if(ret != 0) { fprintf(stderr,"pthread_attr_destroy error :%s \n",strerror(ret)); exit(0); }
ret=pthread_join(pthreadid,NULL); //通过join函数 也可以判断线程是否是分离属性 如果失败就是分离的;
if(ret != 0) { fprintf(stderr,"pthread_attr_join error :%s \n",strerror(ret)); exit(0); }
printf("main:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
pthread_exit(NULL);
}
线程使用注意事项
1. 主线程退出其他线程不退出,主线程应调用 pthread_exit
2. 避免僵尸线程
pthread_join
pthread_detach
pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3. malloc 和 mmap 申请的内存可以被其他线程释放、
(malloc 和 mmap 申请出来就是地址,线程之间地址是共享的,所以彼此之间可以释放)
4. 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程
中均 pthread_exit
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
(如果有多个线程,来了一个信号,默认情况下是谁抢到谁处理,如果想指定其中的哪一个处理
,每个线程有各自独立的屏蔽字,但是它们共享信号的处理方式,未决信号集是共享的) ??
线程安全--线程同步
同步:同步即协同步调,按预定的先后次序运行。
出现线程安全时:
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
一、互斥锁 mutex:
Linux 中提供一把互斥锁 mutex(也称之为互斥量)。
1.pthread_mutex_init 函数
初始化一个互斥锁(互斥量) ---> 初值可看作 1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参 1:传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过除本指针以外的其他变量或指针修改
参 2:互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)。 参 APUE.12.4 同步属性
1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接
使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
2.pthread_mutex_destroy 函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.pthread_mutex_lock 函数
加锁。可理解为将 mutex--(或 -1),操作后 mutex 的值为 0。
int pthread_mutex_lock(pthread_mutex_t *mutex);
4.pthread_mutex_unlock 函数
解锁。可理解为将 mutex ++(或 +1),操作后 mutex 的值为 1。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.pthread_mutex_trylock 函数
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
lock 加锁失败会阻塞,等待锁释放。
trylock 加锁失败直接返回错误号(如:EBUSY),不阻塞。
演示:给线程加互斥锁
在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。
(锁持有的时间越短越好)
pthread_mutex.cpp
#include <stdlib.h>
#include <assert.h>
pthread_mutex_t mutex; // 创建互斥锁
void *tfn(void *arg)
{
srand(time(NULL));
while (1) {
if(pthread_mutex_lock(&mutex) != 0) { printf("pthread_mutex_lock error \n"); return 0; } // P
printf("hello ");
sleep(rand() % 3); /*模拟长时间操作共享资源,导致 cpu 易主,产生与时间有关的错误*/
printf("world\n");
sleep(rand() % 3);
if(pthread_mutex_unlock(&mutex) !=0 ){ printf("pthread_mutex_unlock error \n"); return 0; } //V
sleep(rand() % 3); // 加上给CPU进程切换的时间,不会使其他线程饥饿
}
return NULL;
}
int main(void)
{
printf("\n本程序用于演示加互斥锁来控制 大小写一次性输出\n\n");
pthread_t tid;
pthread_mutex_init(&mutex,NULL); //init mutex :为1
srand(time(NULL));
int ret= pthread_create(&tid, NULL, tfn, NULL); assert(ret==0);
while (1) {
if(pthread_mutex_lock(&mutex) != 0) { printf("pthread_mutex_lock error \n"); return 0; } // P
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD\n");
sleep(rand() % 3);
if(pthread_mutex_unlock(&mutex) !=0 ){ printf("pthread_mutex_unlock error \n"); return 0; } // V
sleep(rand() % 3);
}
pthread_join(tid, NULL);
pthread_mutex_destroy(&mutex); //销毁互斥量
return 0;
}
从上面程序演示产生死锁的情况:
1. 线程试图对同一个互斥量 A 加锁两次
2. 线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁
二、读写锁
介绍:
读写锁也叫共享-独占锁
锁只有一把 !!
以读的方式给数据加锁:读锁
以写的方式给数据加锁:写锁用的不多
特性:
读共享,写独占
如果读锁和写锁一起争,写锁优先级高读写锁非常适合于对数据结构读的次数远大于写的情况。
1.pthread_rwlock_init 函数
初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参 2:attr 表读写锁属性,通常使用默认属性,传 NULL 即可。
2.pthread_rwlock_destroy 函数
销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
3.pthread_rwlock_rdlock 函数
以读方式请求读写锁。(常简称为:请求读锁)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
4.pthread_rwlock_wrlock 函数
以写方式请求读写锁。(常简称为:请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
5.pthread_rwlock_unlock 函数
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
6.pthread_rwlock_trywrlock 函数
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
7.pthread_rwlock_tryrdlock 函数
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
演示:多个线程对同一全局数据读、写操作
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int counter=0; //全局变量,用于观察变化
pthread_rwlock_t rwlock;
/* 3 个线程不定时写同一全局资源,5 个线程不定时读同一全局资源 */
void *th_write(void *arg)
{
int t=0;
int i=*((int*)arg);
while (1) {
pthread_rwlock_wrlock(&rwlock); //以写的方式请求加读写锁
t = counter; //把写之前的值记录,进行对比
usleep(1000); //这是独占,不会失去CPU
printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
pthread_rwlock_unlock(&rwlock); //解读写锁
usleep(10000);
}
return NULL;
}
void *th_read(void *arg)
{
int i=*((int*)arg);
while (1) {
pthread_rwlock_rdlock(&rwlock); //以读的方式请求加读写锁
printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
pthread_rwlock_unlock(&rwlock); //解读写锁
usleep(2000);
}
return NULL;
}
int main(void)
{
printf("\n本程序用于演示读写锁,函数未判断返回值 \n\n");
int i;
pthread_t tid[8];
pthread_rwlock_init(&rwlock, NULL);
for (i = 0; i < 3; i++)
pthread_create(&tid[i], NULL, th_write,&i);
for (i = 0; i < 5; i++)
pthread_create(&tid[i+3], NULL, th_read,&i);
for (i = 0; i < 8; i++)
pthread_join(tid[i], NULL);
pthread_rwlock_destroy(&rwlock); //销毁读写锁
return 0;
}
从中可以分析出读写锁的特性
三、条件变量
介绍:条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
优点:相较于 mutex 而言,条件变量可以减少竞争。 (不太理解)
如直接使用 mutex ,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚 (链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
1.pthread_cond_init 函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.pthread_cond_destroy 函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
3.pthread_cond_wait 函数
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
1. 阻塞等待条件变量 cond(参 1)满足
2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
3. 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
注意:也就是说在这之前隐含了
第三个参数是mutex ,结合互斥锁一起使用。
使用这个函数:
1.得要先定义互斥锁
2.而且初始化互斥锁
3.而且还隐含了已经完成了加锁操作!!!
4.pthread_cond_timedwait 函数
限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec
*restrict abstime);
参 3: 参看 man sem_timedwait 函数,查看 struct timespec 结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参 abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参 APUE.11.6 线程同步条件变量小节
在讲解 setitimer 函数时我们还提到另外一种时间类型:
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
5.pthread_cond_signal 函数
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
阻塞等待的线程怎么样知道等待阻塞的条件满足了??、
就是靠这个函数来通知
6.pthread_cond_broadcast 函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
演示用条件变量实现生产者消费者模型
生产者消费者模型
消费者: 生产者
1.创建锁、条件变量 1.生产数据
2.初始化锁 2.加锁
3.加锁 3.访问公共区域 (生产数据)
4等待条件满足 4.解锁
1)阻塞等条件变量 5.通知阻塞在条件变量上的线程
2)解锁 6.循环生产后续数据
3)加锁
5.访问共享数据
6.解锁、释放条件变量。释放锁
//借助条件变量模拟 生产者.消费者问题
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
//链表作为共享数据,需被互斥量保护
struct msg {
struct msg *next;
int num;
};
struct msg *head;
//静态初始化 一个条件变量 和 一个互斥量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p)
{
struct msg *mp;
for (;;) {
pthread_mutex_lock(&lock); //加互斥锁
while (head == NULL) { //头指针为空,说明没有节点 可以为 if 吗
pthread_cond_wait(&has_product, &lock); //阻塞等待
}
mp = head;
head = mp->next; //模拟消费掉一个产品
pthread_mutex_unlock(&lock);
printf("-Consume ---%d\n", mp->num);
free(mp);
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
while (1) {
mp = (struct msg *)malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1; //模拟生产一个产品
printf("-Produce ---%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程唤醒
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
四、信号量
介绍:进化版的互斥锁(1 --> N)它可以有多个初值: 信号量的初值,决定了占用信号量的线程的个数。优势: (什么意思)由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办 法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
1.sem_init 函数
sem_init 函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参 1:sem 信号量
参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
参 3:value 指定信号量初值
2.sem_destroy 函数
销毁一个信号量
int sem_destroy(sem_t *sem);
3.sem_wait 函数
给信号量加锁 --
int sem_wait(sem_t *sem);
4.sem_post 函数
给信号量解锁 ++
int sem_post(sem_t *sem);
5.sem_trywait 函数
尝试对信号量加锁 -- (与 sem_wait 的区别类比 lock 和 trylock)
int sem_trywait(sem_t *sem);
6.sem_timedwait 函数
限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参 2:abs_timeout 采用的是绝对时间。
定时 1 秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
t.tv_nsec = t.tv_sec +100;
sem_timedwait(&sem, &t); 传参
借助信号量实现生产者消费者模型
实现思路:用两个信号量用来表示产品数 和 空格数
然后进行制约同步协调
#define NUM 5
int queue[NUM]={0}; //共享数据区
sem_t blank_number; //信号量 1
sem_t product_number; //信号量 2
void* producer(void*arg)
{
int i=0;
int ret=-1;
while(1)
{
if( sem_wait(&blank_number) !=0 ) { printf("producer sem_wait error\n") ; return 0;} //P blank_number
queue[i]=rand()%1000+1;
printf("---produce---%d \n",queue[i]);
if( sem_post(&product_number) != 0) { printf("producer sem_post errpr \n") ; return 0; } //V product_number
i=(i+1)%NUM;
sleep(rand()%1);
}
}
void* consumer(void*arg)
{
int i=0;
int ret=-1;
while(1)
{
if( sem_wait(&product_number) !=0 ) { printf("consumer sem_wait error\n") ; return 0;} //P blank_number
printf("--consumer--%d \n",queue[i]);
queue[i]=0;
if( sem_post(&blank_number) != 0) { printf("consumer sem_post errpr\n") ; return 0; } //V product_number
i=(i+1)%NUM;
sleep(rand()%3);
}
}
int main()
{
printf("\n本程序演示用信号量实现生产者消费者模型 \n");
pthread_t pro_pthreadid=-1;
pthread_t con_pthreadid=-1;
if( sem_init(&blank_number,0,NUM) !=0) {printf("sem_init error\n"); return 0;};
if( sem_init(&product_number,0,NUM) !=0) {printf("sem_init error\n"); return 0;};
if( pthread_create(&pro_pthreadid,NULL,producer,NULL) !=0) { printf("pthread_create error\n"); return 0; }
if( pthread_create(&pro_pthreadid,NULL,consumer,NULL) !=0) { printf("pthread_create error\n"); return 0; }
if( pthread_join(con_pthreadid,NULL) != 0) {printf("pthread_join error\n"); return 0;}
if( pthread_join(pro_pthreadid,NULL) != 0) {printf("pthread_join error\n"); return 0;}
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;
}
疑问:段错误