linux多线程编程(2)


线程之间的同步与互斥

     由于线程共享进程的资源和地址空间,因此对这些资源进行操作时,必须考虑到线程间资源访问的同步与互斥问题。

例如int *a int *b分别指向两块内存,上面的值分别初始化为(200, 100)

线程A执行这样的一个操作:将*a的值减少50,*b的值增加50.

线程B执行:打印出(a 跟 b 指向的内存的值的和)。

如果串行运行:A: *a -= 50; *b += 50;  B: printf("%d\n", *a + *b); 

如果并发执行,则有可能会出现一下调度:*a -= 50; printf("%d\n", *a + *b); *b += 50;

因此我们可以引入互斥锁,在对共享数据读写时进行锁操作,实现对内存的访问以互斥的形式进行。

(1)互斥锁线程控制

扫描二维码关注公众号,回复: 955994 查看本文章

        互斥锁通过简单的加锁方式来控制对共享资源的原子操作,它提供一个可以在同一时间,只让一个线程访问资源的的操作接口。

        互斥锁有两种状态,也就是上锁和解锁。同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作,如果其他线程想要上锁已被上锁的互斥锁,该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,互斥锁保证了让每个线程对共享资源按顺序的原子操作。

 POSIX互斥锁相关函数主要有以下5个:

#include <pthread.h>  
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);  
int pthread_mutex_destroy(pthread_mutex_t *mutex);  
int pthread_mutex_lock(pthread_mutex_t *mutex);  
int pthread_mutex_trylock(pthread_mutex_t *mutex);  
int pthread_mutex_unlock(pthread_mutex_t *mutex);  
 这些函数第一个参数mutex指向要操作的目标互斥锁,成功时返回0,出错返回错误码

pthread_mutex_init用于初始化互斥锁,mutexattr用于指定互斥锁的属性,若为NULL,则表示默认属性。除了用这个函数初始化互斥所外,还可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  pthread_mutex_destroy用于销毁互斥锁,以释放占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果

 pthread_mutex_lock以原子操作给一个互斥锁加锁。如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁

 pthread_mutex_trylock和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,是pthread_mutex_lock的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock进行加锁操作;否则将返回EBUSY错误码。注意:这里讨论的pthread_mutex_lock和pthread_mutex_trylock是针对普通锁而言的,对于其他类型的锁,这两个加锁函数会有不同的行为

pthread_mutex_unlock以原子操作方式给一个互斥锁进行解锁操作。如果此时有其他线程正在等待这个互斥锁,则这些线程中的一个将获得它


#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
 
int a = 200;
int b = 100;
pthread_mutex_t lock;
 
void* ThreadA(void*)               //线程A
{
pthread_mutex_lock(&lock);          //上锁
a -= 50;
sleep(5);                           //执行到一半 使用sleep 放弃cpu调度
b += 50;
pthread_mutex_unlock(&lock);       //解锁

}
 
void* ThreadB(void*)                //线程B
{ 
sleep(1);                            //放弃CPU调度 目的先让A线程运行。
pthread_mutex_lock(&lock);   
printf("%d\n", a + b);
pthread_mutex_unlock(&lock);
}
 
int main()
{
pthread_t tida, tidb;
pthread_mutex_init(&lock, NULL);
pthread_create(&tida, NULL, ThreadA, NULL);
pthread_create(&tidb, NULL, ThreadB, NULL);
pthread_join(tida, NULL);
pthread_join(tidb, NULL);
pthread_mutex_destroy(&lock); 
return 0;
}

(2)信号量线程控制

    信号量也就是操作系统中所用到的PV原子操作(P表示通过,V表示释放),它广泛用于进程或线程间的同步与互斥。
    信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
     首先介绍下PV的原子操作原理:
     PV原子操作就是对整数计数器信号量sem的操作。一次P操作使sem减1,一次V操作使sem加1.进程或者线程根据信号量的值来判断 是否能对共享资源具有访问权限。当sem的值大于等于0时,该进程或者线程具有对公共资源的访问权限;相反,当信号量sem的值小于0时, 该进程就讲阻塞直到信号量sem的值大于等于0为止。
    PV原子操作若用于互斥,几个进程或者线程往往只设置一个信号量sem,若用于同步操作,往往会设置几个信号量,并安排不同的初始值来实现他们之间的执行顺序。

主要用到的函数:

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件 semaphore.h
#include <semaphore.h>  
int sem_init(sem_t* sem, int pshared, unsigned int value);  
int sem_destroy(sem_t *sem);  
int sem_wait(sem_t *sem);  
int sem_trywait(sem_t *sem);  
int sem_post(sem_t *sem);  


     这些函数的第一个参数sem指向被操作的信号量,上面这些函数成功时返回0,失败返回-1并设置errno。

 sem_init函数用于初始化一个未命名的信号量(POSIX信号量API支持命名信号量,不过在该章节没有讨论)。pshared制定信号量的类型,如果其值为0,则表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享。value制定信号量的初始值,此外初始化一个已经被初始化的信号量将导致不可预期的后果

sem_destroy用于销毁信号量,以释放其占用的内核资源。如果销毁一个正在等待的信号量,则将导致不可预期的后果

sem_wait以原子操作将信号量值减1,如果信号量的值为0,则sem_wait将被阻塞,直到该信号量值为非0值

sem_trywaitsem_wait函数类似,不过它始终立即返回,而不论信号量是否具有非0值,相当于sem_wait的非阻塞版本。当信号量的值为非0时,sem_trywait对信号量执行减1操作;当信号量为0时,它将返回-1并设置errno为EAGAIN

sem_post以原子操作的方式将信号量的值加1,当信号量的值大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒



线程中使用信号量,示意:
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>

#define err_sys(msg) \
	do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
	do { fprintf(stderr, msg); exit(-1); } while(0)

void *r1(void *arg)
{
	sem_t* sems = (sem_t *)arg;
	static int cnt = 10;

	while(cnt--)
	{
		sem_wait(sems);
		printf("I am in r1. I get the sems.\n");
	}
}

void *r2(void *arg)
{
	sem_t* sems = (sem_t *)arg;
	static int cnt = 10;

	while(cnt--)
	{
		printf("I am in r2. I send the sems\n");
		sem_post(sems);
		sleep(1);
	}
}

int main(void)
{
	sem_t sems;
	pthread_t t1, t2;
	
	printf("sems size: %d\n", sizeof(sems));
	/* sem_init()第二个参数为0表示这个信号量是当前进程的局部信号量,否则该信号
	 * 就可以在多个进程之间共享 */
	if(sem_init(&sems, 0, 0) < 0)
		err_sys("sem_init error");
	pthread_create(&t1, NULL, r1, &sems);
	pthread_create(&t2, NULL, r2, &sems);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	sem_destroy(&sems);

	return 0;
}


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自blog.csdn.net/u012101561/article/details/78449063