代码改变世界

linux系统编程----->线程同步

2018-03-17 19:23  huanew  阅读(264)  评论(0)    收藏  举报

1、【线程同步的概念】

  线程同步和日常生活中的统一步调不一致,线程同步指的是多个线程之间协同(不是统一)步调,按照预计的顺序去访问共享数据。

  线程同步指一个线程发出某一功能调用时在未得到结果之前该调用不能范湖及,同时其他线程为了数据的一致性,不能够调用该功能。

2、【线程同步的方法】

  (A) 互斥量

  Linux中会提供一把互斥锁mutex(称之为互斥量),互斥量的类型为pthread_mutex_t,每个线程对共享资源进行访问时都约定先对互斥量进行加锁,只有成功加锁才能够进行数据访问,当访问完成共享数据需要立即解锁,保证锁的粒度足够小。

  这样保证资源仍然是共享的,线程之间仍然有竞争,但是互斥锁将共享资源的访问变成了互斥操作。

  互斥量的使用

  pthread_mutex_t mutex; //定义一把互斥锁

  pthread_mutex_init(pthread_mutex_t *restrict  mutex, const pthread_mutexattr_t *restrict attr);

  //第一个参数为要初始化的互斥锁,第二个参数为互斥锁的属性,传入NULL则代表互斥锁采用默认属性

  pthread_mutex_destroy(pthread_mutex_t *restrict  mutex);

  //销毁一把互斥锁

  pthread_mutex_lock(pthread_mutex_t *mutex);

  //对互斥锁进行加锁操作,如果锁已经被其他进程加锁则不能加锁成功,阻塞等待

  pthread_mutex_trylock(pthread_mutex_t *mutex);

  //这个函数也是对互斥锁进行加锁操作,但是不会阻塞等待,所以可以采用轮询的方式进行加锁

  pthread_mutex_unlock(pthread_mutex_t*mutex);

  //解锁操作

  --------------------------------

  互斥锁本质是一个结构体,但这里可以将互斥锁看成一个整型数,只能取0,1两个值。初始化完成后锁取1,对锁进行加锁操作

  相当于对锁进行--操作,对锁进行解锁相当于++操作。锁值为0不能继续--,只能阻塞等待。

  ---------------------------

  示例1,父子线程对操作系统的标准输出竞争

  

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<pthread.h>
  5 #include<time.h>
  6 
  7 pthread_mutex_t mutex;
  8 void* thread_handler(void* arg)
  9 {
 10         while(1)
 11         {
 12                 pthread_mutex_lock(&mutex);
 13                 printf("hello,");
 14                 sleep(rand()%2);
 15                 printf(" world!\n");
 16                 pthread_mutex_unlock(&mutex);
 17                 sleep(rand()%2);//模拟失去CPU
 18         }
 19         pthread_exit(NULL);
 20 }
 21 int main()
 22 {
 23         pthread_mutex_init(&mutex,NULL);
 24         pthread_t tid;
 25         srand(time(NULL));
 26         int ret = pthread_create(&tid,NULL,thread_handler,NULL);
 27         if(ret != 0)
 28         {
 29                 printf("pthread_creat error %s\n", strerror(ret));
 30                 exit(EXIT_FAILURE);
 31         }
 32 
 33         while(1)
 34         {
 35                 pthread_mutex_lock(&mutex);
 36                 printf("HELLO,");
 37                 sleep(rand()%2);
 38                 printf(" WORLD!\n");
 39                 pthread_mutex_unlock(&mutex);
 40                 sleep(rand()%2);
 41 
 42         }
       pthread_mutex_destroy(mutex);
43 pthread_join(tid,NULL); //回收线程 44 pthread_exit(NULL); 45 } 46

 

 

 

 对应的程序的执行结果如下,可以从结果中看出来,父子线程分别访问完成标准输出资源,才会被另一线程抢占

死锁的两种情况

  (1) 同一个线程对同一把锁加锁第一次后,未解锁的情况下,又对锁进行第二次加锁。

    避免:在加锁之前应该先检查是否已经将锁释放

  (2) 线程1拥有A锁,此时请求拥有B锁,同时线程2拥有B锁,请求获得A锁,此时也会造成线程阻塞。

    避免:在不能获得所需要的资源时(B锁),可以先释放获得的资源(A锁)。

   第二种情况的死锁示例子

 1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<pthread.h>
  5 #include<time.h>
  6 
  7 pthread_mutex_t mutexA, mutexB;
  8 void* thread_handler(void* arg)
  9 {
 10         while(1)
 11         {
 12                 pthread_mutex_lock(&mutexB);
 13                 printf("B is locked\n");
 14                 pthread_mutex_lock(&mutexA);
 15                 pthread_mutex_unlock(&mutexB);
 16                 printf("child got A\n");
 17                 pthread_mutex_unlock(&mutexA);
 18         }
 19         pthread_exit(NULL);
 20 }
 21 int main()
 22 {
 23         pthread_mutex_init(&mutexA,NULL);
 24         pthread_mutex_init(&mutexB,NULL);
 25         pthread_t tid;
 26         srand(time(NULL));
 27         int ret = pthread_create(&tid,NULL,thread_handler,NULL);
 28         if(ret != 0)
 29         {
 30                 printf("pthread_creat error %s\n", strerror(ret));
 31                 exit(EXIT_FAILURE);
 32         }
 33 
 34         while(1)
 35         {
 36                 pthread_mutex_lock(&mutexA);
 37                 printf("A is locked\n");
 38                 pthread_mutex_lock(&mutexB);
 39                 pthread_mutex_unlock(&mutexA);
 40                 printf("i got B\n");
 41                 pthread_mutex_unlock(&mutexB);
 42         }
43      pthread_mutex_destroy(mutexA);
44      pthread_mutex_destroy(mutexB);
45 pthread_join(tid,NULL); 46 pthread_exit(NULL); 47 }

  

  运行一段时间后程序阻塞了

(B)读写锁<程序对共享资源的读操作显著多于写操作时,性能显著优于互斥量>

  读写锁的状态有三种

  1、读模式下加锁

  2、写模式下加锁

  3、未加锁状态

  读写锁的特性<写独占,读共享,写锁优先级高>

  1、读写锁是“读模式加锁”时,解锁前,所有对该锁加锁的程序都会被阻塞等待;

  2、读写锁是“写模式加锁时”,如果线程以读模式加锁会成功,但是以写模式加锁则会被阻塞;

  3、读写锁“是以读模式加锁”时,既有试图以读模式加锁的线程又有以写模式加锁的线程,此时都会被阻塞,解锁后的加锁请求优先满足写加锁。

     读写锁的使用<可以类比于互斥量>

   pthread_rwlock_t  rwlock;

   int pthread_rwlock_init(pthread_rwlock_t * restrict rwlock, const pthread_rwlockattr_t *restrict attr );

   int pthread_rwlock_destroy(pthread_rwlock_t *  rwlock);

   int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock); //阻塞

   int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);//阻塞

   int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock);//非阻塞

   int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock);//非阻塞

   int pthread_rwlock_unlock(pthread_rwlock_t * rwlock);

  示例

   

 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<pthread.h>
  5 int rw = 0; //shared resource
  6 pthread_rwlock_t rwlock; //rwlock
  7 void* rdhandler(void*arg)
  8 {
  9         int i = 0;
 10 
 11         while( i++ < 5)
 12         {
 13             pthread_rwlock_rdlock(&rwlock);
 14             printf("%lu read thread, the data is %d \n",pthread_self(),rw);
 15             pthread_rwlock_unlock(&rwlock);
 16             sleep(rand()%5);
 17         }
 18         pthread_exit(NULL);
 19 }
 20 
 21 void* wrhandler(void* arg)
 22 {
 23         int i = 0;
 24 
 25         while( i++ < 5)
 26         {
 27             pthread_rwlock_wrlock(&rwlock);
 28             rw += (int)arg;
 29             printf(" %lu write thread, the data is %d \n",pthread_self(),rw);
 30             pthread_rwlock_unlock(&rwlock);
 31             sleep(rand()%5);
 32         }
 33         pthread_exit(NULL);
 34 }
 35 int main()
 36 {
 37         pthread_t tid[5];
 38         srand(time(NULL));
 39         pthread_create(&tid[0],NULL,wrhandler,(void*)2);
 40         pthread_create(&tid[1],NULL,wrhandler,(void*)4);
 41 
 42         pthread_create(&tid[2],NULL,rdhandler,NULL);
 43         pthread_create(&tid[3],NULL,rdhandler,NULL);
        pthread_create(&tid[3],NULL,rdhandler,NULL);
        pthread_create(&tid[4],NULL,rdhandler,NULL);
        int i = 0;
        for(; i < 5; ++i)
            pthread_join(tid[i],NULL);
        pthread_rwlock_destroy(&rwlock);
        pthread_exit(NULL);

}

 

  运行结果

  

  (c) 条件变量

  条件变量本身不是锁,但是它也可以造成线程阻塞,通常与互斥量配合使用,结合线程提供一个会和的场所;

  相较于互斥量条件变量的优点是可以减少不必要的线程间竞争

  条件变量的使用

  int pthread_cond_destroy(pthread_cond_t *cond);

  销毁一个条件变量

  int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

  初始化一个条件变量

  int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,const struct timespec *restrict    abstime);

  1、阻塞等待条件变量满足

  2、释放已经掌握的互斥锁

  3、当被唤醒时,返回并且重新申请获得互斥锁表

  1和2是原子操作不可分,类似于前面的sigsuspend,

  阻塞有时间限制,并且改时间是个绝对时间所以应先获得当前时间然后加上相对时间传入

  int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

  和前述功能一致,但阻塞无时间限制

  int pthread_cond_broadcast(pthread_cond_t *cond);

  唤醒所有因该条件变量阻塞的线程

  int pthread_cond_signal(pthread_cond_t *cond);

  至少唤醒一个因该条件变量阻塞的线程

  -----条件变量的生产者消费者模型-----------

  这里采用一个链表来模拟一个货箱,有2个线程作为生产者往链表中添加数据,而有3个线程作为消费者从节点中取出数据并丢弃。

 1 //link.h
 2 #ifndef _HEAD_USER_LINK
 3 #define _HEAD_USER_LINK 
 4 #include<stdio.h>
 5 #include<stdbool.h>
 6 #include<stdlib.h>
 7 
 8 typedef struct linknode{
 9         struct linknode* next;
10         int num; //assume > 0
11 }node;
12 
13 node* create()
14 {
15         node* head = (node*)malloc(sizeof(node));
16         head->next = NULL;
17         return head;
18 }
19 
20 node* insert(node* head, int data)
21 {
22     node* inode = (node*)malloc(sizeof(node));
23     if(inode == NULL)
24     {
25             fprintf(stderr,"mallocerror");
26             exit(EXIT_FAILURE);
27     }
28     inode->num = data;
29 
30     node* p = head->next;
31     
32     head->next = inode;
33     inode->next = p;
34     return head;
35 }
36 
37 bool isempty(node* head)
38 {
39         if(head->next == NULL)
40                 return true;
41         else
42                 return false;
43 }
44 
45 int consume(node* head)
46 {
47         if(head->next ==  NULL)
48         {
49                 printf("empty link\n");
50                  return -1;
51         }
52 
53         node* pnode = head->next->next;
54 
55         int data = head->next->num;
56 
57         free(head->next);
58 
59         head->next = pnode;
60 
61         return data;
62 }
63 
64 void destroy(node* head)
65 {
66         node* pnode;
67         while(head != NULL)
68         {
69                 pnode = head->next;
70                 free(head);
71                 head = pnode;
72         }
73 }
74 
75 #endif
//pcm.c
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include"link.h"
#include<time.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
node* head = NULL;
void* producer(void* arg)
{
        int i = 0;
        int id = (int) arg;
        while(1)
        {
                i = rand()%101;
                pthread_mutex_lock(&mutex);
                head = insert(head,i);
                pthread_mutex_unlock(&mutex);
                printf("I am the producer %d, I added a product: %d\n",id,i);
                pthread_cond_broadcast(&cond);
                sleep(rand()%2 + 1);
        }

        pthread_exit(NULL);
}

void* consumer(void*arg)
{
        int i = 0;
        int id = (int) arg;
        while(1)    
        {    
                pthread_mutex_lock(&mutex);
                while(isempty(head))
                {
                        pthread_cond_wait(&cond,&mutex);
                }

                i = consume(head);
                pthread_mutex_unlock(&mutex);
                printf("I am the consumer %d, I consumed a product: %d\n",id,i);
                sleep(rand()%5 + 1);
        }

        pthread_exit(NULL);
}
int main()
{
    head = create();
    pthread_t tid[5];
    srand(time(NULL));
    int i = 0;
    for(; i < 5; ++i)
    {
            if(i < 2)
                pthread_create(&tid[i],NULL,producer,(void*)(i+1));
            else
                pthread_create(&tid[i],NULL,consumer,(void*)(i-1));
    }

    for(i = 0; i < 1; ++i)
    {
            pthread_join(tid[i],NULL);
    }
    destroy(head);
    pthread_exit(NULL);
//添加销毁条件变量 和 互斥锁 }

部分运行结果如下

(D)信号量

前面说过对于互斥锁而言,可以把互斥量看成0,或者1,这里的话信号量则不一样其取值可以为0->N所以可以实现N 个线程共享数据。

由于互斥锁的粒度较大,如果我们希望在多个线程间同一时刻对同一个数据的不同部分进行数据共享,这是无法利用互斥锁实现的,因为互斥锁只能将整个数据锁住,这样虽然达到了多个线程操作共享数据时,保证数据的正确性,却无形中导致数据的并发行下降,线程从并行执行变成串行执行,与按照单线程编写程序无异。信号量则是相对折中的方式,即可以保证数据的正确性和不混乱,而且能够提高线程的并发性。

信号量的使用

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

第一个参数为所需要初始化的信号,第二个参数设置为0则代表用于线程间同步,设置为1则代表可用于进程间同步,第三个值为信号量初始值

int sem_wait(sem_t *sem);

这个函数可以看作是之前锁的lock,执行该函数可以视为信号量--,若信号量为0则线程阻塞

int sem_trywait(sem_t *sem);

非阻塞版本wait

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

定时阻塞版本的wait

int sem_post(sem_t *sem);

相当于前面介绍锁的unlock,但同时回唤醒阻塞的线程

------------------信号量的使用例子--------------------

 假设某理发店有4名理发师,但是此时有N名顾客前来理发,这里采用N个线程来代表N个理发师

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<time.h>
#include<semaphore.h>
#define CONNUM 10

sem_t sem;

void* customer(void*arg)
{
        sem_wait(&sem);
        sleep(3);//模拟每个消费者花费时间
        printf("%dth customer recived the service\n",(int)arg);
        sem_post(&sem);

        pthread_exit(NULL);
}

int main()
{
        sem_init(&sem,0,4);
        
        int i = 0;
        time_t start_t,end_t;
        start_t = time(NULL);
        pthread_t tid[CONNUM];
        for(; i < CONNUM; ++i)
        {
                pthread_create(&tid[i],NULL,customer,(void*)i);
        }

        for(i = 0; i < CONNUM; ++i)
        {
                pthread_join(tid[i],NULL);
        }
        end_t = time(NULL);

        printf("the total time spent to serve %d customers is %lu seconds\n",CONNUM,end_t-start_t);
    
        pthread_exit(NULL);
    //添加销毁互斥锁的函数 }

 

 程序运行结果

(3)进程间同步利用文件锁机制

  int fcntl(int fd, int cmd, ... /* arg */ );

  对于文件有一个flag变量是一个int类型

 

    第二个参数为F_SETFD(int flag)时可以设置文件的读写属性。

 例如 int flag = fcntl(fd,F_GETFD);

       fcntl(fd,F_SETFD,flag|O_RDWR);

 这样就为打开的文件设置了读写属性。

 同样地如果第二个参数设置为

     F_SETLK (struct flock *) // 设置文件锁,相当于trylock版本

   F_SETLKW (struct flock *)//设置文件锁,相当于lock版本,解锁也是该函数

   F_GETLK (struct flock *)//对应pid的文件锁状态

    这三个变量的时候则可以实现,对文件的读写锁控制

  struct flock {
               ...
               short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */解锁时设置为F_UNLOCK

    //这里值得注意的一点是和读写锁一样,读共享,写独占,读锁优先级高
               short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
               off_t l_start;   /* Starting offset for lock */
               off_t l_len;     /* Number of bytes to lock 其中0代表锁住整个文件*/
               pid_t l_pid;     /* PID of process blocking our lock
                                   (set by F_GETLK and F_OFD_GETLK) */
               ...
           };

  使用流程示例

  struct flock filelock;

  filelock.I_type = F_RDLOCK;//设置为读锁

  filelock.I_whence = SEEK_SET; //从文件开始位置开始

  filelock.I_start = 100;//偏移量100个字节

  filelock.I_len = 1000;//锁1000个字节的长度

  fcntl(fd,F_SETLKW,&filelock);//加锁

  filelock.I_type = F_UNLOCK;

  fcntl(fd,F_SETLKW,&filelock);//解锁

  多个进程对同一个文件访问并加锁示例

  

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<sys/types.h>
 4 #include<sys/stat.h>
 5 #include<fcntl.h>
 6 #include<unistd.h>
 7 
 8 int setlock(int fd, short int type)
 9 {
10         struct flock lock,oldlock;
11         lock.l_type = type;
12         lock.l_whence = SEEK_SET;
13         lock.l_start = 0;
14         lock.l_len = 0;
15         oldlock = lock;
16         
17         while(1)
18         {
19                 if(fcntl(fd,F_SETLK,&lock) == 0)
20                 {
21                         if(type == F_RDLCK)
22                         {
23                                 printf("file locked with readlock by %d\n",getpid());
24                                 break;
25                         }
26                         else if(type == F_WRLCK )
27                         {
28                                 printf("file locked with writelock by %d\n",getpid());
29                                 break;
30                         }
31                         else
32                         {
33                                 
34                                 printf(" unlock the file by %d\n ",getpid());
35                                 break;
36                         }
37 
38 
39                 }
40                 fcntl(fd,F_GETLK,&oldlock);
41 
42                 if(oldlock.l_type != F_UNLCK)
43                 {
44                         if(oldlock.l_type == F_RDLCK)
45                         {
46                                 printf("the file already locked  with readlock by %d\n",oldlock.l_pid);
47                         }
48                         else if(oldlock.l_type == F_WRLCK )
49                         {
50                                 printf("the file already locked  with write by %d\n",oldlock.l_pid);
51                         }
52                 }
53 
54                 sleep(2);
55         }
56 return;
57 }
58 
59 void main(int argc, char* argv[])
60 {
61     if(argc < 2)
62     {
63             printf("./pgm pathname");
64             exit(EXIT_FAILURE);
65     }
66     int fd = open(argv[1],O_RDWR|O_CREAT,0644);
67 
68     if(fd == -1)
69     {
70             perror("open file eror");
71             exit(EXIT_FAILURE);
72     }
73 
74     setlock(fd,F_WRLCK);
75     sleep(10);
76     setlock(fd,F_UNLCK);
77     return;
78 }

在两个终端分别运行该程序,先启动终端一,后启动终端二

终端一结果

终端二结果

可见写锁是互斥的,如果将上述程序概为读锁加锁

终端一运行结果为

终端二运行结果

可以看出来读锁共享文件 

(4)进程间同步之互斥量

前面我们介绍使用互斥量的时候,创建互斥量采用的是默认属性,对应默认属性的是在线程间进行同步的。

       pthread_mutex_t mutex; //定义一把互斥锁

  pthread_mutex_init(pthread_mutex_t *restrict  mutex, const pthread_mutexattr_t *restrict attr); 

  //第一个参数为要初始化的互斥锁,第二个参数为互斥锁的属性,传入NULL则代表互斥锁采用默认属性

  pthread_mutex_destroy(pthread_mutex_t *restrict  mutex);

  //销毁一把互斥锁

  pthread_mutex_lock(pthread_mutex_t *mutex);

  //对互斥锁进行加锁操作,如果锁已经被其他进程加锁则不能加锁成功,阻塞等待

  pthread_mutex_trylock(pthread_mutex_t *mutex);

  //这个函数也是对互斥锁进行加锁操作,但是不会阻塞等待,所以可以采用轮询的方式进行加锁

  pthread_mutex_unlock(pthread_mutex_t*mutex);

  //解锁操作

下面介绍如何使用互斥量的属性变量

  pthread_mutexattr_t    mutexattr //创建一个属性变量

  int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); //销毁属性变量
       int pthread_mutexattr_init(pthread_mutexattr_t *attr); //对属性变量进行初始化

    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *  restrict attr, int *restrict pshared);

  //获得属性变量
       int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

       / /设置属性变量,其中 pshared 可以设置为PTHREAD_PROCESS_PRIVATE   

     和PTHREAD_PROCESS_SHARED分别对应为进程间私有或者进程间共享,当设置为进程间共享便可以用来实现进程间的数据同步

 下面的示例说明了如何采用互斥量进行进程间数据同步

  

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/mman.h>
#include<time.h>
//为了简单,将共享数据以及互斥量封装在了一起
typedef struct {
        int num;
        pthread_mutex_t mutex;
        pthread_mutexattr_t mutexattr;
}procmutex;

int main()
{
  //在内核里面申请一段共享映射区,供父子进程通信  
        procmutex* pmutex = mmap(NULL,sizeof(procmutex),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
//初始化和设置互斥量属性变量
        pthread_mutexattr_init(&pmutex->mutexattr);
        pthread_mutexattr_setpshared(&pmutex->mutexattr,PTHREAD_PROCESS_SHARED);
//初始化互斥量为进程间共享
        pthread_mutex_init(&pmutex->mutex,&pmutex->mutexattr);
//创建子进程
        pid_t pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                exit(EXIT_FAILURE);
        }

        if(pid == 0)
        {
                int i = 10;
                while(i--)
                {
                    srand(time(NULL));
                    pthread_mutex_lock(&pmutex->mutex);
                    printf("I am child process data:%d\n",pmutex->num += 2);
                    pthread_mutex_unlock(&pmutex->mutex);
                    sleep(rand()%3+1);
                }
        }
        else if(pid > 0)
        {    
            int i = 10;    
                while(i--)
                {
                    srand(time(NULL));
                    pthread_mutex_lock(&pmutex->mutex);
                    printf("I am PARENT process data:%d\n",pmutex->num += 4);
                    pthread_mutex_unlock(&pmutex->mutex);
                    sleep(rand()%2+1);
                }
//回收子进程
    
                wait(NULL);
        }
     pthread_mutex_destroy(&pmutex->mutex);
munmap(pmutex,sizeof(procmutex));
 
return 0; }

程序运行结果如下:

4、线程同步最后一个小作业之哲学家吃饭问题

场景:5个哲学家,5把叉子,5盘意大利面(意大利面很滑,需要两把叉子才能拿起)大家围绕桌子,进行思考与进食的活到,如下图所示。

哲学家的活动方式为:要么放下左右手刀叉进行思考,要么拿起刀叉开始吃饭(刀叉拿起时,必须拿两把,而且只能左右手依次拿,先左手拿左边,后右手拿右边,或者先右手拿右边,左边拿左边)。其只有这两种交替状态。

哲学家们面临的问题为:如何安排哲学家们一致的行动逻辑,保证他们至少有人且尽可能两个人能同时拿到两把叉子开始吃饭,而不会发生“死锁”,“饥饿”,“干等”的状态。需要注意的是,大家想吃饭的时机是随机的,想思考的时机也是随机的,这个不受控制,不可能由“你”来安排哲学家们哪几个先吃,哪几个后吃,他们不受你控制,但你要赋予他们一种性格,或者说思考方式,保证他们自主的思考,自主的解决问题。

  • 死锁:大家都同时想吃饭,结果同时拿起左手边叉子,发现同时右边没有叉子,然后各怀私心,僵持者希望有人能放下他左手边叉子,然后抢夺之,开始吃意大利面,结果大家都没放。。。
  • 饥饿:大家都同时想吃饭,结果同时拿起左手边叉子,发现同时右边没有叉子,然后都很慷慨,结果大家同时放下左手边叉子,然后大家发现有叉子了,又同时开始拿起左手边叉子,又同时放下,如此反复。。。
  • 干等:假设想拿叉子这个想法的产生是一个“原子”操作,即不可同时发生,不可中断,然后一旦有人想拿,就进化为X教授,然后用能力控制了其他人处于僵化状态,然后开始独享,独享完后放下叉子想思考了,立即丧失超能力,于是其余四人回归正常,然后5人中再次有人想拿叉子,进化为教授,周而复始。但是这样,尼玛一个人吃,其余四个干看着啊。。怎么说也有5把叉子,5盘意大利面,至多可以两个人同时吃的。

这里采用五个线程模拟五个哲学家,5把互斥锁模拟5只筷子,解决方案为除了五号线程所有人均先抢自己右手边的然后再抢左手边的,当不能同时获得两个锁的时候就线释放资源

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

pthread_mutex_t mutex[5];
void* eat_thinking(void*arg)
{
    int i = (int)arg;
    int left = (i-1)%5;
    int right = i%5;
    if(i == 5) //五号线程和其他线程相反避免活锁的情况
    {
            int temp = left;
            left = right;
            right = temp;
    }

    while(1)
    {
            
                    if(pthread_mutex_trylock(&mutex[right])==0)
                            if(pthread_mutex_trylock(&mutex[left])==0)
                            {
                                    printf("i am eating,%d\n",i);
                                    sleep(2);
                                    break;
                            }
                            else
                            {
                                    pthread_mutex_unlock(&mutex[right]);
                            }
    }

    pthread_mutex_unlock(&mutex[left]);
    pthread_mutex_unlock(&mutex[right]);
    pthread_exit(NULL);
}
void main()
{
        int i;
        pthread_t tid[5];
        int tbeg,tend;
        tbeg = time(NULL);
        
        for(i = 0; i < 5; ++i)
        {
                pthread_mutex_init(&mutex[i],NULL);
        }

        for(i = 0; i < 5; ++i)
        {
                pthread_create(&tid[i],NULL,eat_thinking,(void*)(i+1));
        }

        for(i = 0; i < 5; ++i)
        {
                pthread_join(tid[i],NULL);
        }
        tend = time(NULL);

        printf("the total time is%d\n",tend-tbeg);

     for(i = 0; i < 5; ++i)
        {
                pthread_mutex_destroy(&mutex[i]);
        }
return; }

几次不同的运行结果如下

5【总结】

通过学习不同的线程同步方法,并且进行示例编写对学习的知识进行熟悉,更好地掌握了这些知识。