目录

信号量介绍

信号量与多线程结合的例子

信号量介绍

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。

当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。

信号量的数据类型为结构sem_t,它本质上是一个长整型的数。

函数sem_init()用来初始化一个信号量。它的原型为:

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem为指向信号量结构的一个指针;pshared不为时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。

函数sem_post(sem_t *sem)用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。

函数sem_wait(sem_t *sem)被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。

函数sem_trywait (sem_t *sem)是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。

函数sem_destroy(sem_t *sem)用来释放信号量sem

例子: 利用信号量机制控制多线程的运行顺序,分析多线程中数据的共享情况;

程序源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* File sem.c */
#include<stdio.h>
#include<pthread.h>
#include <semaphore.h>

sem_t sem1;
sem_t sem2;
int i = 0;

void child(){
for(;i<10;i++){
sem_wait(&sem2);
printf("Child thread:%d\n",i);
sem_post(&sem1);
}
}

int main(void){
pthread_t t;
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
pthread_create(&t,NULL,(void *)child,NULL);
for(;i<10;i++){
sem_wait(&sem1);
printf("Main thread:%d\n",i);
sem_post(&sem2);
}
pthread_join(t,NULL);
}

信号量控制多线程的运行顺序的原理很简单,有多少个线程就设置多少个信号量,首先执行的信号量初始值为1,其他信号量初始值为0,所以就算其他线程先占有CPU但也会被阻塞在sem_wait()函数那里,只有信号量为1的那个线程执行完后然后增加下一个信号量的值,下一个信号量对应的线程才能执行,其他线程就算得到CPU也无法执行,然后最后一个线程增加第一个线程的信号量就可以组成一个环,这样子就可以实现多线程之间的顺序执行。

编译运行

1
2
3
4
5
6
7
8
9
10
11
12
13
hylee@ubuntu:~/Desktop$ gcc sem.c -lpthread -o sem
hylee@ubuntu:~/Desktop$ ./sem
Main thread:0
Child thread:1
Main thread:2
Child thread:3
Main thread:4
Child thread:5
Main thread:6
Child thread:7
Main thread:8
Child thread:9
Main thread:10

可以看到主线程和子线程共享全局变量i,当某个线程改变了i的值时,另外一个线程读取是也会读取到修改后的值,即进程中的多个线程共享进程的数据。

Linux系统下多进程与多线程中的区别(通过实验现象可以看出来的):

如果使用的是fork函数实现多进程的编程,对于全局变量i,如果某个进程对i进行了修改,并不会影响另外一个进程i的值(写时复制)。

程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int i = 0;

int main() {
int pid;
pid=fork(); /*创建子进程*/
if(pid == -1){ /*创建失败*/
printf("fork fail!\n");
exit(1);
}else if(pid == 0){
for(int j = 0;j < 10;j++){
i += 2;
printf("Child process:%d\n",i);
}
}else if(pid > 0){
for(int j = 0;j < 10;j++){
i += 1;
printf("Parent process:%d\n",i);
}
wait(NULL);
}
return 0;
}

编译运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hylee@ubuntu:~/Desktop$ gcc fork.c -o fork
hylee@ubuntu:~/Desktop$ ./fork
Parent process:1
Parent process:2
Parent process:3
Parent process:4
Parent process:5
Parent process:6
Parent process:7
Parent process:8
Parent process:9
Parent process:10
Child process:2
Child process:4
Child process:6
Child process:8
Child process:10
Child process:12
Child process:14
Child process:16
Child process:18
Child process:20

可以看到父进程对i进行修改对子进程中的i没有任何影响,反之也一样。

最后修改日期:2020年5月14日

留言

撰写回覆或留言