这篇文档已经过时了,建议转到我主页中的另一篇文章《嵌入式系统原理与应用——基于Linux和ARM笔记整理》,里面有该题更详细的讲解
准备工作
导入库函数
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h> //线程调度
#include <sched.h> //进程调度
#include <sys/mman.h> //共享内存
#include <semaphore.h> //信号量
#include <sys/socket.h> //socket接口
声明所有线程函数
//declare call thread function
void *fun1(void *);
void *fun2(void *);
void *fun3(void *);
void *fun4(void *);
void *fun5(void *);
void *fun6(void *);
3个进程,每个进程中包含2个线程
pid_t pid1,pid2,pid3;
int ret;
pid1=fork();
printf("pid1:%d ",getpid());
printf("the return value:%d\n",pid1);
pthread_t tid1,tid2;
ret = pthread_create(&tid1,NULL,fun1,NULL);
ret = pthread_create(&tid2,NULL,fun2,NULL);
pid2=fork();
printf("pid2:%d ",getpid());
printf("the return value:%d\n",pid2);
pthread_t tid3,tid4;
ret = pthread_create(&tid3,NULL,fun3,NULL);
ret = pthread_create(&tid4,NULL,fun4,NULL);
pid3=fork();
printf("pid3:%d ",getpid());
printf("the return value:%d\n",pid3);
pthread_t tid5,tid6;
ret = pthread_create(&tid5,NULL,fun5,NULL);
ret = pthread_create(&tid6,NULL,fun6,NULL);
每个线程函数的内部如下:
void *fun1(void *arg)
{
printf("pthread_tid1 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
void *fun2(void *arg)
{
printf("pthread_tid2 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
void *fun3(void *arg)
{
printf("pthread_tid3 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
void *fun4(void *arg)
{
printf("pthread_tid4 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
void *fun5(void *arg)
{
printf("pthread_tid5 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
void *fun6(void *arg)
{
printf("pthread_tid6 = %lu\n",(unsigned long)pthread_self());
pthread_exit(NULL);
}
采用共享内存、套接字、信号量等通信方式实现进程间的通信、同步、互斥操作
互斥锁API
互斥量变量一般申请全局变量
pthread_mutex_t mutex;
int num=0; //公共临界变量
修改线程的函数,使fun1与fun2的线程使用互斥锁,即使用共享内存的形式进行同步
void *fun1(void *arg)
{
pthread_mutex_lock(&mutex); //加锁
printf("pthread_tid1 = %lu\n",(unsigned long)pthread_self());
sleep(1); //休眠1s
pthread_mutex_unlock(&mutex); //解锁
pthread_exit(NULL); //线程退出 可以使用pthread_join来回收资源
}
void *fun2(void *arg)
{
pthread_mutex_lock(&mutex); //加锁
printf("pthread_tid2 = %lu\n",(unsigned long)pthread_self());
sleep(2); //休眠2s
pthread_mutex_unlock(&mutex); //解锁
pthread_exit(NULL);
}
主函数最后回收所有资源,包括销毁互斥量操作
pthread_join(tid1,NULL);//阻塞回收线程 1
pthread_join(tid2,NULL);//阻塞回收线程 2
pthread_mutex_destroy(&mutex);//销毁互斥量
套接字
套接字主要用于进程间的通信
创建套接字对
int sockfd[2];
// 创建套接字对
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) == -1) {
perror("socketpair");
return 1;
}
父(子)进程关闭子(父)进程的套接字
close(sockfd[1]); // 关闭父进程的套接字
// 子进程接收消息
close(sockfd[0]); // 关闭与父进程通信的套接字
close(sockfd[0]); // 关闭子进程的套接字
// 父进程发送消息给子进程
close(sockfd[1]); // 关闭与子进程通信的套接字
整合代码如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
int main() {
int sockfd[2];
// 创建套接字对
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) == -1) {
perror("socketpair");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程
close(sockfd[1]); // 关闭父进程的套接字
printf("Child process (%d)\n", getpid());
// 子进程接收消息
char buf[256];
ssize_t num_bytes = read(sockfd[0], buf, sizeof(buf));
if (num_bytes == -1) {
perror("read");
} else {
printf("Received message from parent: %s\n", buf);
}
close(sockfd[0]); // 关闭与父进程通信的套接字
return 0;
}
else {
// 父进程
close(sockfd[0]); // 关闭子进程的套接字
printf("Parent process (%d)\n", getpid());
// 父进程发送消息给子进程
char msg[] = "Hello from parent process";
ssize_t num_bytes = write(sockfd[1], msg, sizeof(msg));
if (num_bytes == -1) {
perror("write");
}
close(sockfd[1]); // 关闭与子进程通信的套接字
return 0;
}
}
运行结果
信号量
注意 互斥锁API 章节中的代码使用sleep函数勉强来控制线程的执行顺序,但这显然不是“长久之计”
信号量则可以帮助控制执行的顺序
申请信号量,一般都是全局变量
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
sem_t sem1,sem2,sem3;//申请的三个信号量变量
在主函数中初始化信号量,然后创建线程
int ret;
pthread_t tid1,tid2;
ret = sem_init(&sem1,0,1); //初始化信号量1 并且赋予其资源
相应地,为了使用信号量进行线程的同步,需要修改线程函数。
void *fun1(void *arg)
{
sem_wait(&sem1);//因sem1本身有资源,所以不被阻塞 获取后sem1-1 下次会会阻塞
printf("pthread_tid1 = %lu\n",(unsigned long)pthread_self());
sem_post(&sem2);// 使得sem2获取到资源
pthread_exit(NULL);
}
void *fun2(void *arg)
{
sem_wait(&sem2);//因sem2在初始化时无资源会被阻塞,直至14行代码执行 不被阻塞 sem2-1 下次会阻塞
printf("pthread_tid2 = %lu\n",(unsigned long)pthread_self());
sem_post(&sem1);// 使得sem1获取到资源
pthread_exit(NULL);
}
由此,更改sem_post里的参数地址,就可以实现控制线程执行顺序的目的
同样在主函数最后,需要回收线程资源和销毁信号量
/*回收线程资源*/
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
/*销毁信号量*/
sem_destroy(&sem1);
sem_destroy(&sem2);
简易版,只控制两个线程的代码整合如下:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
sem_t sem1,sem2,sem3;//申请的三个信号量变量
void *fun1(void *arg)
{
sem_wait(&sem1);//因sem1本身有资源,所以不被阻塞 获取后sem1-1 下次会会阻塞
printf("pthread_tid1 = %lu\n",(unsigned long)pthread_self());
sem_post(&sem2);// 使得sem2获取到资源
pthread_exit(NULL);
}
void *fun2(void *arg)
{
sem_wait(&sem2);//因sem2在初始化时无资源会被阻塞,直至14行代码执行 不被阻塞 sem2-1 下次会阻塞
printf("pthread_tid2 = %lu\n",(unsigned long)pthread_self());
sem_post(&sem1);// 使得sem1获取到资源
pthread_exit(NULL);
}
int main(){
int ret;
pthread_t tid1,tid2;
ret = sem_init(&sem1,0,1); //初始化信号量1 并且赋予其资源
ret = sem_init(&sem2,0,1); //初始化信号量2 并且赋予其资源
ret = sem_init(&sem3,0,1); //初始化信号量1 并且赋予其资源
ret = pthread_create(&tid1,NULL,fun1,NULL);
ret = pthread_create(&tid2,NULL,fun2,NULL);
/*回收线程资源*/
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
/*销毁信号量*/
sem_destroy(&sem1);
sem_destroy(&sem2);
}
运行结果