赞
踩
Linux和windows进程同步与线程同步那些事儿(一)
Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例
Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例
Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步
Linux和windows进程同步与线程同步那些事儿(五):Linux下进程同步
在Linux中,进程同步可以通过多种机制来实现,其中最常见的包括信号量(semaphore)、共享内存(shared memory)、管道(pipe)、消息队列(message queue)和文件锁(file lock)等。
Semaphore
):信号量是一种经典的进程同步机制,它可以用于控制对共享资源的访问。
在Linux中,可以使用sem_t
类型的信号量来实现进程同步。
在Linux下,可以使用信号量实现多进程之间的同步。信号量是一个计数器,用于多个进程之间共享资源的同步操作。
下面是一个使用信号量进行多处理器同步的C++代码示例:
#include <iostream> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> // 定义信号量的键值 #define KEY 123456 // 定义信号量的个数 #define NUM_SEMS 1 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { // 创建信号量 int semid = semget(KEY, NUM_SEMS, IPC_CREAT | 0666); if (semid == -1) { std::cerr << "Failed to create semaphore" << std::endl; return 1; } // 初始化信号量 union semun arg; arg.val = 0; if (semctl(semid, 0, SETVAL, arg) == -1) { std::cerr << "Failed to initialize semaphore" << std::endl; return 1; } // 创建子进程 pid_t pid = fork(); if (pid == -1) { std::cerr << "Failed to fork process" << std::endl; return 1; } else if (pid == 0) { // 子进程和父进程通过信号量进行同步操作 // P操作,等待信号量计数器大于0 struct sembuf sop; sop.sem_num = 0; sop.sem_op = -1; sop.sem_flg = 0; if (semop(semid, &sop, 1) == -1) { std::cerr << "Failed to perform P operation" << std::endl; return 1; } // 输出一段文字 std::cout << "Child process output" << std::endl; // V操作,增加信号量计数器 sop.sem_op = 1; if (semop(semid, &sop, 1) == -1) { std::cerr << "Failed to perform V operation" << std::endl; return 1; } } else { // 父进程和子进程通过信号量进行同步操作 // 输出一段文字 std::cout << "Parent process output" << std::endl; // V操作,增加信号量计数器 struct sembuf sop; sop.sem_num = 0; sop.sem_op = 1; sop.sem_flg = 0; if (semop(semid, &sop, 1) == -1) { std::cerr << "Failed to perform V operation" << std::endl; return 1; } // P操作,等待信号量计数器大于0 sop.sem_op = -1; if (semop(semid, &sop, 1) == -1) { std::cerr << "Failed to perform P operation" << std::endl; return 1; } } // 删除信号量 if (semctl(semid, 0, IPC_RMID) == -1) { std::cerr << "Failed to remove semaphore" << std::endl; return 1; } return 0; }
在上面的代码示例中,首先使用semget
函数创建了一个信号量集,通过指定键值和信号量的个数来创建。然后使用semctl
函数初始化了信号量的计数器为0。接着通过fork
函数创建了一个子进程。
在子进程中,使用semop
函数进行P操作,即等待信号量计数器大于0;然后输出一段文字;再使用semop
函数进行V操作,即增加信号量计数器。
在父进程中,首先输出一段文字;然后使用semop
函数进行V操作;最后使用semop
函数进行P操作。
这样,父进程和子进程通过信号量的P操作和V操作进行同步,保证了子进程的输出一定在父进程的输出之后。
最后,使用semctl
函数删除了创建的信号量集。
运行代码示例,可以看到父进程先输出一段文字,然后子进程再输出一段文字,证明了通过信号量实现了多进程的同步。
需要注意的是,上述代码示例只使用了一个信号量来进行同步,如果需要更复杂的同步操作,可以使用多个信号量来实现。此外,需要保证在使用信号量之前先创建信号量,并在使用完毕后删除信号量。
Shared Memory
):共享内存允许多个进程访问同一块内存区域,从而实现进程间的数据共享和通信。
在Linux中,可以使用shmget
和shmat
等系统调用来创建和访问共享内存区域。
在Linux下,使用共享内存进行多进程间的同步一般会用到信号量(semaphore
)来进行进程间的互斥和同步操作。下面是一个使用C进行编写的示例代码:
#include <stdio.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/sem.h> #include <unistd.h> // 定义信号量的操作结构体 union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; }; // 定义共享内存的大小 #define SHM_SIZE 1024 // 定义信号量操作函数 int sem_op(int semid, int sem_num, int op) { struct sembuf semop = {sem_num, op, SEM_UNDO}; return semop(semid, &semop, 1); } int main() { key_t key; int shmid, semid; char *shm, *s; // 创建key值 key = ftok(".", 'S'); if (key == -1) { perror("ftok error"); exit(1); } // 创建共享内存 shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666); if (shmid == -1) { perror("shmget error"); exit(1); } // 关联共享内存 shm = shmat(shmid, NULL, 0); if (shm == (char *)-1) { perror("shmat error"); exit(1); } // 创建信号量 semid = semget(key, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget error"); exit(1); } // 初始化信号量 union semun sem_val; sem_val.val = 1; if (semctl(semid, 0, SETVAL, sem_val) == -1) { perror("semctl error"); exit(1); } // 多进程同步 pid_t pid = fork(); if (pid == -1) { perror("fork error"); exit(1); } else if (pid == 0) { // 子进程 // P 操作 if (sem_op(semid, 0, -1) == -1) { perror("sem_op error"); exit(1); } // 操作共享内存 s = shm; for (char c = 'a'; c <= 'z'; c++) { *s++ = c; usleep(10000); } // V 操作 if (sem_op(semid, 0, 1) == -1) { perror("sem_op error"); exit(1); } // 分离共享内存 if (shmdt(shm) == -1) { perror("shmdt error"); exit(1); } exit(0); } else { // 父进程 // P 操作 if (sem_op(semid, 0, -1) == -1) { perror("sem_op error"); exit(1); } // 读取并输出共享内存 s = shm; while (*s != '\0') { putchar(*s++); usleep(10000); } putchar('\n'); // V 操作 if (sem_op(semid, 0, 1) == -1) { perror("sem_op error"); exit(1); } // 删除共享内存 if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl error"); exit(1); } // 删除信号量 if (semctl(semid, IPC_RMID, 0) == -1) { perror("semctl error"); exit(1); } exit(0); } return 0; }
这段代码通过创建共享内存和信号量来实现多进程间的同步,其中子进程将字母逐个写入共享内存,父进程从共享内存中读取并输出字母。
首先,使用ftok
函数创建一个唯一的key值,用于共享内存和信号量的标识。然后,使用shmget
函数创建共享内存,指定大小为SHM_SIZE
。接着,使用shmat
函数关联共享内存并返回一个指向共享内存的指针。再然后,使用semget
函数创建一个信号量,并使用semctl
函数初始化信号量的值为1。
接下来,使用fork
函数创建一个子进程。在子进程中,使用sem_op
函数进行P操作(信号量减1),然后通过指针s
操作共享内存,将字母逐个写入共享内存。最后,使用sem_op
函数进行V操作(信号量加1),然后使用shmdt
函数分离共享内存。
在父进程中,使用sem_op
函数进行P操作,然后通过指针s
从共享内存中读取并输出字母,直到遇到结束符’\0’。然后,使用sem_op
函数进行V操作。最后,使用shmctl
函数删除共享内存,使用semctl
函数删除信号量。
以下是一个使用共享内存实现多进程同步的示例代码,分为A和B两个程序:
程序A:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/shm.h> #include <sys/wait.h> #define SHM_KEY 1234 #define SHM_SIZE 256 int main() { // 创建共享内存 int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666); if (shmid == -1) { perror("shmget"); exit(1); } // 连接共享内存 char* shared_memory = (char*)shmat(shmid, NULL, 0); if (shared_memory == (char*)-1) { perror("shmat"); exit(1); } // 写入数据到共享内存 sprintf(shared_memory, "Hello, World!"); // 创建子进程 pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(1); } else if (pid == 0) { // 子进程等待一段时间 sleep(1); // 父子进程间同步,等待父进程写入数据 while (shared_memory[0] == '\0') { sleep(1); } // 读取并打印共享内存中的数据 printf("Data in shared memory: %s\n", shared_memory); // 断开连接共享内存 if (shmdt(shared_memory) == -1) { perror("shmdt"); exit(1); } exit(0); } else { // 等待子进程结束 wait(NULL); // 断开连接共享内存 if (shmdt(shared_memory) == -1) { perror("shmdt"); exit(1); } // 删除共享内存 if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl"); exit(1); } } return 0; }
程序B:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/shm.h> #define SHM_KEY 1234 #define SHM_SIZE 256 int main() { // 获取共享内存的ID int shmid = shmget(SHM_KEY, SHM_SIZE, 0666); if (shmid == -1) { perror("shmget"); exit(1); } // 连接共享内存 char* shared_memory = (char*)shmat(shmid, NULL, 0); if (shared_memory == (char*)-1) { perror("shmat"); exit(1); } // 等待父进程写入数据 while (shared_memory[0] == '\0') { sleep(1); } // 读取并打印共享内存中的数据 printf("Data in shared memory: %s\n", shared_memory); // 断开连接共享内存 if (shmdt(shared_memory) == -1) { perror("shmdt"); exit(1); } return 0; }
以上代码展示了一个简单的多进程同步示例。程序A创建了共享内存并将数据写入其中,然后创建了一个子进程程序B。程序B连接到共享内存,等待程序A写入数据后读取并打印。程序A和程序B通过共享内存进行了数据的同步。
注意代码中的关键步骤:
shmget
函数创建共享内存,指定一个唯一的键值和大小。shmat
函数连接共享内存,获取指向共享内存的指针。shmdt
函数断开与共享内存的连接。shmctl
函数删除共享内存。这样,两个进程就能够实现通过共享内存进行数据同步了。
管道是一种单向的通信机制,可以用于实现具有父子关系的进程间通信。
在Linux中,可以使用pipe系统调用来创建管道。
在Linux下,使用管道进行多进程同步一般会用到父子进程间的通信机制。下面是一个使用C进行编写的示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main() { int fd[2]; pid_t pid; char buf[100]; // 创建管道 if (pipe(fd) == -1) { perror("pipe error"); exit(1); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork error"); exit(1); } else if (pid == 0) { // 子进程 close(fd[0]); // 关闭读端 // 在子进程中向管道写入数据 char *str = "Hello from child process!"; write(fd[1], str, strlen(str) + 1); printf("Child process writes to pipe: %s\n", str); close(fd[1]); // 关闭写端 exit(0); } else { // 父进程 close(fd[1]); // 关闭写端 // 在父进程中从管道读取数据 read(fd[0], buf, sizeof(buf)); printf("Parent process reads from pipe: %s\n", buf); close(fd[0]); // 关闭读端 exit(0); } return 0; }
这段代码通过创建管道来实现父子进程间的数据传输和同步,其中子进程向管道写入数据,父进程从管道读取数据并输出。
首先,使用pipe
函数创建一个管道,返回的fd
数组包含两个文件描述符,fd[0]
用于读取数据,fd[1]
用于写入数据。
接下来,使用fork
函数创建一个子进程。在子进程中,关闭fd[0]
读端,使用write
函数向管道写入数据,然后关闭fd[1]
写端。
在父进程中,关闭fd[1]
写端,使用read
函数从管道中读取数据,然后输出到屏幕上。最后,关闭fd[0]
读端。
以上就是使用管道进行多进程同步的C代码示例及详细讲解。通过在父子进程间传输数据进行同步,父进程从管道中读取数据时会阻塞,直到子进程写入数据到管道中。这样就实现了简单的多进程同步。
消息队列允许进程之间通过消息进行通信,可以实现进程间的异步通信。
在Linux中,可以使用msgget
、msgsnd
和msgrcv
等系统调用来创建和操作消息队列。
在Linux下,可以使用消息队列来实现多进程之间的同步。消息队列是一种进程间通信的方式,可以在不同进程之间传递数据。下面是一个示例,展示了如何使用消息队列实现多进程同步。
程序A:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define MSG_SIZE 128 // 定义消息结构体 struct msg_buffer { long msg_type; char msg_text[MSG_SIZE]; }; int main() { key_t key; int msg_id; struct msg_buffer message; // 生成唯一的key key = ftok("msg_queue_example", 65); // 创建消息队列,如果已经存在则打开 msg_id = msgget(key, 0666 | IPC_CREAT); if (msg_id == -1) { perror("msgget"); return -1; } // 接收来自程序B的消息 msgrcv(msg_id, &message, MSG_SIZE, 1, 0); printf("Received message: %s\n", message.msg_text); // 向程序B发送消息 message.msg_type = 2; sprintf(message.msg_text, "Hello from A"); msgsnd(msg_id, &message, sizeof(message), 0); printf("Message sent: %s\n", message.msg_text); // 删除消息队列 msgctl(msg_id, IPC_RMID, NULL); return 0; }
程序B:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define MSG_SIZE 128 // 定义消息结构体 struct msg_buffer { long msg_type; char msg_text[MSG_SIZE]; }; int main() { key_t key; int msg_id; struct msg_buffer message; // 生成唯一的key key = ftok("msg_queue_example", 65); // 连接到消息队列 msg_id = msgget(key, 0666 | IPC_CREAT); if (msg_id == -1) { perror("msgget"); return -1; } // 向程序A发送消息 message.msg_type = 1; sprintf(message.msg_text, "Hello from B"); msgsnd(msg_id, &message, sizeof(message), 0); printf("Message sent: %s\n", message.msg_text); // 接收来自程序A的消息 msgrcv(msg_id, &message, MSG_SIZE, 2, 0); printf("Received message: %s\n", message.msg_text); return 0; }
在程序A中,首先调用ftok
函数生成一个唯一的key,用于创建消息队列。然后使用msgget
函数创建或打开一个消息队列,如果队列已经存在,则打开;如果队列不存在,则创建一个新的队列。然后使用msgrcv
函数接收来自程序B的消息,并打印接收到的消息内容。接下来,向程序B发送一个消息,使用msgsnd
函数。最后,使用msgctl
函数删除消息队列。
在程序B中,也是首先调用ftok
函数生成一个唯一的key,用于连接到消息队列。然后使用msgget
函数连接到消息队列。接下来,向程序A发送一个消息,使用msgsnd
函数。然后使用msgrcv
函数接收来自程序A的消息,并打印接收到的消息内容。
这个示例展示了两个进程之间如何使用消息队列进行通信和同步。程序A和程序B分别使用不同的消息类型作为消息的标志,以便接收和发送不同类型的消息。msgrcv
和msgsnd
函数用于接收和发送消息。
文件锁可以用于控制对文件的访问,从而实现进程间的同步。
在Linux中,可以使用fcntl系统调用来对文件进行加锁和解锁操作。
在Linux下,可以使用文件锁(fcntl)来实现多进程之间的同步。文件锁可以用于进程间共享的文件,通过对文件进行加锁和解锁,可以实现进程之间的互斥访问。下面是一个示例,展示了如何使用文件锁实现多进程同步。
程序A:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { int fd; struct flock lock; // 打开共享文件 fd = open("shared_file.txt", O_RDWR | O_CREAT, 0666); if (fd == -1) { perror("open"); return -1; } // 加锁 lock.l_type = F_WRLCK; // 写锁 lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; // 锁定整个文件 if (fcntl(fd, F_SETLKW, &lock) == -1) { perror("fcntl"); return -1; } printf("A: File locked\n"); sleep(5); // 模拟处理时间 // 解锁 lock.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &lock) == -1) { perror("fcntl"); return -1; } printf("A: File unlocked\n"); return 0; }
程序B:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { int fd; struct flock lock; // 打开共享文件 fd = open("shared_file.txt", O_RDWR | O_CREAT, 0666); if (fd == -1) { perror("open"); return -1; } // 加锁 lock.l_type = F_WRLCK; // 写锁 lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; // 锁定整个文件 if (fcntl(fd, F_SETLKW, &lock) == -1) { perror("fcntl"); return -1; } printf("B: File locked\n"); sleep(5); // 模拟处理时间 // 解锁 lock.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &lock) == -1) { perror("fcntl"); return -1; } printf("B: File unlocked\n"); return 0; }
在程序A中,首先使用open
函数打开共享文件,如果文件不存在则创建。然后定义一个struct flock
结构体用于加锁和解锁。接下来,设置锁的类型为写锁,通过fcntl
函数的F_SETLKW
参数来加锁,并指定锁定的范围为整个文件。等待一段时间(这里用sleep(5)
模拟处理时间)。最后,设置锁的类型为解锁,通过fcntl
函数来解锁。
程序B的逻辑与程序A类似,首先使用open
函数打开共享文件,然后设置锁的类型为写锁,通过fcntl
函数的F_SETLKW
参数来加锁,并指定锁定的范围为整个文件。等待一段时间,最后解锁。
这个示例展示了两个进程之间如何使用文件锁实现同步。进程A和进程B都对共享文件进行加锁,当一个进程已经持有锁时,另一个进程会在fcntl
函数上阻塞,直到持有锁的进程释放锁。这样保证了进程A和进程B可以交替地对共享资源进行访问,实现了同步。
既然说到了文件锁,有必要在此强调下读锁、写锁和读写锁,对于不同的应用场景,合理选择文件锁,可以优化程序执行效率。
在Linux中,文件锁(fcntl)包括读锁和写锁两种类型,以及读写锁(pthread_rwlock)。
读锁(Shared Lock):
写锁(Exclusive Lock):
读写锁(Read-Write Lock):
下面是一个使用文件锁和读写锁的示例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <pthread.h> // 文件锁示例 void fileLockExample() { int fd; struct flock lock; // 打开文件 fd = open("shared_file.txt", O_RDWR | O_CREAT, 0666); if (fd == -1) { perror("open"); return; } // 加写锁 lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fd, F_SETLKW, &lock) == -1) { perror("lock"); return; } printf("File locked\n"); sleep(5); // 模拟处理时间 // 解锁 lock.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &lock) == -1) { perror("unlock"); return; } printf("File unlocked\n"); close(fd); } // 读写锁示例 pthread_rwlock_t rwlock; void* reader(void* arg) { pthread_rwlock_rdlock(&rwlock); printf("Reader: Reading shared resource\n"); sleep(3); // 模拟读取时间 pthread_rwlock_unlock(&rwlock); printf("Reader: Finished reading\n"); return NULL; } void* writer(void* arg) { pthread_rwlock_wrlock(&rwlock); printf("Writer: Writing to shared resource\n"); sleep(3); // 模拟写入时间 pthread_rwlock_unlock(&rwlock); printf("Writer: Finished writing\n"); return NULL; } void rwLockExample() { pthread_t readerThread1, readerThread2, writerThread; pthread_rwlock_init(&rwlock, NULL); // 创建读者线程 pthread_create(&readerThread1, NULL, reader, NULL); pthread_create(&readerThread2, NULL, reader, NULL); // 创建写者线程 pthread_create(&writerThread, NULL, writer, NULL); // 等待线程结束 pthread_join(readerThread1, NULL); pthread_join(readerThread2, NULL); pthread_join(writerThread, NULL); pthread_rwlock_destroy(&rwlock); } int main() { printf("File Lock Example:\n"); fileLockExample(); printf("\nRead-Write Lock Example:\n"); rwLockExample(); return 0; }
在示例中,fileLockExample
函数展示了如何使用fcntl
函数实现文件锁。首先打开共享文件,在加锁之前设置锁的类型为写锁。然后,通过fcntl
函数的F_SETLKW
参数来加锁,使用F_UNLCK
参数来解锁。
rwLockExample
函数展示了如何使用读写锁(pthread_rwlock
)实现多线程同步。首先初始化读写锁,然后创建读者线程和写者线程。读者线程使用pthread_rwlock_rdlock
函数加读锁,写者线程使用pthread_rwlock_wrlock
函数加写锁。最后,使用pthread_rwlock_unlock
函数解锁,并销毁读写锁。
文件锁和读写锁总结:
linux进程间同步可以使用信号量、共享内存、管道、消息队列和文件锁等机制。下面是它们的使用场景及优缺点的比较:
1. 信号量:
2. 共享内存:
3. 管道:
4. 消息队列:
5. 文件锁:
综上所述,不同的机制适用于不同的场景。选择适当的进程间同步机制取决于具体的需求和限制。信号量和共享内存适用于高性能数据共享和同步,管道适用于具有父子关系的进程通信,消息队列适用于异步通信,文件锁适用于文件的互斥访问。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。