
共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,比如前面说到的信号量。
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h中。
使用共享内存的方法是:
首先要用的函数是shmget,它获得一个共享存储标识符。
#include <sys/types.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>
int shmget(key_t key, int size, int
flag);
这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。
当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
void *shmat(int shmid, void *addr, int flag);
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。
共享内存机制使得两个不相关进程可以读取和修改同一段内存从而实现数据共享。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <string.h>
int main(int argc,char **argv)
{
int shm_id;
pid_t pid;
char *paddr,*caddr;
if(argc != 2)
{
printf("please enter string\n");
exit(1);
}
if((shm_id = shmget(IPC_PRIVATE,1024,0666)) < 0)
{
perror("shmget");
exit(1);
}
if((pid = fork()) < 0)
{
perror("fork");
exit(1);
}
if(pid > 0) //父进程
{
paddr = shmat(shm_id,0,0); //映射
memset(paddr,'\0',1024); //清空
strncpy(paddr,argv[1],1024); //放数据到共享内存
wait();
exit(0);
}
else //子进程
{
sleep(1);
caddr = shmat(shm_id,0,0);
printf("child printf %s\n",caddr);
exit(0);
}
}

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/shm.h> #include <unistd.h> #include <sys/ipc.h> #include <string.h> int main(int argc,char **argv) { int shm_id; pid_t pid; char *paddr,*caddr; if(argc != 2) { printf("please enter string\n"); exit(1); } if((shm_id = shmget(IPC_PRIVATE,1024,0666)) < 0) { perror("shmget"); exit(1); } system("ipcs -m"); //查看共享内存状态 if((pid = fork()) < 0) { perror("fork"); exit(1); } if(pid > 0) //父进程 { paddr = shmat(shm_id,0,0); //映射 memset(paddr,'\0',1024); //清空 strncpy(paddr,argv[1],1024); //放数据到共享内存 system("ipcs -m"); //查看共享内存状态 if(shmdt(paddr) < 0) { perror("paddr shmdt"); exit(1); } else { printf("parent deattach share-memory\n"); } system("ipcs -m"); //查看共享内存状态 wait(); exit(0); } else //子进程 { sleep(1); caddr = shmat(shm_id,0,0); printf("child printf %s\n",caddr); system("ipcs -m"); //查看共享内存状态 if(shmdt(caddr) < 0) { perror("caddr shmdt"); exit(1); } else { printf("child deattach share-memory\n"); } system("ipcs -m"); //查看共享内存状态 if(shmctl(shm_id,IPC_RMID,NULL) < 0) { perror("shmctl"); exit(1); } else { printf("delete share-memory\n"); } exit(0); } }
在之前的代码基础上添加了部分操作,然后运行结果如下:(nattch在不停变哟)

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。Linux的消息队列(queue)实质上是一个链表, 它有消息队列标识符(queue ID). msgget创建一个新队列或打开一个存在的队列; msgsnd向队列末端添加一条新消息; msgrcv从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息.
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与信号量和共享内存相似。消息队列, 信号量和共享内存, 都属于内核中的IPC结构, 它们都用标识符来描述. 这个标识符是一个非负整数, 与文件描述符不同的是, 创建时并不会重复利用通过删除回收的整数, 而是每次+1, 直到整数最大值回转到0。标识符是IPC对象的内部名, 而它的外部名则是key(键), 它的基本类型是key_t, 在头文件<sys/types.h>中定义为长整型。键由内核变换成标识符。
创建/打开消息队列:
msgget函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t, key, int msgflg);
返回值: 成功则返回消息队列ID, 出错则返回-1.
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。
key是IPC_PRIVATE。
key当前未与特定类型的IPC结构相结合, 并且msgflg中指定了IPC_CREAT位。
数据放到消息队列:
调用msgsnd将数据放到消息队列中。
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);返回值: 成功则返回0, 出错则返回-1。
说 明: 可以定义一个消息结构, 结构中带类型, 这样就可用非先进先出顺序取消息了。当msgsnd成功返回, 与消息队列相关的msqid_ds结构得到更新, 以标明发出该调用的进程ID(msg_lsqid), 进行该调用的时间(msg_stime), 并指示队列中增加了一条消息(msg_qnum).
消息队列中取消息:
调用msgrcv将从消息队列中取消息.
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
返回值: 成功则返回消息的数据部分的长度, 出错则返回-1.
参数:
msg_ptr: 指向一个长整型数(返回的消息类型存放在其中), 跟随其后的是存放实际消息数据的缓冲区.
msg_st: 数据缓冲区的长度. 若返回的消息大于msg_st, 且在msgflg中设置了MSG_NOERROR, 则该消息被截短.
msgtype:
msgtype == 0: 返回队列中的第一个消息.
msgtype > 0: 返回队列中消息类型为type的第一个消息.
msgtype < 0: 返回队列中消息类型值小于或等于type绝对值的消息, 如果这种消息有若干个, 则取类型值最小的消息.
说明: 当msgrcv成功返回时, 与消息队列相关的msqid_ds结构被更新, 以指示调用者的进程ID, 调用时间和队列中的消息数减1.
msgctl函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int command, struct msgid_ds *buf);
返回值: 成功则返回0, 出错则返回-1.
参数: command参数说明对msqid指定的队列要执行的命令:
IPC_STAT: 取此队列的msqid_ds结构, 并将它存放在buf指向的结构中.
IPC_SET: 按由buf指向结构中的值, 设置与此队列相关结构中的msg_perm.uid, msg_perm.gid, msg_perm.mode和msg_qbytes. 该命令只有下列两种进程可以执行:
有效用户ID等于msg_perm.cuid或msg_per.uid.
具有超级用户特权的进程.
IPC_RMID: 从系统中删除该消息队列以及仍在该队列中的所有数据. 执行权限同上。
发送部分代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <string.h> #include <sys/msg.h> //int msgget(key_t key, int msgflg); //int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); #define MAXSIZE 512 struct msgBuf { long msgType; char msg_text[MAXSIZE]; }; int main() { key_t key; int qid; struct msgBuf msg; if((key = ftok(".",'a')) < 0) { perror("ftok"); exit(1); } if((qid = msgget(key,IPC_CREAT | 0666)) < 0) //获取或打开一个消息队列 { perror("msgget"); exit(1); } printf("open queue %d\n",qid); while(1) { printf("please enter message to the queue:\n"); if(fgets(msg.msg_text,MAXSIZE,stdin) == NULL) { printf("no message\n"); exit(1); } msg.msgType = getpid(); if(msgsnd(qid,&msg,strlen(msg.msg_text),0) < 0) { perror("message snd"); exit(1); } if(strncmp(msg.msg_text,"quit",4) == 0) { break; } } exit(0); }
接收部分代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <string.h> #include <sys/msg.h> //int msgget(key_t key, int msgflg); //int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); #define MAXSIZE 512 struct msgBuf { long msgType; char msg_text[MAXSIZE]; }; int main() { key_t key; int qid; struct msgBuf msg; if((key = ftok(".",'a')) < 0) { perror("ftok"); exit(1); } if((qid = msgget(key,IPC_CREAT | 0666)) < 0) //获取或打开一个消息队列 { perror("msgget"); exit(1); } printf("open queue %d\n",qid); while(1) { memset(msg.msg_text,0,MAXSIZE); if(msgrcv(qid,(void*)&msg,MAXSIZE,0,0) < 0) { perror("message rcv"); exit(1); } printf("the message from %d:%s\n",msg.msgType,msg.msg_text); if(strncmp(msg.msg_text,"quit",4) == 0) { break; } } if((msgctl(qid,IPC_RMID,NULL)) < 0) { perror("msgctl"); exit(1); } exit(0); }
运行的结果是:
在最初运行接收部分时,碰到过一个错误,报错:Argument list too long,上网找到了问题出在哪里了。报错Argument list too long错在使用msgsnd和msgrcv函数的时候,size_t msgsz 这个msgsz的大小双方不一致,msgsnd的长度大于了msgrcv的长度,所以就导致报错:Argument list too long

十七、进程间通信总结
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET。
管道包括三种:1、普通管道PIPE, 通常有种限制,一是半双工,只能单向传输;二是只能在父子进程间使用. 2流管道s_pipe去除了第一种限制,可以双向传输。 3命名管道name_pipe, 去除了第二种限制,可以在许多并不相关的进程之间进行通讯。系统IPC的三种方式类同,都是使用了内核里的标识符来识别。
管道的使用方法与文件类似,都能使用read,write,open等普通IO函数. 管道描述符来类似于文件描述符. 事实上, 管道使用的描述符, 文件指针和文件描述符最终都会转化成系统中SOCKET描述符. 都受到系统内核中SOCKET描述符的限制. 本质上LINUX内核源码中管道是通过空文件来实现。管道: 优点是所有的UNIX实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除;缺陷是管道只允许单向传输或者用于父子进程之间。系统IPC: 优点是功能强大,能在毫不相关进程之间进行通讯; 缺陷是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制。
无名管道文件(单工通信,只能从一端到令一端)
#include <unistd.h>
int pipe(int filedes[2]);
函数功能:
产生一对文件描述符,其中filedes[0]用于读,filedes[1]用于写.
工作流程:
1.定义一组或多组描述符
2.利用描述符创建匿名管道文件
3.通过每组的读写属性在父子进程间建立通信
有名管道文件
#include<sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数功能:通过管道文件名和创建属性创建管道文件
工作流程:
1.在其中一个进程中创建管道文件
2.在两个进程中都打开该文件
3.一个进程读一个进程写
消息队列
#include<sys/msg.h>
#include<sys/ipc.h>
1.msgget函数说明
int msgget(key_t key,int flags)
flags分创建与打开
创建:IPC_CREAT|EXCL
打开:0
2.msgsnd函数说明
int msgsnd(int msgid
const void *msg,//消息缓冲
size_t size,//消息大小
int flags//消息发送方式
)
发送消息的长度是消息的长度,不是结构体的长度,结构体的第一个元素是系统用来维护队列的。
3.msgrcv函数说明
ssize_t msgrcv(int msqid,
void *msgp,//自定义结构体
size_t msgsz, //返回缓冲的空间大小(结构体第二个数据的大小)
long msgtyp,//和msgp里面的第一个参数相对应
int msgflg//
);
4.int msgctl(int msqid, int cmd, struct msqid_ds *buf);
使用方法和共享内存中的一致
共享内存
#include<sys/shm.h>
#include<sys/ipc.h>
1.shmget函数说明
int shmget(
key_t key,//唯一决定共享内存ID的生成
size_t size,//创建共享内存的大小
int flags//创建共享内存的方式与权限
flags:
);
id是根据key产生的
返回共享内存ID
filetokey把文件路径转化成Key
key_t ftok(const char*filepath,int projid);
filepath:文件路径名,因为在某个路径下文件是唯一的所以产生的key也是唯一的。
projid:微调作用,可能这个路径下有几个程序同时用了这个路径,就需要用不同的projid来区分这几个不同的程序产生不同的key。
2.shmat函数说明
void * shmat(int shmid,
const void *shmaddr, //NULL系统指定
int shmflg//指定挂载方式IPC_RDONLY或者0读写
)
返回:挂载成功返回内存地址,0就为失败
3.shmdt函数说明
int shmdt(const void *shmaddr);
4.shmctl函数说明
作用:控制(删除|获取共享内存的状态|修改共享内存状态的值)共享内存
int shmctl(int shmid,
int cmd,
struct shmid_ds *buf//只对IPC_STAT|IPC_SET有意义
);
信号量
#include<sys/ipc.h>
#include<sys/sem.h>
1.semget函数说明
int semget(key_t key,
int nsems,//创建的信号量的个数
int sem_flags//创建/打开信号量IPC_CREAT|IPC_EXCL,0
)
2.semop函数说明
int semop(int semid,
struct sembuf *op,//对信号量的操作
unsigned int nsops//第二个结构体数组的个数
)
3.semctl函数说明
删除ID
修改信号量
获取信号量
修改信号量状态
int semctl(int semid,
int index,//信号量下标
int cmd,//信号量的操作命令
... //对应命令的相关操作
)

十八、Linux多线程编程
1.Linux进程与线程
Linux进程创建一个新线程时线程将拥有自己的栈因为线程有自己的局部变量但与它的创建者共享全局变量、文件描述符、信号句柄和当前目录状态。Linux通过fork创建子进程与创建线程之间是有区别的fork创建出该进程的一份拷贝这个新进程拥有自己的变量和自己的PID它的时间调度是独立的它的执行几乎完全独立于父进程。进程可以看成一个资源的基本单位而线程是程序调度的基本单位一个进程内部的线程之间共享进程获得的时间片。
2._REENTRANT宏
在一个多线程程序里默认情况下只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中这些函数通常用一个单独的全局性区域来缓存输出数据。为解决这个问题需要使用可重入的例程。可重入代码可以被多次调用而仍然工作正常。编写的多线程程序通过定义宏_REENTRANT来告诉编译器我们需要可重入功能这个宏的定义必须出现于程序中的任何#include语句之前。
_REENTRANT为我们做三件事情并且做的非常优雅
1它会对部分函数重新定义它们的可安全重入的版本这些函数名字一般不会发生改变只是会在函数名后面添加_r字符串如函数名gethostbyname变成gethostbyname_r。
2stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。
3在error.h中定义的变量error现在将成为一个函数调用它能够以一种安全的多线程方式来获取真正的errno的值。
3.线程的基本函数
大多数pthread_XXX系列的函数在失败时并未遵循UNIX函数的惯例返回-1这种情况在UNIX函数中属于一少部分。如果调用成功则返回值是0如果失败则返回错误代码。
1.线程创建
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void
*arg);
参数说明
thread指向pthread_create类型的指针用于引用新创建的线程。
attr用于设置线程的属性一般不需要特殊的属性所以可以简单地设置为NULL。
*(*start_routine)(void *)传递新线程所要执行的函数地址。
arg新线程所要执行的函数的参数。
调用如果成功则返回值是0如果失败则返回错误代码。
2.线程终止 #include <pthread.h>
void pthread_exit(void *retval);
参数说明
retval返回指针指向线程向要返回的某个对象。
线程通过调用pthread_exit函数终止执行并返回一个指向某对象的指针。注意绝不能用它返回一个指向局部变量的指针因为线程调用该函数后这个局部变量就不存在了这将引起严重的程序漏洞。
3.线程同步
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
参数说明
th将要等待的张璐线程通过pthread_create返回的标识符来指定。
thread_return一个指针指向另一个指针而后者指向线程的返回值。
#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <string.h> char str[] = "hello world"; void *pthreadFunc(void *arg); /* int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); */ int main() { pthread_t pthread; //线程标志 int res; void *pthread_result; res = pthread_create(&pthread,NULL,pthreadFunc,(void *)str); if(res != 0) { perror("pthread_creadte error\n"); exit(EXIT_FAILURE); } printf("waiting..\n"); res = pthread_join(pthread,&pthread_result); //等待 if(res != 0) { perror("pthread_join error\n"); exit(EXIT_FAILURE); } printf("str --> %s\n",str); printf("pthread return --> %s\n",pthread_result); exit(0); } void *pthreadFunc(void *arg) { printf("gogogo...%s\n",str); sleep(3); strcpy(str,"eepw-->liklon"); pthread_exit("好久不见!\n"); }

如果不加-lpthread会出现undefined reference to 'pthread_create'问题
问题原因:
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
问题解决:
在编译中要加 -lpthread参数
gcc thread.c -o thread -lpthread
thread.c为你些的源文件,不要忘了加上头文件#include<pthread.h>

接下来是编写一个程序验证两个线程的执行是同时进行的。如果是在一个单处理器系统上线程的同时执行就需要靠CPU在线程之间的快速切换来实现了。 程序需要利用一个原理即除了局部变量外其他的变量在一个进程中的所有线程之间是共享的。在这个程序中是在两个线程之间使用轮询方式称为忙等待所以它的效率会很低。代码中两个线程会不断的轮询判断flag的值是否满足各自的要求。然后打印信息
#include <stdio.h> #include <stdlib.h> #include <pthread.h> int flag = 1; void *pthreadFunc(void *arg); int main() { pthread_t pthread; int count = 0; int res; void *pthreadResult; res = pthread_create(&pthread,NULL,pthreadFunc,NULL); if(res != 0) { perror("pthread_create error\n"); exit(EXIT_FAILURE); } while(count++ < 10) { if(flag == 1) { printf("-->eepw\n"); flag = 2; } else sleep(1); } printf("wait --> pthread finish\n"); res = pthread_join(pthread,&pthreadResult); if(res != 0) { perror("pthread_join error\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } void *pthreadFunc(void *arg) { int count = 0; while(count++ < 10) { if(flag == 2) { printf("-->liklon\n"); flag = 1; } else sleep(1); } pthread_exit(NULL); }


续上一贴:
在一段代码中采用轮询的方式在两个线程之间不停地切换是非常笨拙且没有效率的实现方式,幸运的是专门有一级设计好的函数为我们提供更好的控制线程执行和访问代码临界区的方法。
本次代码是利用信号量让两个线程同步。
1.信号量创建
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明
sem 信号量对象。
pshared 控制信号量的类型0表示这个信号量是当前进程的局部信号量否则这个信号量就可以在多个进程之间共享。
value 信号量的初始值。
2.信号量控制
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_post的作用是以原子操作的方式给信号量的值加1。
sem_wait的作用是以原子操作的方式给信号量的值减1但它会等到信号量非0时才会开始减法操作。如果对值为0的信号量调用sem_wait这个函数就会等待直到有线程增加了该信号量的值使其不再为0。
3.信号量销毁
#include <semaphore.h>
int sem_destory(sem_t *sem);
这个函数的作用是用完信号量后对它进行清理清理该信号量所拥有的资源。如果你试图清理的信号量正被一些线程等待就会收到一个错误。 与大多数Linux函数一样这些函数在成功时都返回0。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> /* #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); */ sem_t sem; void *pthreadFunc(void *arg); int main() { pthread_t pthread; int count = 0; int res; void *pthreadResult; res = sem_init(&sem,0,1); //信号量初始化 if(res != 0) { perror("sem inint error\n"); exit(EXIT_FAILURE); } res = pthread_create(&pthread,NULL,pthreadFunc,NULL); //创建线程 if(res != 0) { perror("pthread_create error\n"); exit(EXIT_FAILURE); } while(count++ < 10) { sem_wait(&sem); printf("-->eepw\n"); sem_post(&sem); sleep(1); } printf("wait --> pthread finish\n"); res = pthread_join(pthread,&pthreadResult); if(res != 0) { perror("pthread_join error\n"); exit(EXIT_FAILURE); } sem_destroy(&sem); //销毁信号量 exit(EXIT_SUCCESS); } void *pthreadFunc(void *arg) { int count = 0; while(count++ < 10) { sem_wait(&sem); printf("-->liklon\n"); sem_post(&sem); sleep(1); } pthread_exit(NULL); }

此段代码是在上一段代码基础上修改,看起来更加方便。

十九、线程同步--互斥锁
为了让多个线程达到同步的目的,在对于全局变量等大家都要用的资源的使用上,通常得保证同时只能由一个线程在用,一个线程没有宣布对它的释放之前,不能够给其他线程使用这个变量。
1.申请一个互斥锁
pthread_mutex_t mutex; //申请一个互斥锁
你可以声明多个互斥量。
在声明该变量后,你需要调用pthread_mutex_init()来创建该变量。pthread_mutex_init的格式如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
第一个参数,mutext,也就是你之前声明的那个互斥量,第二个参数为该互斥量的属性。属性定义如下:
互斥量分为下面三种:
快速型(PTHREAD_MUTEX_FAST_NP)。这种类型也是默认的类型。该线程的行为正如上面所说的。
递归型(PTHREAD_MUTEX_RECURSIVE_NP)。如果遇到我们上面所提到的死锁情况,同一线程循环给互斥量上锁,那么系统将会知道该上锁行为来自同一线程,那么就会同意线程给该互斥量上锁。
错误检测型(PTHREAD_MUTEX_ERRORCHECK_NP)。如果该互斥量已经被上锁,那么后续的上锁将会失败而不会阻塞,pthread_mutex_lock()操作将会返回EDEADLK。可以通过函数
注意以下语句可以做到将一个互斥锁快速初始化为快速型。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2.销毁一个互斥锁
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
3.上锁(相当于windows下的EnterCriticalSection)
在创建该互斥量之后,你便可以使用它了。要得到互斥量,你需要调用下面的函数:
int pthread_mutex_lock(pthread_mutex_t *mutex);
该函数用来给互斥量上锁。互斥量一旦被上锁后,其他线程如果想给该互斥量上锁,那么就会阻塞在这个操作上。如果在此之前该互斥量已经被其他线程上锁,那么该操作将会一直阻塞在这个地方,直到获得该锁为止。
在得到互斥量后,你就可以进入关键代码区了。
4.解锁(相当于windows下的LeaveCriticalSection)
在操作完成后,你必须调用下面的函数来给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
/*
int pthread_numtex_init()
*/
pthread_mutex_t mutex;
void *pthreadFunc(void *arg);
int main()
{
pthread_t pthread;
int count = 0;
int res;
void *pthreadResult;
res = pthread_mutex_init(&mutex,NULL);
if(res != 0)
{
perror("mutex inint error\n");
exit(EXIT_FAILURE);
}
res = pthread_create(&pthread,NULL,pthreadFunc,NULL); //创建线程
if(res != 0)
{
perror("pthread_create error\n");
exit(EXIT_FAILURE);
}
while(count++ < 10)
{
pthread_mutex_lock(&mutex);
printf("-->eepw\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
printf("wait --> pthread finish\n");
res = pthread_join(pthread,&pthreadResult);
if(res != 0)
{
perror("pthread_join error\n");
exit(EXIT_FAILURE);
}
pthread_mutex_destroy(&mutex); //销毁信号量
exit(EXIT_SUCCESS);
}
void *pthreadFunc(void *arg)
{
int count = 0;
while(count++ < 10)
{
pthread_mutex_lock(&mutex);
printf("-->liklon\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(NULL);
}
回复
有奖活动 | |
---|---|
【EEPW电子工程师创研计划】技术变现通道已开启~ | |
发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
【EEPW在线】E起听工程师的声音! | |
“我踩过的那些坑”主题活动——第001期 | |
高校联络员开始招募啦!有惊喜!! | |
【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
送您一块开发板,2025年“我要开发板活动”又开始了! | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
多组DCTODC电源方案被打赏50分 | |
【我踩过的那些坑】STM32cubeMX软件的使用过程中的“坑”被打赏50分 | |
新手必看!C语言精华知识:表驱动法被打赏50分 | |
【我踩过的那些坑】杜绑线问题被打赏50分 | |
【我踩过的那些坑】STM32的硬件通讯调试过程的“坑”被打赏50分 | |
【我踩过的那些坑】晶振使用的问题被打赏100分 | |
【我踩过的那些坑】电感选型错误导致的处理器连接不上被打赏50分 | |
【我踩过的那些坑】工作那些年踩过的记忆深刻的坑被打赏10分 | |
【我踩过的那些坑】DRC使用位置错误导致的问题被打赏100分 | |
我踩过的那些坑之混合OTL功放与落地音箱被打赏50分 |