3. 利用管道实现互相的发收通信
jack.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <pthread.h>
//有名管道进程间通信
void* read_thread(void* argc){
//第二根管子
if(access("/home/lsf/jincheng_course/fifo2",F_OK)==-1){//fifo2不存在则创建管道
//创建一个有名管道
int ret_fifo2 = mkfifo("/home/lsf/jincheng_course/fifo2",0777);
if(ret_fifo2==-1){
perror("file exist");
}
}
//打开文件
int fd2 = open("/home/lsf/jincheng_course/fifo2",O_RDONLY);
if(fd2==-1){
perror("open myfifo failed");
return NULL;
}
char msg[128];
//从管道2读数据
while(1){
memset(msg,0,sizeof(msg));
read(fd2,msg,sizeof(msg)-1);
printf("[rose说]:%s\n",msg);
if(strcmp(msg,"bye\n")==0){
break;
}
}
close(fd2);
}
int main()
{
//创建一个线程用于从管子2中读取数据
pthread_t tid1;
pthread_create(&tid1,NULL,&read_thread,NULL);
//access检测文件是否存在
if(access("/home/lsf/jincheng_course/fifo1",F_OK)==-1){//fifo1不存在则创建管道
//创建一个有名管道
int ret_fifo1 = mkfifo("/home/lsf/jincheng_course/fifo1",0777);
if(ret_fifo1==-1){
perror("file exist");
}
}
//打开文件
int fd1 = open("/home/lsf/jincheng_course/fifo1",O_WRONLY);
if(fd1==-1){
perror("open myfifo failed");
return -1;
}
char buf[128];//定义缓冲区
//写文件
while(1){
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);//获取键盘输入
write(fd1,buf,sizeof(buf)-1);
if(strcmp(buf,"bye\n")==0){//因为我们输入完bye后还会输入一个回车会被fgets读取到,所以为了避免错我们加一个回车
break;
}
}
close(fd1);
return 0;
}
rose.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <pthread.h>
//有名管道进程间通信
void* write_thread(void* argc){
//第二根管子
if(access("/home/lsf/jincheng_course/fifo2",F_OK)==-1){//fifo2不存在则创建管道
//创建一个有名管道
int ret_fifo2 = mkfifo("/home/lsf/jincheng_course/fifo2",0777);
if(ret_fifo2==-1){
perror("file exist");
}
}
//打开文件
int fd2 = open("/home/lsf/jincheng_course/fifo2",O_WRONLY);
if(fd2==-1){
perror("open myfifo failed");
return NULL;
}
char msg[128];
//往管道2写数据
while(1){
memset(msg,0,sizeof(msg));
fgets(msg,sizeof(msg),stdin);//键盘输入
write(fd2,msg,strlen(msg));
if(strcmp(msg,"bye\n")==0){//因为我们输入完bye后还会输入一个回车会被fgets读取到,所以为了避免错我们加一个回车
break;
}
}
close(fd2);
}
int main()
{
//创建一个线程用于从管子2中读取数据
pthread_t tid1;
pthread_create(&tid1,NULL,&write_thread,NULL);
//access检测文件是否存在,F_OK,R_OK,W_OK,X_OK
if(access("/home/lsf/jincheng_course/fifo1",F_OK)==-1){//fifo1不存在则创建管道
//创建一个有名管道
int ret_fifo1 = mkfifo("/home/lsf/jincheng_course/fifo1",0777);
if(ret_fifo1==-1){
perror("file exist");
}
}
//打开文件
int fd1 = open("/home/lsf/jincheng_course/fifo1",O_RDONLY);
if(fd1==-1){
perror("open myfifo failed");
return -1;
}
char buf[128];
//读文件
while(1){
memset(buf,0,sizeof(buf));//清空
read(fd1,buf,sizeof(buf)-1);
printf("[jack说]:%s\n",buf);
if(strcmp(buf,"bye\n")==0){
break;
}
}
close(fd1);
return 0;
}
4. POSIX无名信号量
4.1 使用
如果我们要解决的是一个进程内部的线程间的同步互斥,那么也许不需要使用有名信号
量,因为这些线程共享同一个内存空间,我们可以定义更加轻量化的、基于内存的 (不在任 何文件系统内部) 无名信号量来达到目的。
这种信号量的使用步骤是:
1,在这些线程都能访问到的区域定义这种变量 (比如全局变量) ,类型是sem_t。 2,在任何线程使用它之前,用 sem_init( )初始化他。
3,使用 sem_wait( )/sem_trywait( )和 sem_post( )来分别进行 P 、V 操作。 4,不再需要时,使用 sem_destroy( )来销毁他。
无名信号量一般用在进程内的线程间,因此 pshared 参数一般都为 0。当将此种信号量 用在进程间时,必须将他定义在各个进程都能访问的地方—— 比如共享内存之中。
对于我们接触过的三种信号量:system-V 信号量和 POSIX 信号量 (named-sem 和 unnamed-sem) ,下面是他们的区别:
1,sys-V 信号量较古老,语法艰涩。POSIX 信号量简单,轻量。
2,sys-V 信号量可以对代表多种资源的多个信号量元素同一时间进行原子性的 P/V 操作,POSIX 信号量每次只能操作一个信号量。
3 ,sys-V 信号量和 named-sem 是系统范围的资源 ,进程消失之后继续存在 ,而 unnamed-sem 是进程范围的资源,随着进程的退出而消失。
4,sys-V 信号量的 P/V 操作可以对信号量元素加减大于 1 的数值,而 POSIX 信号量每 次 P/V 操作都是加减 1。
5 ,sys-V 信号量甚至还支持撤销操作——一个进程对 sys-V 信号量进行 P/V 操作时可
以给该操作贴上需要撤销的标识,那么当进程退出之后,系统会自动撤销那些做了标识的操 作。而 POSIX 信号没有此功能。
6,sys-V 信号量和 named-sem 适合用在进程间同步互斥,而 unamed-sem 适合用在线 程间同步互斥。
总的来说,system-V 的信号量功能强大,强大到臃肿啰嗦,如果在现实工作中不需要 那些高级功能,建议使用接口清晰、逻辑简单的 POSIX 信号量。
posix_no_name.c
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <semaphore.h>
//posix无名信号量的使用,主线程输入字符串,子线程打印个数
sem_t space;
sem_t data;
//统计buf个数的线程
void* count(void* argc){
char* str = (char*)argc;
while(1){
sem_wait(&data);
printf("你输入的字符串个数为:%ld\n",strlen(str)-1);
sem_post(&space);
}
}
int main()
{
//初始化无名信号量
sem_init(&space,0,1);
sem_init(&data,0,0);
char buf[128];
pthread_t tid;
//创建子线程
pthread_create(&tid,NULL,&count,(void*)buf);
while(1){
sem_wait(&space);//p -1操作
memset(buf,0,sizeof(buf));
//获取键盘输入
printf("请输入字符串:");
fgets(buf,sizeof(buf),stdin);
sem_post(&data);//v +1操作
}
return 0;
}
5. 互斥锁与读写锁
如果信号量的值最多为 1,那实际上相当于一个共享资源在任意时刻最多只能有一个线 程在访问,这样的逻辑被称为“互斥”。这时,有一种更加方便和语义更加准确的工具来满 足这种逻辑,他就是互斥锁。
“锁”是一种非常形象的说法:就像一个房间只能住一个人一样,任何人进去之后就把 门锁上了,其他任何人都不能进去,直到进去的那个人重新开开锁,即释放了这个锁资源为 止:
5.1 互斥锁
pthread_mutex_lock(&m);//上锁
pthread_mutex_unlock(&m);//解锁
对互斥锁的操作无非就是:初始化、加锁、解锁、销毁。下面的代码通过展示两条线程 如何使用互斥锁来互斥地访问标准输出,来理解互斥锁的正确使用
huchi_lock.c
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <semaphore.h>
//互斥锁的使用
pthread_mutex_t m;//定义互斥锁
void* count(void* argc){
char* str = (char*)argc;
pthread_mutex_lock(&m);
while(*str!='\0'){
fprintf(stderr,"%c",*str);
// printf("%c",*str);
str++;
}
printf("\n");
pthread_mutex_unlock(&m);
}
int main()
{
pthread_t tid;
pthread_t tid2;
//创建子线程
pthread_create(&tid,NULL,&count,"hello");
pthread_create(&tid2,NULL,&count,"world");
pthread_exit(NULL);
return 0;
}
5.2 压栈、或弹栈线程的取消处理例程
由于线程任何时刻都有可能持有诸如互斥锁、信号量等资源,一旦被取消很有可能导致
别的线程出现死锁,因此如果一条线程的确可能被取消,那么在被取消之前必须使用以下 API 来为将来可能出现的取消请求注册“处理例程”,让这些例程自动释放持有的资源。
lock_and_threadCancel.c
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <semaphore.h>
//演示当一个线程被取消了,留下来的锁该怎么办
pthread_mutex_t m;//定义互斥锁
//压栈、或弹栈线程的取消处理例程
void routine(void* arg){
pthread_mutex_unlock(&m);//解锁
}
void* count(void* argc){
char* str = (char*)argc;
pthread_mutex_lock(&m);//上锁
//这一个步就是将解锁锁备份,防止线程被意外取消后产生错误(因为线程没了,他身上的锁肯定也跟着没了,那就找不到这把锁,也就没办法解锁了)
pthread_cleanup_push(&routine,NULL);
while(*str!='\0'){
fprintf(stderr,"%c",*str);
// printf("%c",*str);
str++;
}
printf("\n");
pthread_mutex_unlock(&m);//解锁
pthread_cleanup_pop(1);//1代表执行备份操作,0代表不执行
}
int main()
{
pthread_mutex_init(&m, NULL);//初始化互斥锁
pthread_t tid;
pthread_t tid2;
//创建子线程
pthread_create(&tid,NULL,&count,"hello");
pthread_create(&tid2,NULL,&count,"world");
pthread_exit(NULL);
pthread_mutex_destroy(&m);//销毁锁
return 0;
}
5.3 读写锁
互斥锁使用非常简便,但他也有不适用的场合——假如要保护的共享资源在绝大多数的 情况下是读操作,就会导致这些本可以一起读的线程阻塞在互斥锁上,资源得不到最大的利 用。
互斥锁的低效率,是因为没有更加细致地区分如何访问共享资源,一刀切地在任何时候 都只允许一条线程访问共享资源,而事实情况是读操作可以同时进行,只有写操作才需要互 斥,因此如果能根据访问的目的——读或者写,来分别加读锁 (可以重复加) 或者写锁 (只 允许一次一个) ,就能就能极大地提高效率 (尤其是存在大量读操作的情况下) 。
相关函数
pthread_rwlock_t rwlock; //定义读写锁
pthread_rwlock_init(&rwlock,NULL); //初始化
pthread_rwlock_wrlock(&rwlock); //上写锁
pthread_rwlock_rdlock(&rwlock); //上读锁
pthread_rwlock_unlock(&rwlock); //释放读写锁
pthread_rwlock_destroy(&rwlock); // 销毁读写锁
rwlock.c
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
//演示读写锁
static pthread_rwlock_t rwlock;//定义读写锁
int global = 0;
void *routine1(void *arg){
// 对共享资源进行写操作之前,必须加写锁 (互斥锁)
pthread_rwlock_wrlock(&rwlock);
global += 1;
printf("I am %s, now global=%d\n", (char *)arg, global);
// 访问完之后释放该锁
pthread_rwlock_unlock(&rwlock);
pthread_exit(NULL);
}
void *routine2(void *arg)
{
// 对共享资源进行写操作之前,必须加写锁 (互斥锁)
pthread_rwlock_wrlock(&rwlock);
global = 100;
printf("I am %s, now global=%d\n", (char *)arg, global);
// 访问完之后释放该锁
pthread_rwlock_unlock(&rwlock);
pthread_exit(NULL);
}
void *routine3(void *arg)
{
// 对共享资源进行读操作之前,可以加读锁 (共享锁)
pthread_rwlock_rdlock(&rwlock);
printf("I am %s, now global=%d\n", (char *)arg, global);
// 访问完之后释放该锁
pthread_rwlock_unlock(&rwlock);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
//对读写锁进行初始化
pthread_rwlock_init(&rwlock,NULL);
// 创建三条线程,对共享资源同时进行读写操作
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, routine1, "thread 1");
pthread_create(&t2, NULL, routine2, "thread 2");
pthread_create(&t3, NULL, routine3, "thread 3");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}