本篇文章主要内容:
- Linux系统应用编程(四)Linux多线程
- 一、线程和进程的区别
- 二、Linux多线程
- 1.线程的使用 - 创建、退出、等待
- 2.线程的同步 - 互斥量
- (1)互斥量的理解(略)
- (2)互斥量的使用
- (3)死锁
- 3.线程间通信- 条件变量
- (1)条件变量的理解
- (2)条件变量的使用
Linux系统应用编程(四)Linux多线程
一、线程和进程的区别
- 进程是静态的程序/代码,在操作系统分配的资源下运行起来用于完成特定任务的动态程序,简单说就是代码在操作系统上跑起来/运行起来就是一个进程。这个过程中,操作系统会为进程分配独立的地址空间,每个进程之间相互独立;
- 线程是进程的一条执行路径,只有独立的堆栈和局部变量,没有独立的地址空间,且线程共享进程的资源。可以说,进程是操作系统分配资源的基本单元,线程是进程执行过程中的一个单元。
- 线程相比于进程,线程之间实现通信,共享数据更方便,不需要像进程间通信使用额外的机制;线程运行切换速度快,开销小,比进程的响应速度快,更轻量级。
二、Linux多线程
1.线程的使用 - 创建、退出、等待
#include <stdio.h>
#include <pthread.h>
/* 主线程:遍历100内奇数 */
/* 子线程:遍历100内偶数 */
/* 主线程等待子线程退出后,输出其退出返回值 */
void *threadRun(void *pretn){
static int i = 0;
for(i=0;i<=100;i++){
if(i%2 == 0)printf("subThread:%d\n",i);
}
pthread_exit(pretn);
}
int main(){
pthread_t thread1;
int arg = 2023;
void *ptemp = &arg;
pthread_create(&thread1,NULL,threadRun,ptemp);
for(int i=0;i<=100;i++){
if(i%2 != 0)printf("Main:%d\n",i);
}
void *p = NULL;
pthread_join(thread1,&p);
printf("subThread return value: %d\n",*(int *)p);
return 0;
}
2.线程的同步 - 互斥量
- 经典同步问题:银行储户取钱(C语言版,Java版储户存钱见Java多线程 7.编程练习)
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
/* 银行账户 */
typedef struct Account {
int balance; //余额
int (*dramMoney)(int *,int); //取钱方法
int (*saveMoney)(int *,int); //存钱方法(本例未实现)
}Account;
/* 取钱方法实现 */
int dramMoney(int *bal,int cash){
while(*bal>0 && cash>0){ //余额,取钱金额大于0,可以取钱
usleep(20*1000); //由于未进行同步,即使进行条件判断,也会出现错误
*bal -= cash; //取钱:余额=余额-取出的金额
printf("(tid=%ld)Dram Money:%d$ succeed,now balance:%d$\n",pthread_self(),cash,*bal);
}
return *bal;
}
/* 客户取钱(一个线程=一个客户) */
void *customer(void *acc){
Account *ptmp = (Account *)acc;
ptmp->dramMoney(&ptmp->balance,1000);
pthread_exit(acc);
}
int main(){
/* 初始化:账户里有18000 */
Account *acc = (Account *)malloc(sizeof(Account));
acc->balance = 18000;
acc->dramMoney = dramMoney;
/* 创建两个线程模拟两个客户同时取钱 */
pthread_t t1,t2;
pthread_create(&t1,NULL,customer,acc);
pthread_create(&t2,NULL,customer,acc);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
- 可以看到,出现了线程安全问题。接下来使用互斥量对其进行修改
(1)互斥量的理解(略)
互斥量(mutex)本质上是一把锁,在访问共享资源前对互斥量进行加锁,访问完成后释放锁。加锁状态下,其他线程阻塞等待,防止多个线程同时访问相同的共享资源。
(2)互斥量的使用
- 银行储户取款(同步版)
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
/* 银行账户 */
typedef struct Account {
int balance; //余额
int (*dramMoney)(int *,int); //取钱方法
int (*saveMoney)(int *,int); //存钱方法(本例未实现)
}Account;
pthread_mutex_t mutex;
/* 取钱方法实现 */
int dramMoney(int *bal,int cash){
while(1){ //余额,取钱金额大于0,可以取钱
pthread_mutex_lock(&mutex);
if(*bal > 0) { //如果未进行同步,即使进行条件判断,也会出现错误
usleep(50 * 1000);
*bal -= cash; //取钱:余额=余额-取出的金额
printf("(tid=%ld)Dram Money:%d$ succeed,now balance:%d$\n", pthread_self(), cash, *bal);
}else{
pthread_mutex_unlock(&mutex);
break;
}
pthread_mutex_unlock(&mutex);
}
return *bal;
}
/* 客户取钱(一个线程=一个客户) */
void *customer(void *acc){
Account *ptmp = (Account *)acc;
ptmp->dramMoney(&ptmp->balance,1000);
pthread_exit(acc);
}
int main(){
pthread_mutex_init(&mutex,NULL);
/* 初始化:账户里有180000 */
Account *acc = (Account *)malloc(sizeof(Account));
acc->balance = 180000;
acc->dramMoney = dramMoney;
/* 创建两个线程模拟两个客户同时取钱 */
pthread_t t1,t2;
pthread_create(&t1,NULL,customer,acc);
pthread_create(&t2,NULL,customer,acc);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
PS:原先设置账户余额1800,发现cpu速度太快第二个线程还没抢到锁运行就结束,改成18万就能看到线程切换了 如果是ubuntu虚拟机,把cpu设置成1核就可以看到线程切换运行了,前面发现只有单线程再跑,还以为是代码有问题
(3)死锁
死锁是指在多进程或多线程下,由于进程/线程之间竞争获取共享资源,导致进程/线程间彼此都相互等待对方释放所持有的资源,使程序所有线程处于无法继续执行的无限等待状态。<如何避免死锁?>规划设计好代码中线程对同步锁的操作,避免嵌套同步、也尽量减少同步资源的定义以避免出现死锁。
3.线程间通信- 条件变量
(1)条件变量的理解
条件变量是Linux线程同步和线程通信的一种机制,条件变量可以使线程进入等待,也可以唤醒等待中的进程,以确定何时执行某些操作,例如等待某个资源的可用性;条件变量和互斥量配合使用,可以实现线程同步(线程安全)的线程间通信,也可以解决某个线程长时间得不到锁处于等待状态(线程饥饿)。例如:在多线程环境中实现线程安全的线程间通信,避免同一时间的并发访问共享资源。
(2)条件变量的使用
用法上很像Java的wait()、notify()实现线程间通信的方法。
- 银行储户取钱(使用条件变量+互斥量实现两个储户交替取钱)
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
/* 银行账户 */
typedef struct Account {
int balance; //余额
int (*dramMoney)(int *,int); //取钱方法
int (*saveMoney)(int *,int); //存钱方法(本例未实现)
}Account;
pthread_mutex_t mutex;
pthread_cond_t cond;
/* 取钱方法实现 */
int dramMoney(int *bal,int cash){
while(1){ //余额,取钱金额大于0,可以取钱
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
if(*bal > 0) {
usleep(50 * 1000); //由于未进行同步,即使进行条件判断,也会出现错误
*bal -= cash; //取钱:余额=余额-取出的金额
printf("(tid=%ld)Dram Money:%d$ succeed,now balance:%d$\n", pthread_self(), cash, *bal);
/* 取完钱等待并释放锁给另一个线程;另一个线程得到锁就上锁再唤醒它,依次循环实现交替执行 */
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
}else{
pthread_mutex_unlock(&mutex);
break;
}
}
return *bal;
}
/* 客户取钱(一个线程=一个客户) */
void *customer(void *acc){
Account *ptmp = (Account *)acc;
ptmp->dramMoney(&ptmp->balance,1000);
pthread_exit(acc);
}
int main(){
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
/* 初始化:账户里有90000 */
Account *acc = (Account *)malloc(sizeof(Account));
acc->balance = 9000;
acc->dramMoney = dramMoney;
/* 创建两个线程模拟两个客户同时取钱 */
pthread_t t1,t2;
pthread_create(&t1,NULL,customer,acc);
pthread_create(&t2,NULL,customer,acc);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}