同步

啥事同步,同时起步,协调一致的,然而不同的对象,又有不同的同步概念

设备同步,两个设备之间的一个共同时间的参考,数据库同步等等

在linxu中我们需要了解线程同步

线程同步

同步即协同一致,按照预订的先后次序运行

一个线程调用一个函数时,为了保证其他线程调用时的数据一致性,不能调用其他函数

这个时候我们就需要给这个调用的数据,上一个锁,对共享的区域做一个保护

所以在linux中当多个流程操作一个共享资源时都需要加锁

常用锁和常用原语

互斥量(互斥锁)

pthread_mutex_init

初始化一个互斥锁

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

注意这个关键词restrict,指名只能使用该指针,不能通过其他指针操作内存指向

  • pthread_mutex_t *restrict mutex 可以单纯理解为锁 ,锁上是为0,解锁为1
  • mutex,const pthread_mutexattr_t *restrict attr 锁的属性

默认初始值为1

当不需要使用特殊配置时,可以直接使用宏变量赋值

1
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

来代替该函数

pthread_mutex_destory

删除一个互斥锁

1
int pthread_mutex_destroy(pthread_mutex_t *mutex);

一般在结束时,不再使用该锁时,进行调用删除

pthread_mutex_lock

顾名思义,上锁

1
int pthread_mutex_lock(pthread_mutex_t *mutex);

可以简单理解为对锁进行减1操作

pthread_mutex_unlock

顾名思义,释放锁

1
int pthread_mutex_unlock(pthread_mutex_t *mutex);

可以简单理解为对锁进行++操作

配合lock使用,

在不同的线程中需要访问同一个共享内存,都需要执行上锁和解锁的操作

使用代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>

pthread_mutex_t mutex;

void *printHello(){
srand(time(NULL));
while (1)
{
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand() % 3);
printf("world!\n");
pthread_mutex_unlock(&mutex);
sleep(rand() % 3);
}
return NULL;
}

int main(){
pthread_t tid;
srand(time(NULL));
int count = 5;
pthread_mutex_init(&mutex, NULL);
//当调用成功mutex值为1
int ret = pthread_create(&tid, NULL, printHello, NULL);
if(ret != 0){
fprintf(stderr,"error : %s", strerror(ret));
exit(1);
}
while (count--)
{
pthread_mutex_lock(&mutex);
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD!\n");
pthread_mutex_unlock(&mutex);
sleep(rand() % 3);
}
pthread_cancel(tid);//检查点在sleep具体是哪一个是随机的
pthread_join(tid, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}

注意:锁的粒度应该是越小越好,访问共享操作后立即解锁

死锁

是一种现象,而不是一种新的锁机制。

产生的原因

  • 没有解锁(unlock,对同一个共享内存加锁(会出现lock两次的情况

    • 可以理解为,一个唯一会开锁的小偷把自己锁在了保险库,其他同伙想进去,等待笨比解锁,而笨比被自己锁在里头了,自己又忘记了解锁密码。偷钱活动就此失败。想要打开保险库,必须触发保险库中的机关,这个机关会把保险库的小偷给杀死。
  • 两个锁造成的死锁

    • 有两个共享数据,线程一需要同时拿到两把锁才能访问两个共享数据,
      假设

      线程2只拿到锁1,当锁2拿到后才能释放锁1

      线程1只拿到锁2,当锁1拿到后才能释放锁2

      流沙河,你下来,你上来,你过来我就下去,你下来我就上去。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
int a = 0;
int b = 0;
void *tfn(){
srand(time(NULL));
int count = 3;
while(count--){

pthread_mutex_lock(&mutex2);
sleep(rand() % 3);
b += 100;
pthread_mutex_lock(&mutex1);
a += 1;
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
printf("i m thread, a = %d , b = %d \n", a, b);
}
pthread_exit(NULL);
}

int main(){

//设定一个死锁现象
int count = 5;
pthread_t tid;
srand(time(NULL));
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);

int ret = pthread_create(&tid, NULL, tfn, NULL);
if(ret != 0){
fprintf(stderr, "error: %s\n", strerror(ret));
exit(1);
}
while(count--){

pthread_mutex_lock(&mutex1);
sleep(rand() % 3);
a += 1;
pthread_mutex_lock(&mutex2);
b += 100;
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
printf("i m main, a = %d , b = %d \n", a, b);
}
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
pthread_exit(NULL);
}

死锁的处理方式

  • 第一种死锁
    • 可以直接避免,只要你写的细心一点
  • 第二种
    • 调用trylock,线程一方拿不到锁时,主动释放锁

读写锁

写独占,读共享。写锁的优先级更高

对一个共享数据进行操作的锁,常用在读次数大于写的情况。

当线程1读操作时,其他线程也可以对其进行读操作,这是共享模式锁

当线程执行写操作上了写操作时,其他线程都无法对其进行任何操作,这是独占模式锁

pthread_rwlock_init

初始化读写锁

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

pthread_rwlock_destroy

删除锁

1
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock

设定一个读锁

pthread_rwlock_wdlock

设定一个写锁

pthread_rwlock_trywrlock

尝试写锁,非阻塞的写法

pthread_rwlock_unlock

解锁

其实读写锁的核心就14个字:

读时占用,写时共享,写锁优先级高

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>

pthread_rwlock_t rwlock;
int a = 0;

void *th_write(void *arg){
int i = (int)arg;
int olda;
while (1)
{ olda = a;
usleep(1000);
pthread_rwlock_wrlock(&rwlock);
printf("====write :%d olda = %d, a = %d \n", i, olda, ++a);
pthread_rwlock_unlock(&rwlock);
usleep(5000);
}
}

void *th_read(void *arg){
int i = (int)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read :%d a = %d\n", i, a);
pthread_rwlock_unlock(&rwlock);
usleep(900);
}
}

int main(int argc, char *argv[]){

if(argc != 3){
printf("./name rtnums wtnums\n");
exit(1);
}
int count_r = atoi(argv[1]);
int count_w = atoi(argv[2]);

int i;
pthread_t tidr[count_r];
pthread_t tidw[count_w];
pthread_rwlock_init(&rwlock, NULL);
for(i = 0; i < count_w; i++){
int ret = pthread_create(&tidw[i], NULL, th_write, (void*)i);
if(ret != 0){
fprintf(stderr, "error:%s\n", strerror(ret));
exit(1);
}
}
for (i = 0; i < count_r; i++)
{
int ret = pthread_create(&tidr[i], NULL, th_read, (void*)i);
if(ret != 0){
fprintf(stderr, "error:%s\n", strerror(ret));
exit(1);
}
}
for (i = 0; i < count_r; i++)
{
pthread_join(tidr[i], NULL);
}
for (i = 0; i < count_w; i++)
{
pthread_join(tidw[i], NULL);
}

return 0;
}

条件变量

条件变量本身不是锁,但是一样造成线程阻塞,说白了就是在互斥锁中进行条件判断,给多线程提供一个会合的场所

pthread_cond_wait

阻塞等待一个条件变量

1
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 第一个,老套路,一个结构体
  • 第二个就有意思了,是一个互斥锁结构体。

一个看就知道这个函数不是等闲之辈

重点就是这个函数,有三个作用

  • 阻塞等待条件变量cond满足
  • 释放已掌握的互斥锁,实际上就是unlock(&mutex);
    • 需要注意的是上方两个作用实际上只是一个原子操作。
  • 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新组阻塞并重新申请获取互斥锁相当于 lokc(&mutex);

注意这个唤醒,

有两种唤醒方式

  • pthread_cond_signal
    • 唤醒一个
  • pthread_cond_broadcast(广播)

pthread_cond_signal

唤醒满足条件变量的上的至少一个阻塞的线程

pthread_cond_broadcast

唤醒所有的满足的条件变量阻塞的线程

pthread_cond_timedwait

设定一个默认时间自动唤醒的wait

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

相较于一般的wait,多了一个结构体参数

const struct timespec abstime

  • 绝对时间的意思
  • 绝对时间实际上也是相对的,从1970年1月1号0000开始计时
  • time_t tv_sec 秒
  • long tv_nsec 纳秒

想要定时5秒钟实际上需要使用操作

1
2
3
time_t cur =time(NULL)
struct timespec t;
t.tv_sec = cur + 5;

生产者消费者模型实现

一个典型的线程同步的模型

生产者,产品,消费者

生产者进程负责生产产品

消费者进程负责消费产品

一个简单的实现代码

这里是定义了一个简单的链表节点,一个生产者每次生产一个,一个消费者也每次消费一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct msg
{
struct msg *next;
int num;
};

struct msg *head;
struct msg *mp;

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p){
for(;;){
pthread_mutex_lock(&lock);
while (head == NULL)//这里用while是精髓
{
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);
printf("-Consume ----%d\n", mp->num);
free(mp);
sleep(rand() % 5);
}
}
void *producer(void *p){
for(;;){
mp = malloc(sizeof(struct msg));
mp->num = rand() %1000 + 1;
printf("-Produce ----%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(rand() % 5);
}
}

int main(){

pthread_t sid, cid;
srand(time(NULL));
pthread_create(&sid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(sid, NULL);
pthread_join(cid, NULL);
return 0;
}

条件变量的优点

相较于mutex,能减少不必要的竞争,如果没有释放,多个进程之间还会互相竞争

信号量

进化版的互斥锁,虽然也是线程库函数里的东西,但是,是可以用在进程间的锁

初始化的值是N值(与互斥锁默认值为1不同,N值是自己可以指定任意数值(必须是正整数),可以理解为锁的钥匙,资源量,*可以同时使用这把锁的进程数

出现的考量,是为了多个进程or线程需要共同访问一个的共享资源的同时,保证线程或者进程的并发性。

sem_init

1
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • sem_t sem
  • pshared 是否在进程共享
  • unsigned int value 设定的值

sem_destroy

对应的删除函数

1
int sem_destroy(sem_t *sem);

sem_wait

加锁函数,等同于lock,一样可以理解为—操作,当值为0时,进行阻塞

1
int sem_wait(sem_t *sem);

sem_trywait

尝试加锁,同理trylock,非阻塞式加锁

1
int sem_trywait(sem_t *sem);

sem_post

解锁,等同于unlock,一样可以理解为++操作。

1
int sem_trywait(sem_t *sem);

sem_timedwait

定时加锁

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

同样是绝对时间

直接看代码示例吧

比如说实现一个线程每隔五秒打印“hello world”,用户与输入立马打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sem_t lock;
char str[64] = "";

void *printHello(void *p){
for(;;){
time_t cur = time(NULL);
struct timespec t ;
t.tv_sec = cur + 5;
sem_timedwait(&lock, &t);
printf("Hellp world\n");
}
}
void *scanInput(void *p){
for(;;){
while (strlen(str) == 0){
fgets(str, sizeof(str), stdin);
}
sem_post(&lock);
memset(str, 0, 64);
}
}
int main(){

pthread_t pid, cid;
sem_init(&lock, 0, 1);
pthread_create(&pid, NULL, printHello, NULL);
pthread_create(&cid, NULL, scanInput, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}

还是典型的消费者生产者模型

上方实现的模型的一对一的进化版

一个生产者,多个产品,一个消费者,不过此时消费者在某种意义上来说也是一种生产者的角色,生产一个空,此时公共区产品使用queue(队列)表示

一个生产者,多个产品,二个消费者,生产速率和消费速率是随机的,代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>
#define NUM 5
#define CNUM 2

int queue[NUM];
sem_t blank_number, product_number;
sem_t consumer_number;

void *producer(){

int i = 0;
while (1){
sem_wait(&blank_number);
queue[i] = i + 1;
printf("Produce----%d\n", queue[i]);
sem_post(&product_number);
i = (i+1) % NUM;//循环赋值
sleep(rand() % 1);//模拟生产产品时间,随机数
}
}

void *consumer(void *arg){

int j = (int)arg;
int i = 0;
while (1){
sem_wait(&product_number);
sem_wait(&consumer_number);
while (queue[i] == 0){
i = (i+1) % NUM;
}
printf("Consumer NO.%d----%d\n", j, queue[i]);
queue[i] = 0;
sem_post(&consumer_number);
sem_post(&blank_number);
i = (i+1) % NUM;//循环遍历
sleep(rand() % 3);//随机数,模拟消费者对产品的消化时间
}
}

int main(){

pthread_t cid[CNUM], pid;
sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0);
sem_init(&consumer_number, 0, CNUM);
pthread_create(&pid, NULL, producer, NULL);
for (int i = 0; i < CNUM; i++){
pthread_create(&cid[i], NULL, consumer, (void *)i);
}
for (int i = 0; i < CNUM; i++){
pthread_join(cid[i], NULL);
}
pthread_join(pid, NULL);
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;

}

注意这其实只是一对多的情况,一个生产者,多个产品,多个消费者,双方的速率也都是随机的,而且没有将“拿出”和“消耗”的产品的操作单独分开,比较简陋

单纯讨论线程间的同步问题,双方速率和产品 暂且不论,有两个关键变量,生产者个数,消费者个数,就有4种情况

  • 一生产,一消费。

  • 多生产,一消费。

  • 一生产,多消费。
  • 多生产,多消费。

思想是一样的,每有需要进行同步的线程组,就需要设定一个锁(不是绝对,根据需要

生产者与消费者之间,就需要一个锁,而消费者与消费者之间也会需要一个锁,确保每个消费者都能拿到数据

进程间的互斥量

如果想要将这些线程库中的锁给进程使用呢?

这个时候就需要在进行锁的定义时对锁的属性进行修改

进程的锁

定义一个锁属性的结构体

1
pthread_mutexattr_t mutexattr;

需要对这个属性在进行初始化

1
pthread_mutexattr_init(&mutexattr);

调用设置属性函数

1
pthread_mutexattr_setpshared(&mutexattr, int pshared);

注意第二个参数的取值

  • PTHREAD_PROCESS_SHARED(公开给进程使用的锁)
  • PTHREAD__PROCESS_PRIVATE(线程私有的锁);

当不需要使用该属性结构体时同样需要调用destroy函数对属性进行删除

1
pthread_mutexattr_destroy(&mutexattr);

文件锁

基于fcntl实现,说实话我都忘了咋怎么用了,隐约记得一般用来修改文件的属性。

锁的设置,区别不大其实,只不过换了个函数实现而已

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

需要用到的宏参,第二个参数

  • F_SETLK 设置文件锁(trylock)
  • F_SETLKW 设置文件锁 (lokc) W wait
  • F_GETLK 获取当前文件锁

要设置的锁的结构体,同时也是第三个参数

struct flock(这里列用到的数据)

  • …..
  • short l_type;
    • 锁的类型
    • F_RDLCK(读锁)
    • F_WRLCK(写锁)
    • F_UNLCK(无文件锁)
  • short l_whence
    • 文件的指针的起始位置
      • SEEK_SET
      • SEEK_END
      • SEEK_CUR
  • off_t l_start
    • 从哪开始出现锁的位置,相对于起始位置的偏移量
  • off_t l_len
    • 要锁的字节量
    • 特殊值0 ,所有的
  • pid_t l_pid
    • 当使用F_GETlK时才会获取到该锁的进程id

使用代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int argc, char *argv[]){
if(argc != 2){
printf("filelock r/w\n");
exit(1);
}

int fd = open("文件锁文件.txt", O_CREAT|O_RDWR, 0777);
if(fd < 0){
perror("open");
exit(1);
}
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if(strcmp(argv[1], "r") == 0){
lock.l_type = F_RDLCK;
}else{
lock.l_type = F_WRLCK;
}
fcntl(fd, F_SETLKW, &lock);
printf("get flokc\n");
sleep(10);//这个10s假设我在进行写操作
printf("unlock!\n");
return 0;

}

开了两个终端测试发现,文件锁的规则其实就是,写时独占,读时共享

注意这是,进程间独占的锁机制

在线程中实际上使用的读写锁

哲学家进餐

五个哲学家在一个餐桌上吃饭,每个人只有一根筷子,想要吃上中间的菜,一个人需要完整的一双筷子(

一个人想要吃菜,就拿下一个人的筷子(咦惹

1—-2

2—-3

3—-4

4—-5

5—-1

这个时候如果5个人同时想吃菜,五个人都拿了下一个人的筷子,自己的筷子同时也被前一个人拿走了,这个时候就出现一个一比较尴尬的状态。如果此时谁也不肯放那么就没有人能够吃菜。

代码模拟一下这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#define COUNT 5
pthread_mutex_t mutex[COUNT];

void *diner(void *arg){
int i = (int)arg;
while (1)
{
if(i == 4){

pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[0]);
printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
pthread_mutex_unlock(&mutex[0]);

}else{
pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[i+1]);
printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
pthread_mutex_unlock(&mutex[i+1]);

}

}
pthread_exit(NULL);
}


int main(){
int i;
pthread_t tid[COUNT];
for (i = 0; i < COUNT; i++)
{
pthread_mutex_init(&mutex[i], NULL);
}
for ( i = 0; i < COUNT; i++)
{
int ret = pthread_create(&tid[i], NULL, diner, (void*)i);
if(ret != 0){
fprintf(stderr, "error:%s", strerror(ret));
exit(1);
}
}
for ( i = 0; i < COUNT; i++)
{
pthread_join(tid[i], NULL);
}
return 0;

}

不得不说我运气有点好。运行了好5秒都没出现死锁现象,还以为写错了,好在第二次进行运行时死锁了。

死锁了咋整呢?

有三种解决方案

  • 有个人主动放弃拿自己的筷子,先去拿别人的筷子,这样就能保证自己的筷子能被下一个人拿到凑成两个。
  • 再定义一个互斥量,用来锁住其他人拿到筷子,保证有一个人拿到一双
  • 嘶,想了想好像不知三种解决方案啊。比如什么不同的序号不同取筷子的顺序,或者定义一个为n-1的信号量,有一个人必须等待其他n个人执行拿筷子动作后都成功后才能拿筷子。等等。

归根结底,只要保证有一个人肯定能够拿到一双筷子就可以实现永动。

对了忘了这里再补个进程版的哲学家问题。

这里使用信号量作为锁

当然也可以用mutex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
nt main(){

int i;
sem_t *lock= mmap(NULL, sizeof(sem_t)* count, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
if(lock == MAP_FAILED){
perror("mmap");
exit(1);
}
for ( i = 0; i < count; i++)
{
sem_init(&lock[i], 1, 1);
}

pid_t pid;
for (i = 0; i < count; i++)
{
pid = fork();
if (pid == 0){
break;
}else if(pid < 0){
perror("fork\n");
exit(1);
}
}
if(i < 5){
int left, right;
if(i == 4){
left = i;
right = 0;
}else{
left = i;
right = i + 1;
}
while (1){
sem_wait(&lock[left]);
sem_wait(&lock[right]);
printf("%d: 进餐\n", i);
sem_post(&lock[left]);
sem_post(&lock[right]);
}

}else{
pid_t wpid;
do{
wpid = waitpid(-1, NULL, WNOHANG);
if(wpid < 0){
perror("error");
exit(1);
}
}while(wpid != -1);
for ( i = 0; i < count; i++)
{
sem_destroy(&lock[i]);
}
munmap(lock,sizeof(sem_t)*count);
return 0;
}

}

需要注意的事进程需要用到锁时

注意初始化的锁的属性,是否分享给进程使用。

解决方案代码

这里简单例一个比较简单的解决方式

再加一个锁,获取当有有人拿起筷子时,其他人都不能动

当然,这样原本好好的并行线程就给搞成串行了,能不能再优化呢?答案是可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pthread_mutex_t mutex[COUNT];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *diner(void *arg){
int i = (int)arg;
while (1)
{
if(i == 4){
pthread_mutex_lock(&lock);
pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[0]);
pthread_mutex_unlock(&lock);

printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
pthread_mutex_unlock(&mutex[0]);

}else{
pthread_mutex_lock(&lock);
pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[i+1]);
pthread_mutex_unlock(&lock);

printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
pthread_mutex_unlock(&mutex[i+1]);

}

}

只需要,把加的读写锁改成信号量,最多可以实现4个线程,不过有一个线程就得先当冤大头了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pthread_mutex_t mutex[COUNT];
sem_t lock;
void *diner(void *arg){
int i = (int)arg;
while (1)
{
if(i == 4){
sem_wait(&lock);
pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[0]);
printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
sem_post(&lock);

pthread_mutex_unlock(&mutex[0]);

}else{
sem_wait(&lock);
pthread_mutex_lock(&mutex[i]);
pthread_mutex_lock(&mutex[i+1]);
printf("%d:好吃!\n", i);
pthread_mutex_unlock(&mutex[i]);
sem_post(&lock);

pthread_mutex_unlock(&mutex[i+1]);
}
}

当然也可以,将奇数线程,先拿左手筷子,偶数线程先拿右手筷子,交错拿。也可以避免死锁问题

等等,思想是不变的,保证只要总有边界资源能够释放就是可以避免