linux多线程编程(2)

来源:互联网 发布:淘宝店如何开通直通车 编辑:程序博客网 时间:2024/05/24 16:13


线程之间的同步与互斥

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

例如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)互斥锁线程控制

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

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

 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;}


 
原创粉丝点击