Linux系统编程与网络编程——线程同步:互斥锁,读写锁,条件变量,无名信号量,文件锁(十六)

互斥锁

当我们需要控制对共享资源的存取的时候,可以用一种简单的加锁的方法来控制。下面来看一个造成值不可知的情况。

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

#define NLOOP 5000
static int counter;

void *mythread(void *vptr)
{
	int i, val;
	for (i = 0; i < NLOOP; i++)
	{
		val = counter;
		printf("%x: %d\n", (unsigned int)pthread_self(), ++val);
		counter = val;
	}
	return NULL;
}

int main(int argc, char **argv)
{
	pthread_t tidA, tidB;
	pthread_create(&tidA, NULL, &mythread, NULL);
	pthread_create(&tidB, NULL, &mythread, NULL);
	/* wait for both threads to terminate */
	pthread_join(tidA, NULL);
	pthread_join(tidB, NULL);
	return 0;
}

在pthread.h中,互斥锁为结构体 pthread_mutex_t , 并且可以使用动态初始化 pthread_mutex_init() 来初始化一个锁,其函数原型为:
在这里插入图片描述
第一个参数是互斥锁的地址,第二个参数设置互斥锁的属性,大多数情况下,选择默认属性,传递NULL。也可以使用静态初始化一把锁。
在这里插入图片描述
分别使用 pthread_mutex_lock() 上锁, pthread_mutex_unlock() 解锁。这个两个函数的原型是:
在这里插入图片描述
pthread_mutex_lock() 函数声明开始用互斥锁上锁,此后代码直至调用pthread_mutex_unlock()为止,均被上锁,即同一时间只能被一个线程调用执行,当一个线程执行到pthread_mutex_lock()处时,如果该锁此时被另一个线程使用,那么此线程被阻塞,线程一直阻塞直到另一个线程释放此互斥锁。

两个函数的参数都是互斥变量的地址,函数执行成功返回0,否则返回错误值。
源代码: mutex_test.c

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

static value = 0;
static pthread_mutex_t mutex;

void* mythread(void* arg)
{
	/*上锁*/
	printf("mythread begin lock\n");
	pthread_mutex_lock(&mutex);
	printf("mythread locked\n");
	sleep(5);
	printf("mythread get value = [%d]\n", value++);
	/*解锁*/
	pthread_mutex_unlock(&mutex);
	printf("mythread unlocked\n");
	pthread_exit((void*)0);
}

int main(int argc, char** argv)
{
	int ret;
	pthread_t pth;
	pthread_mutex_init(&mutex, NULL);
	ret = pthread_create(&pth, NULL, mythread, NULL);
	if(ret != 0)
	{
		printf("create thread:%s\n", strerror_r(ret));
		return -1;
	}
	/*主线程上锁*/
	printf("main thread begin lock\n");
	pthread_mutex_lock(&mutex);
	printf("main thread locked\n");
	sleep(5);
	printf("main thread get value = [%d]\n", value++);
	/*主线程解锁*/
	pthread_mutex_unlock(&mutex);
	printf("main thread unlocked\n");
	pthread_join(pth, NULL);
	return 0;
}

执行后输出为:
在这里插入图片描述
主线程先抢到锁,然后子线程阻塞等待直到获取到锁资源。

非阻塞上锁 pthread_mutex_trylock , 成功获得锁返回0,其他任何值都是没获取到锁。
其函数原型为:
在这里插入图片描述
将源代码中的 mythread 更改如下:
在这里插入图片描述
每过一秒钟尝试获取锁。

销毁锁,当互斥锁不再使用的时候,应当调用 pthread_mutex_destroy() 来销毁锁,其函数原型为:
在这里插入图片描述
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。

临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。临界区的选定因尽可能小,如果选定太大会影响程序的并行处理性能。


读写锁

读写锁是用来解决读写问题的,读操作可以共享,写操作是独占的,读可以有多个在读,写只有唯一个在写,同时写的时候不允许读。互斥锁会导致读时不能共享
在这里插入图片描述

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

int count;
pthread_rwlock_t rwlock;

/*3个线程不定时写同一全局资源,5个线程不定时读同一全局资源*/
void *th_write(void *arg)
{
	while (1)
	{
		pthread_rwlock_wrlock(&rwlock);
		printf("%x get wrlock\n", (int)pthread_self());
		sleep(5);
		printf("write %x : count=%d \n", (int)pthread_self(), ++count);
		pthread_rwlock_unlock(&rwlock);
		sleep(5);
	}
} 

void *th_read(void *arg)
{
	while (1)
	{
		pthread_rwlock_rdlock(&rwlock);
		printf("read %x get rdlock\n", (int)pthread_self());
		sleep(1);
		printf("read %x : %d\n", (int)pthread_self(), count);
		pthread_rwlock_unlock(&rwlock);
		sleep(1);
	}
} 

int main(void)
{
	int i;
	pthread_t tid[6];
	pthread_rwlock_init(&rwlock, NULL);
	
	for (i = 0; i < 5; i++)
		pthread_create(&tid[i], NULL, th_read, NULL);
		
	pthread_create(&tid[5], NULL, th_write, NULL);
	pthread_rwlock_destroy(&rwlock);
	
	for (i = 0; i < 6; i++)
		pthread_join(tid[i], NULL);
		
	return 0;
}

线程同步-条件变量

互斥锁只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足条件变量通常和互斥锁一起使用

条件变量被用来阻塞一个线程,当条件不足时,线程通常先解开相应的互斥锁进入阻塞状态,等待条件发生变化。一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个 或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。

条件变量初始化

条件变量的结构为 pthread_cond_t ,函数 pthread_cond_init() 被用来初始化一个条件变量。它的函数原型为:
在这里插入图片描述
cond : 是一个指向结构 pthread_cond_t 的指针。

cond_attr : 是一个指向结构 pthread_condattr_t 的指针。结构 pthread_condattr_t 是条件变量的属性结构,
和互斥锁一样我们可以用它来设置。

条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_PROCESS_PRIVATE , 即此条件变量被同一进程内的各个线程使用。

条件变量销毁

当条件变量不在使用的时候,应当释放,释放一个条件变量的函数:
在这里插入图片描述

等待条件变量

当我们需要用一个条件变量来阻塞线程的时候,调用函数pthread_cond_wait().其函数原型为:
在这里插入图片描述
函数的第一个参数是条件变量,第二个参数是线程阻塞的时候要解开的互斥锁

唤醒等待的线程

线程可以被函数 pthread_cond_signal 和函数 pthread_cond_broadcast 唤醒,其函数原型是:
在这里插入图片描述
他们用来释放被阻塞的条件变量cond上的线程,这两个函数区别在于当有多个线程都被同一个条件变量所阻塞时:
用 pthread_cond_broadcast() 函数可以使所有线程都被唤醒。
用 pthread_cond_signal() 时哪一个线程被唤醒是由线程的调度策略所决定。
源代码:cond_test.c

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

static int condition = 0;
static value = 0;
static pthread_mutex_t mutex;
static pthread_cond_t cond;

void* mythread(void* arg)
{
	/*上锁*/
	pthread_mutex_lock(&mutex);
	printf("mythread(%d) locked\n", (int)arg);
	while(condition != 1)
	{
		/*条件不满足,等待条件*/
		printf("mythread(%d) wait cond\n", (int)arg);
		pthread_cond_wait(&cond, &mutex);
	} 
	printf("mythread(%d) get value = [%d]\n", (int)arg, value++);
	/*解锁退出*/
	pthread_mutex_unlock(&mutex);
	printf("mythread(%d) unlocked\n", (int)arg);
	pthread_exit((void*)0);
} 

int main(int argc, char** argv)
{
	int ret;
	pthread_t pth0;
	pthread_t pth1;
	
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);
	ret = pthread_create(&pth, NULL, mythread, (void*)0);
	if(ret != 0)
	{
		printf("create thread:%s\n", strerror_r(ret));
		return -1;
	} 
	ret = pthread_create(&pth, NULL, mythread, (void*)1);
	if(ret != 0)
	{
		printf("create thread:%s\n", strerror_r(ret));
		return -1;
	} 
	sleep(5);
	
	/*主线程获取锁*/
	pthread_mutex_lock(&mutex);
	printf("main thread locked\n");
	condition = 1;
	/*主线程解锁*/
	printf("main thread unlocked\n");
	pthread_mutex_unlock(&mutex);
	
	/*主线程唤醒在条件上等待的线程*/
	pthread_cond_signal(&cond);
	pthread_join(pth0, NULL);
	pthread_join(pth1, NULL);
	return 0;
}

进程开了两个子线程,两个子线程通过上锁 mutex 来访问 value 变量,但是在这访问变量之前需要 condition 这个条件为1,不成立解锁 mutex 阻塞等待条件成立。

主线程通过上锁 mutex 来将 condition 设置为1,然后通过函数 pthread_cond_signal() 唤醒在这个cond条件上等待的两个线程的其中一个。

如果想要唤醒全部在cond条件上等待的线程,则可以使用 pthread_cond_broadcast() 。

pthread_cond_test 输出如下图:
在这里插入图片描述


线程同步-无名信号量

信号量从本质上是一个非负整数计数器,通常被用来控制对公共资源的访问。当可用的公共资源增加时,调用函数 sem_post() 增加信号量。只有当信号量大于0时,函数 sem_wait() 才能返回,并将信号量的值减1,当信号量等于0时, sem_wait() 将被阻塞直到信号量的值大于0.函数 sem_trywait() 是函数 sem_wait() 的非阻塞版本。

信号量的数据类型为结构 sem_t .函数 sem_init() 用来初始化一个信号量。其的函数原型为:
在这里插入图片描述
在这里插入图片描述
函数 sem_post() 用来增加信号量的值,其函数原型是:
在这里插入图片描述
函数 sem_wait() 用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减去1,表明公共资源经使用后减少,其函数原型是:
在这里插入图片描述
函数sem_trywait()与函数sem_wait()的区别是当信号量的值等于0时,sem_trywait()函数不会阻塞当前线程。
在这里插入图片描述
当信号量不再使用的时候,要用函数sem_destroy()来释放信号量。其函数原型是:
在这里插入图片描述
在下面的例子中,我们创建三个线程a、b、c。但是要求线程按照c->b->a的顺序执行 。
源代码:sem_test.c

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

static sem_t sem1;
static sem_t sem2;

void *thread_a(void *arg)
{
	/*等待信号量sem1*/
	sem_wait(&sem1);
	printf("Thread_a running\n");
} 

void *thread_b(void *arg)
{
	/*等待信号量sem2*/
	sem_wait(&sem2);
	printf("thread_b running\n");
	/*增加信号量sem1,使得thread_a可以执行*/
	sem_post(&sem1);
} 

void *thread_c(void *arg)
{
	printf("thread_c running\n");
	/*增加信号量sem2,使得thread_b可以执行*/
	sem_post(&sem2);
} 

int main(int argc, char** argv)
{
	pthread_t a,b,c;
	
	/*增加信号量*/
	sem_init(&sem1, 0, 0);
	sem_init(&sem2, 0, 0);
	
	/*创建线程a, b, c*/
	pthread_create(&a, NULL, thread_a, NULL);
	pthread_create(&b, NULL, thread_b, NULL);
	pthread_create(&c, NULL, thread_c, NULL);
	
	/*等待线程退出*/
	pthread_join(a, NULL);
	pthread_join(b, NULL);
	pthread_join(c, NULL);
	
	/*删除信号量*/
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	return 0;
}

文件锁
在这里插入图片描述
参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。

例子:

#include <sys/types.h>
#include <stdio.h>
#include <sys/file.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
	char buf[] = "helloworld";
	
	if(argc < 2)
	{
		printf("usage: %s [file]\n", argv[0]);
		exit(1);
	} 
	int fd = open(argv[1], O_WRONLY);
	if(fd == -1)
	{
		perror("open");
		exit(1);
	} 
	printf("lock file %s\n", argv[1]);
	flock(fd, LOCK_EX);
	printf("get lock\n");
	write(fd, buf, strlen(buf));
	sleep(10);
	
	flock(fd, LOCK_UN);
	printf("unlock file %s\n", argv[1]);
	
	close(fd);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/89063560