Linux - 线程的同步与互斥操纵

打印 上一主题 下一主题

主题 991|帖子 991|积分 2973

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
同步与互斥概念

同步: 多个线程同时运行, 线程与线程之间可能存在某种关系, 须要让线程按照某种顺序举行执行.
   如: 线程 A 产生须要处理的数据 X, 而处理这个数据的任务交给了线程 B. 那么线程 A, B 之间的运行关系应该是, A 线程先运行, 在 A 产生了数据 X 之后, B 线程在运行举行处理
  互斥: 线程共享同一进程内的资源, 一个共享资源在被多个线程同时访问时, 就有可能会出现问题. 所以, 为了应对这种情况, 就须要让这些进程互斥的去访问这个共享资源.
   如: 多个线程同时访问同一个文件, 然后同时向文件内写入数据, 在这种场景下, 每个线程存放的数据都有可能会被打乱, 导致文件中存储的数据不完整/无法识别. 所以我们就要让这些线程互斥的去向文件中写入数据.
  锁的概念

在上面了解了同步和互斥的基本概念. 在了解锁之前还须要了解一些其他概念.
   临界区资源: 被多个线程共享访问的资源就称为临界资源
  临界区: 在每个线程内部, 访问临界区资源的代码, 就被称为临界区
  原子性: 不会被打断的操纵, 这个操纵只有两种状态, 要么完成, 要么未完成 (不存在完成一部分)
  大部分情况下, 线程使用的都是线程内的局部变量, 这些变量存储在线程的栈空间, 其他线程无法访问. 但也存在多线程共享某一个资源.
如经典例子: 卖票系统, 一共由10 0000张票, 两个线程同时售卖这10 0000张票.
  1. #include<pthread.h>
  2. #include<stdio.h>
  3. int sum = 100000000;
  4. void* func1(void* arg)
  5. {
  6.     int x = 0;
  7.     while(sum > 0)
  8.     {
  9.         --sum;
  10.     }
  11.     return NULL;
  12. }
  13. int main()
  14. {
  15.     pthread_t t1;
  16.     pthread_create(&t1, NULL, func1, NULL);
  17.     pthread_t t2;
  18.     pthread_create(&t2, NULL, func1, NULL);
  19.     pthread_t t3;
  20.     pthread_create(&t3, NULL, func1, NULL);
  21.     pthread_join(t1, NULL);
  22.     pthread_join(t2, NULL);
  23.     pthread_join(t3, NULL);
  24.     printf("sum: %d\n", sum);
  25.     return 0;
  26. }
复制代码
会得到 sum 最后的效果为 -1. 


我们来分析一下:

那么为了办理上面的问题: 就出现了锁.
对于多个线程都要访问的共享资源, 要求最多只能由一个线程举行访问. 当有线程已经在访问共享资源之后, 其他进程就不能进入访问. 互斥锁就能完成这样的功能.

   在同一时间内, 最多只能有一个线程获得锁, 然后向后执行代码, 其他没有获得锁的线程, 就会在获取锁的地方举行阻塞, 直到锁被开释并获取到锁之后, 才会继续向后执行. 
  此时, 这些线程通过竞争这把锁, 它们之间就形成了互斥关系.
  互斥锁的使用

1. 初始化互斥锁
初始化锁有两种方式: 静态初始化 和 动态初始化
  1. #include <pthread.h>
  2. // 静态初始化
  3. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  4. // 动态初始化
  5. pthread_mutex_t mutex;
  6. if (pthread_mutex_init(&mutex, NULL) != 0)
  7. {
  8.     perror("pthread_mutex_init");
  9. }
复制代码
2. 加锁. 获得锁之后访问共享资源
使用函数 pthread_mutex_lock() 函数举行加锁
  1. pthread_mutex_lock(&mutex);
  2. // 访问共享资源
复制代码
  如果获得了锁, 那么就会继续向后执行代码.
  如果锁已经被其他线程获取, 那么线程就会被阻塞在这里, 直到锁被开释, 然后再一次竞争锁.
  3. 开释锁
开释锁须要使用函数 pthread_mutex_unlock() 函数来解锁.
  1. pthread_mutex_unlock(&mutex);
复制代码
  调用这个函数后, 线程就开释了获取的锁, 如果想要再次获得锁, 就须要和全部的线程去竞争
  4. 烧毁锁
当不须要使用锁后, 可以使用函数 pthread_mutex_destroy() 函数烧毁, 开释资源.
  1. if (pthread_mutex_destroy(&mutex) != 0)
  2. {
  3.     perror("pthread_mutex_destroy");
  4. }
复制代码
  如果是通过静态初始化得到的锁, 不须要烧毁. 通过动态初始化得到的锁才须要使用这个函数举行烧毁.
  那么现在就能对上面的代码举行优化了: 
  1. #include<pthread.h>#include<stdio.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int sum = 100000000;void* func1(void* arg){    int x = 0;    pthread_mutex_lock(&mutex);    while(sum > 0)    {        --sum;    }    pthread_mutex_unlock(&mutex);    return NULL;}int main(){    pthread_t t1;    pthread_create(&t1, NULL, func1, NULL);    pthread_t t2;    pthread_create(&t2, NULL, func1, NULL);    pthread_t t3;    pthread_create(&t3, NULL, func1, NULL);    pthread_join(t1, NULL);    pthread_join(t2, NULL);    pthread_join(t3, NULL);    printf("sum: %d\n", sum);    return 0;}
复制代码

当参加了互斥锁之后, 对于共享资源的访问就变得更加安全可控了.
线程同步

上面我们使用互斥锁让线程对于共享资源的访问变得更加安全可控. 
在上面的阐明中也了解到了, 线程同步就是让线程按肯定的顺序来运行.
如: 一个线程先生产数据, 当数据生产完成后, 由另一个线程来举行处理.
   那么线程生产了数据后, 就要放入共享资源中, 这样才能被其他的线程访问并获取.
这样就须要使用锁, 线程不断地访问共享资源, 检测数据是否被生产出来, 这就须要不断地加锁解锁.由于就近原则, 这个消耗线程开释锁后, 离锁更近, 就有更大的可能性获得锁. 这样下去就有可能导致生产线程恒久无法获得锁.
  在上面的例子中, 我们须要让生产线程先运行生产数据, 当数据产生后, 再让消耗线程开始运行.
这里可以引入: 条件变量 (Condition Variable). 条件变量通常和互斥锁一起使用
1. 初始化条件变量
同样也分为两种: 静态初始化 和 动态初始化.
  1. pthread_cond_t cond; // 定义条件变量
  2. // 1. 静态初始化
  3. cond = PTHREAD_COND_INITIALIZER;
  4. // 2. 动态初始化
  5. pthread_cond_init(&cond, NULL)
复制代码
2. 等候条件变量
调用函数 pthread_cond_wait() 来举行等候.
  1. pthread_mutex_lock(&mutex); // 加锁while(!condition) // 当条件不符合时, 调用 pthread_cond_wait() 函数举行等候{    pthread_cond_wait(&cond, &mutex); // 等候条件变量, 此时线程就在这里举行了等候}                                     // 直到被唤醒, 然后线程才会向下继续向下执行pthread_mutex_unlock(&mutex);// 在调用 pthread_cond_wait() 举行等候后, mutex 锁会被开释, 后续可以被其他线程竞争锁// 当线程被唤醒后, 线程会重新去竞争锁, 当竞争成功后, 才会继续执行, 否则照旧会被阻塞
复制代码
3. 通知条件变量
有两种方法通知正在等候的线程
   1. pthread_cond_signal() 函数: 唤醒正在等候的某一个线程
  2. pthread_cond_broadcast() 函数: 唤醒全部在等候的线程
  1. pthread_mutex_lock(&mutex);  // 加锁, 并不会阻塞在这里, 因为上面的 wait 等将 mutex 开释condition = 1;pthread_cond_signal(&cond);  // 通知单个线程, 然后本线程继续向后执行// 大概使用函数 pthread_cond_broadcast(&cond);  通知全部线程, 然后继续向后执行pthread_mutex_unlock(&mutex);  // 解锁
复制代码
4. 烧毁条件变量
调用函数 pthread_cond_destroy() 举行烧毁.
  1. pthread_cond_destroy(&cond)
复制代码
 下面实现一个例子: 
  1. #include<pthread.h>#include<stdio.h>#include<unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int sum = 0;void* func1(void* arg){    pthread_mutex_lock(&mutex);    if(sum <= 0)    {        printf("线程1举行等候\n");        pthread_cond_wait(&cond, &mutex);    }    printf("线程1被唤醒\n");    pthread_mutex_unlock(&mutex);    return NULL;}void* func2(void* arg){    sleep(5);    pthread_mutex_lock(&mutex);    ++sum;    pthread_cond_signal(&cond);    pthread_mutex_unlock(&mutex);    return NULL;}int main(){    pthread_t t1;    pthread_create(&t1, NULL, func1, NULL);    pthread_t t2;    pthread_create(&t2, NULL, func2, NULL);    pthread_join(t1, NULL);    pthread_join(t2, NULL);    pthread_mutex_destroy(&mutex);    pthread_cond_destroy(&cond);    return 0;}
复制代码
运行这段代码, 能观察到屏幕上会先打印出 "线程1举行等候", 当过了5秒之后线程2唤醒线程1, 然后线程1开始执行打印 "线程1被唤醒".
死锁概念

   死锁是指多个线程在执行过程中, 因为资源抢夺造成的一种僵局.
  

发生死锁的四个须要条件:

  • 互斥哀求: 资源在某段时间内只能有一个线程访问
  • 哀求与保持: 一个线程已经获得了最少一个资源, 并且还在哀求其他资源, 但是其他资源已经被其他线程持有. 自己不会开释已经获得的资源, 而是不停哀求.
  • 不可抢占: 线程与线程之间不可逼迫抢夺资源. 一个线程已经获得了资源, 其他线程不能直接抢夺.
  • 循环等候: 线程都分别获得一些资源, 但是还须要其他线程已获取的资源. 其他线程同理, 也须要其他线程已获取的资源. 线程之间形成循环等候资源的场景.
想要粉碎死锁, 那么就只要粉碎上面的任意一个条件即可.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表