2020-09-12

Linux应用 – 多线程同步介绍

Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量、信号量和读写锁。下面进行详细的讲解:
1 互斥锁
1.1 临界区
在计算机系统中有许多共享资源不允许用户并行使用。例如打印机设备,如果它同时进行两份文档打印,它的输出就会产生交错,从而都无法获得正确的文档。像打印机这样的共享设备被称为“ 排它性资源”,因为它一次只能由一个执行流访问。执行流必须以互斥的方式执行访问排它性资源的代码。
临界区是必须以互斥方式执行的代码段,也就是说在临界区的范围内只能有一个活动的执行线程。
1.2 什么是互斥量
互斥量( Mutex), 又称为互斥锁, 是一种用来保护临界区的特殊变量, 它可以处于锁定( locked) 状态, 也可以处于解锁( unlocked) 状态:
如果互斥锁是锁定的, 就是一个特定的线程持有这个互斥锁;
如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。
每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时,一个线程试图获取这个互斥锁时, 这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时,一个线程试图获取这个互斥锁时,这个线程将阻塞在互斥锁的等待线程队列内。

互斥量是最简单也是最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。另外,互斥量只能被短时间地持有,使用完临界资源后应立即释放锁。

1.3 创建与销毁
1.3.1 创建互斥量
pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步时需要先对它进行初始化,可以静态或动态方式对互斥量进行初始化。
( 1)静态初始化
对是静态分配的 pthread_mutex_t 变量来说值需要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
( 2)动态初始化
对动态分配或者不使用默认互斥属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。动态初始化的操作及参数说明:
*名字
pthread_mutex_init – 互斥变量初始化
用法
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t restrict attr);
mutex – 一个指向要初始化的互斥量的指针;
attr – 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。
描述
pthread_mutex_init ()函数:对动态分配或者不使用默认互斥属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作
返回值
成功:0,失败:返回一个非0的错误码

下表列出 pthread_mutex_init 出错的错误码。
静态初始化程序通常比调用 pthread_mutex_init 更有效,而且在任何线程开始执行之前,确保变量被执行一次。以下代码用来动态的初始化默认属性的互斥量 mylock:

int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_init(&mylock, NULL))
fprintf(stderr, "Failed to initialize mylock : %s\n", strerror(error));

1.3.2 销毁互斥量
销毁互斥量的操作及参数说明:
*名字
pthread_mutex_destroy – 销毁互斥量
用法
int pthread_mutex_destroy(pthread_mutex_t mutex);
mutex – 指向要销毁的互斥量的指针。
描述
pthread_mutex_destroy ()函数:销毁互斥量
返回值
成功:0,失败:返回一个非0的错误码

以下代码销毁了 mylock 互斥量:

int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_destroy(&mylock))
    fprintf(stderr, "Failed to destroy mylock : %s\n", strerror(error));

1.4 加锁与解锁
1.4.1 加锁
线程试图锁定互斥量的过程称之为加锁。
加锁的操作及参数说明:
*名字
pthread_mutex_lock – 锁定互斥量
pthread_mutex_trylock – 尝试加锁
用法
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_trylock (pthread_mutex_t mutex);
mutex – 需要加锁的互斥量。
描述
pthread_mutex_lock()函数会一直阻塞到互斥量可用为止,而 pthread_mutex_trylock()会尝试加锁,通常立即返回
返回值
函数成功返回 0,否则返回一个非 0 的错误码,其中另一个线程已持有锁的情况下,调用 pthread_mutex_trylock()函数是错误码为 EBUSY。

1.4.2 解锁
解锁是线程将互斥量由锁定状态变为解锁状态。以下伪代码展示了互斥量保护临界区的基本用法:

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mylock);
...临界区代码省略...
pthread_mutex_unlock(&mylock);

解锁的操作及参数说明:
*名字
pthread_mutex_unlock – 解锁
用法
int pthread_mutex_unlock (pthread_mutex_t mutex);
mutex – 需要加锁的互斥量。
描述
pthread_mutex_unlock()函数:用来释放指定的互斥量
返回值
函数成功返回 0,否则返回一个非 0 的错误码。只有当线程需要进入临界区之前正确地获取适当的互斥量,并在线程离开临界区时释放互斥量

1.4.3 互斥量例程
下列程序清单是使用互斥量来保证多线程同时输出顺序例子,互斥量保证获取的线程打印完才让别的线程打印,杜绝了打印乱序的问题。使用互斥量保护多线程同时输出。

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
 
pthread_t tid[2];
pthread_mutex_t lock;
 
void* doPrint(void *arg)
{
	int id = (long)arg;
	int i = 0;
	 
	pthread_mutex_lock(&lock); /* 使用互斥量保护临界区 */
	printf("Job %d started\n", id);
	for (i = 0; i < 5; i++) 
	{
		printf("Job %d printing\n", id);
		usleep(10);
	}
	printf("Job %d finished\n", id);
	pthread_mutex_unlock(&lock);
	return NULL;
}
 
int main(void)
{
	long i = 0;
	int err;
 
	if (pthread_mutex_init(&lock, NULL) != 0) /* 动态初始化互斥量 */
	{
		printf("\n Mutex init failed\n");
		return 1;
	}
	
	while(i < 2)
	{
		err = pthread_create(&(tid[i]), NULL, &doPrint, (void*)i);
		if (err != 0)
		printf("Can't create thread :[%s]", strerror(err));
		i++;
	}
	
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	pthread_mutex_destroy(&lock);
 
	return 0;
}

1.5 死锁和避免
1.5.1 什么是死锁
死锁是指两个或两个以上的执行序在执行过程中, 因争夺资源而造成的一种互相等待的现象。例如: 一个线程 T1 已锁定了一个资源 R1, 又想去锁定资源 R2,而此时另一个线程 T2 已锁定了资源 R2,却想去锁定资源R1,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况,如下图所示。
在这里插入图片描述

死锁发生示意图

下面程序清单示例了死锁发生的情况,程序创建了两个线程,第一个线程先获取 mutexA锁,再获取 mutexB 锁;第二个线程先获取 mutexB 后获取 mutexA,这时死锁就可能发生。

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
 
pthread_t tid[2];
pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;
 
void * t1(void *arg) {
	pthread_mutex_lock(&mutexA); /* 线程 1 获取 mutexA */
	printf("t1 get mutexA\n");
	usleep(1000);
	pthread_mutex_lock(&mutexB); /* 线程 1 获取 mutexB */
	printf("t1 get mutexB\n");
	pthread_mutex_unlock(&mutexB); /* 线程 1 释放 mutexB */
	printf("t1 release mutexB\n");
	pthread_mutex_unlock(&mutexA); /* 线程 1 释放 mutexA */
	printf("t1 release mutexA\n");
	return NULL;
}
 
void * t2(void *arg) {
	pthread_mutex_lock(&mutexB);
	printf("t2 get mutexB\n");
	usleep(1000);
	pthread_mutex_lock(&mutexA);
	printf("t2 get mutexA\n");
	pthread_mutex_unlock(&mutexA);
	printf("t2 release mutexA\n");
	pthread_mutex_unlock(&mutexB);
	printf("t2 release mutexB\n");
	return NULL;
}
 
int main(void) {
	int err;
	err = pthread_create(&(tid[0]), NULL, &t1, NULL ); /* 创建线程 1 */
	
	if (err != 0)
		printf("Can't create thread :[%s]", strerror(err));
	err = pthread_create(&(tid[1]), NULL, &t2, NULL); /* 创建线程 2 */
	
	if (err != 0)
		printf("Can't create thread :[%s]", strerror(err));
	
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	return 0;
}

1.5.1 死锁的避免
当多个线程需要相同的一些锁, 但是按照不同的顺序加锁, 死锁就很容易发生, 如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。 例如,规定程序内有三个互斥锁的加锁顺序为 mutexA->mutexB->mutexC,则线程 t1、 t2、 t3 线程操作伪代码如下所示:
在这里插入图片描述

2 条件变量
2.1 为什么需要条件变量
在多线程编程中仅使用互斥锁来完成互斥是不够用的, 如以下情形:假设有两个线程 t1 和 t2, 需要这个两个线程循环对一个共享变量 sum 进行自增操作,那么 t1 和 t2 只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:

pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) {
    pthread_mutex_lock(&sumlock);
    sum++;
    pthread_mutex_unlock(&sumlock);
}

如果这时需要增加另一个线程 t3,需要 t3 在 count 大于 100 时将 count 值重新置 0 值,那么可以 t3 可以实现如下:

void * t3 (void) {
    pthread_mutex_lock(&sumlock);
    if (sum >= 100) {
        sum = 0;
        pthread_mutex_unlock(&sumlock);
    } else {
        pthread_mutex_unlock(&sumlock);
        usleep(100);
    }
}

以上代码存在以下问题:
1) sum 在大多数情况下不会到达 100, 那么对 t3 的代码来说,大多数情况下, 走的是 else分支, 只是 lock 和 unlock,然后 sleep()。 这浪费了 CPU 处理时间。
2) 为了节省 CPU 处理时间, t3 会在探测到 sum 没到达 100 的时候 usleep()一段时间。这样却又带来另外一个问题, 亦即 t3 响应速度下降。 可能在 sum 到达 200 的时候, t3 才会醒过来。
这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。

2.2 创建与销毁
2.2.1 创建条件变量
Pthreads 用 pthread_cond_t 类型的变量来表示条件变量。程序必须在使用 pthread_cond_t 变量之前对其进行初始化。
( 1) 静态初始化
对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
( 2)动态初始化
静态初始化程序通常比调用 pthread_cond_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。动态初始化的操作及参数说明:
*名字
pthread_cond_init – 条件变量初始化
用法
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t restrict attr);
cond – 一个指向需要初始化 pthread_cond_t 变量的指针;
attr – 传递 NULL 值时, pthread_cond_init()将 cond 初始化为默认属性的条件变量。
描述
pthread _cond_init()函数:对动态分配或者不使用默认属性的条件变量来说可以使用 pthread _cond_init()来初始化。
返回值
成功:0,失败:返回一个非0的错误码

以下代码示例了条件变量的初始化:

pthread_cond_t cond;
int error;
if (error = pthread_cond_init(&cond, NULL));
    fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));

2.2.2 销毁条件变量
以下代码演示了如何销毁一个条件变量。

pthread_cond_t cond;
int error;
if (error = pthread_cond_destroy(&cond))
fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));

销毁条件变量的操作及参数说明:
*名字
pthread_cond_destroy – 销毁条件变量
用法
int pthread_cond_destroy(pthread_cond_t cond);
cond – 一个指向pthread_cond_t 变量的指针。
描述
pthread_cond_destroy():用来销毁它参数所指出的条件变量
返回值
成功:0,失败:返回一个非0的错误码

2.3 等待与通知
2.3.1 等待
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。
等待的操作及操作说明:
*名字
pthread_cond_wait – 条件等待;
pthread_cond_timedwait – 等待一段时间
用法
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec restrict abstime);
cond – 一个指向条件变量的指针,
mutex – 一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。
pthread_timedwait()的第三个参数 abstime – 一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出, abstime 是个绝对时间,而不是时间间隔。
描述
pthread_cond_wait()函数在条件不满足时将一直等待, 而 pthread_cond_timedwait()将只等待一段时间。
返回值
成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait() 函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。

以下代码使得线程进入等待,直到收到通知并且满足 a 大于等于 b 的条件。

pthread_mutex_lock(&mutex)
while(a < b)
    pthread_cond_wait(&cond, &mutex)
pthread_mutex_unlock(&mutex)

2.3.2 通知
当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。
通知的操作及操作说明:
*名字
pthread_cond_signal – 条件通知,唤醒一个在条件变量等待队列等待的线程;
pthread_cond_broadcast --条件通知,唤醒所有在条件变量等待队列等待的线程
用法
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t cond);
cond – 一个指向条件变量的指针
描述
pthread_cond_signal 函数:可以唤醒一个在条件变量等待队列等待的线程;
pthread_cond_broadcast函数:可以唤醒所有在条件变量等待队列等待的线程
返回值
成功:0,失败:返回一个非0的错误码

2.3.3 线程范例
条件变量范例

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
 
pthread_t tid[3];
int sum = 0;
pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */
pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 静态初始化条件变量 */
 
void * t1t2(void *arg)
{
	int i;
	long id = (long)arg;
	
	for (i = 0; i < 60; i++) 
	{
		pthread_mutex_lock(&sumlock); /* 使用互斥量保护临界变量 */
		sum++;
		printf("t%ld: read sum value = %d\n", id + 1 , sum);
		pthread_mutex_unlock(&sumlock);
		if (sum >= 100)
			pthread_cond_signal(&cond_sum_ready); /* 发送条件通知,唤醒等待线程 */
	}
	return NULL;
}
void * t3(void *arg) 
{
	pthread_mutex_lock(&sumlock);
	while(sum < 100) /* 不满足条件将一直等待 */
	pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待条件满足 */
	sum = 0;
	printf("t3: clear sum value\n");
	pthread_mutex_unlock(&sumlock);
	return NULL;
}
 
int main(void) 
{
	int err;
	long i;
	for (i = 0; i < 2; i++) 
	{
		err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i); /* 创建线程 1 线程 2 */
		if (err != 0) 
		{
			printf("Can't create thread :[%s]", strerror(err));
		} 
	}
	err = pthread_create(&(tid[2]), NULL, &t3, NULL); /* 创建线程 3 */
	if (err != 0)
		printf("Can't create thread :[%s]", strerror(err));
	for (i = 0; i < 3; i++)
		pthread_join(tid[i], NULL);
	
	return 0;
}

上面程序清单的可能运行结果如下所示, sum 累加到 100 时发送条件通知,但程序结果中 sum 计算到 103 时, t3 才被调用,这是因为 signal 与 wait 调用之间有间隙存在。

  1. 读写锁(同步)
      读写锁与互斥量类似,不过读写锁允许更改的并行性,也叫共享互斥锁。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态:读模式下加锁状态、写模式加锁状态、不加锁状态。
      一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁(允许多个线程读但只允许一个线程写)。
    读写锁的特点:如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作;
    如果有其它线程写数据,则其它线程都不允许读、写操作。
    读写锁的规则:如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。读写锁适合于对数据结构的读次数比写次数多得多的情况。

3.1 初始化读写锁
初始化读写锁的操作及参数说明:
*名字
pthread_rwlock_init --初始化读写锁
用法
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t attr);
rwlock – 表示的是一个读写锁;
attr – 读写锁的属性,一般设置为NULL。
描述
pthread_rwlock_init ()函数:初始化读写锁
返回值
成功:0,失败:返回一个非0的错误码

3.2 申请读/写锁
申请读/写锁的操作及参数说明:
*名字
pthread_rwlock_rdlock – 读锁
pthread_rwlock_wrlock – 获取写锁
pthread_rwlock_unlock – 释放锁
用法
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t rwlock);
rwlock – 指向读写锁的指针。
描述
pthread_rwlock_rdlock():实现获取读锁;
pthread_rwlock_wrlock ():获取写锁;
pthread_rwlock_unlock():释放锁的操作.
获取锁的两个函数是阻塞操作。
返回值
成功:0,失败:返回一个非0的错误码

3.3非阻塞的获取读/写锁
非阻塞的获取读/写锁的操作及参数说明:
*名字
pthread_rwlock_tryrdlock – 非阻塞的获取读锁
pthread_rwlock_trywrlock – 非阻塞的获取写锁
用法
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t rwlock);
rwlock – 指向读写锁的指针。
描述
pthread_rwlock_tryrdlock ()函数:非阻塞的获取读锁;
pthread_rwlock_trywrlock ()函数:非阻塞的获取写锁;
返回值
成功:0,失败:错误:返回EBUSY

3.4销毁读写锁
销毁读写锁的操作及参数说明:
*名字
pthread_rwlock_destroy – 销毁读写锁
用法
int pthread_rwlock_destroy(pthread_rwlock_t rwlock);
rwlock – 指向读写锁的指针。
描述
pthread_rwlock_destroy ()函数:销毁读写锁
返回值
成功:0,失败:返回一个非0的错误码

3.5 读写锁实例
一个使用读写锁来实现 4 个线程读写一段数据是实例。在此示例程序中,共创建了 4 个线程,其中两个线程用来写入数据,两个线程用来读取数据。

#include <stdio.h>  
#include <unistd.h>  
#include <pthread.h>  

pthread_rwlock_t rwlock; //读写锁  
int num = 1;  
  
//读操作,其他线程允许读操作,却不允许写操作  
void *fun1(void *arg)  
{  
    while(1)  
    {  
        pthread_rwlock_rdlock(&rwlock);
        printf("read num first == %d\n", num);
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
  
//读操作,其他线程允许读操作,却不允许写操作  
void *fun2(void *arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("read num second == %d\n", num);
        pthread_rwlock_unlock(&rwlock);
        sleep(2);
    }
}
 
//写操作,其它线程都不允许读或写操作  
void *fun3(void *arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        num++;
        printf("write thread first\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(2);
    }
}
 
//写操作,其它线程都不允许读或写操作  
void *fun4(void *arg)
{
    while(1)
    {  
        pthread_rwlock_wrlock(&rwlock);  
        num++;  
        printf("write thread second\n");  
        pthread_rwlock_unlock(&rwlock);  
        sleep(1);  
    }  
}  
  
int main()  
{  
    pthread_t ptd1, ptd2, ptd3, ptd4;  
      
    pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁  
      
    //创建线程  
    pthread_create(&ptd1, NULL, fun1, NULL);  
    pthread_create(&ptd2, NULL, fun2, NULL);  
    pthread_create(&ptd3, NULL, fun3, NULL);  
    pthread_create(&ptd4, NULL, fun4, NULL);  
      
    //等待线程结束,回收其资源  
    pthread_join(ptd1, NULL);  
    pthread_join(ptd2, NULL);  
    pthread_join(ptd3, NULL);  
    pthread_join(ptd4, NULL);  
      
    pthread_rwlock_destroy(&rwlock);//销毁读写锁  
      
    return 0;  
}  
  1. 自旋锁(同步)
      自旋锁与互斥量功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁。
      自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。
      自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。

  2. 信号量(同步与互斥)
      信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
      编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
    5.1初始化信号量
    初始化信号量的操作及参数说明:
    *名字
    sem_init --初始化信号量
    用法
    #include <semaphore.h>
    int sem_init(sem_t sem, int pshared, unsigned int value);
    sem – 指向的信号对象;
    pshared – 控制信号量的类型,值为 0 代表该信号量用于多线程间的同步,值如果大于 0 表示可以共享,用于多个相关进程间的同步;
    value – 信号量初始的整数值。
    描述
    sem_init ()函数:初始化由 sem 指向的信号对象,并给它一个初始的整数值 value。
    返回值
    成功:0,失败:返回一个非0的错误码

5.2 信号量减一操作
信号量减一的操作及参数说明:
*名字
sem_wait – 信号量减一操作,有线程申请资源;
sem_trywait – 以非阻塞的方式来对信号量进行减 1 操作
用法
int sem_wait(sem_t *sem);
int sem_trywait(sem_t sem);
sem – 指向信号量的指针。
描述
sem_wait():是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。若 sem value > 0,则该信号量值减去 1 并立即返回。若sem value = 0,则阻塞直到 sem value > 0,此时立即减去 1,然后返回。
sem_trywait(): 是非阻塞的函数,它会尝试获取获取 sem value 值,如果 sem value = 0,不是阻塞住,而是直接返回一个错误 EAGAIN。
返回值
成功:0,失败:返回一个非0的错误码

5.3信号量加一操作
信号量加一的操作及参数说明:
*名字
sem_post --信号量加一操作,有线程释放资源
用法
int sem_post(sem_t sem);
sem – 指向信号量的指针。
描述
sem_post ()函数:把指定的信号量 sem 的值加 1,唤醒正在等待该信号量的任意线程。
返回值
成功:0,失败:返回一个非0的错误码

5.4获取信号量的值
获取信号量的值的操作及参数说明:
*名字
sem_getvalue – 获取信号量的值
用法
int sem_getvalue(sem_t *sem, int sval);
sem – 指向信号量的指针;
sval – 指向获取信号量值的指针。
描述
sem_getvalue ()函数:获取信号量 sem 的当前值,把该值保存在 sval,若有 1 个或者多个线程正在调用 sem_wait 阻塞在该信号量上,该函数返回阻塞在该信号量上进程或线程个数。
返回值
成功:0,失败:返回一个非0的错误码

5.5 销毁信号量的值
销毁信号量的值的操作及参数说明:
*名字
sem_destroy –-销毁信号量
用法
int sem_destroy(sem_t sem);
sem – 指向信号量的指针。
描述
sem_destroy ()函数:销毁信号量
返回值
成功:0,失败:返回一个非0的错误码

5.6 信号量用于同步实例

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
 
sem_t sem_g,sem_p;   //定义两个信号量
char ch = 'a';
 
void *pthread_g(void *arg)  //此线程改变字符ch的值
{
    while(1)
    {
        sem_wait(&sem_g);
        ch++;
        sleep(1);
        sem_post(&sem_p);
    }
}
 
void *pthread_p(void *arg)  //此线程打印ch的值
{
    while(1)
    {
        sem_wait(&sem_p);
        printf("%c",ch);
        fflush(stdout);
        sem_post(&sem_g);
    }
}
 
int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    sem_init(&sem_g, 0, 0); // 初始化信号量为0
    sem_init(&sem_p, 0, 1); // 初始化信号量为1
    
    // 创建两个线程
    pthread_create(&tid1, NULL, pthread_g, NULL);
    pthread_create(&tid2, NULL, pthread_p, NULL);
    
    // 回收线程
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    return 0;
}
  • 14
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值