一.线程是啥
介绍:典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
进程是程序执行时的一个实例,进程本身不是基本运行单位,而是线程的容器。
总结:进程——资源分配的最小单位,线程——程序执行的最小单位
二.pthread库使用
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持,多线程开发的最基本概念主要包含三点:
1.线程(三种):①创建,②退出,③等待
2.互斥锁(四种):①创建,②销毁,③加锁,④解锁
3.条件(五种):①创建,②销毁,③触发,④广播,⑤等待
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。
1.线程
线程创建:pthread_create
原型
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
/*tidp:当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。
attr:一般设为NULL。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
(*start_rtn)(void *) :新创建的线程的入口地址。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。
arg:向start_rtn函数传递的参数。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构体的地址作为arg参数传入。
*/
返回值:成功返回0,否则返回错误编号
线程的退出pthread_exit:三种方式①线程只是从启动例程中返回,返回值是线程的退出码。②线程可以被同一进程中的其他线程取消。③线程调用pthread_exit:
原型:
int pthread_exit(void *rval_ptr);
//rval_ptr:是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
线程的等待pthread_join:
原型:
int pthread_join(pthread_t thread, void **rval_ptr);
//thread:被等待的线程ID
**rval_ptr:用户定义的指针,用来存储被等待线程的返回值。如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
返回值:成功返回0,否则返回错误编号
2.互斥锁
介绍:在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。
互斥锁创建pthread_mutex_init:
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
/*mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量
attr:互斥量的属性,一般设为NULL,要用默认的属性初始化互斥量,只需把attr设置为NULL。
*/
返回值:若成功返回0,否则返回错误编号
互斥锁销毁pthread_mutex_destroy:
原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量
返回值:若成功返回0,否则返回错误编号
互斥锁加锁pthread_mutex_lock:
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量
返回值:若成功返回0,否则返回错误编号
互斥锁解锁pthread_mutex_trylock:
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//mutex:锁的地址,需定义一个pthread_mutex_t类型的全局变量
返回值:若成功返回0,否则返回错误编号
死锁是啥:
情况1:在互斥锁默认属性的情况下,在同一个线程中不允许对同一互斥锁连续进行加锁操作,因为之前锁处于未解除状态,如果再次对同一个互斥锁进行加锁,那么必然会导致程序无限阻塞等待。
情况2:多个线程对多个互斥锁交叉使用,每一个线程都试图对其他线程所持有的互斥锁进行加锁。如下图所示情况,线程分别持有了对方需要的锁资源,并相互影响,可能会导致程序无限阻塞,就会造成死锁。
3.条件变量
介绍:条件变量是线程另一可用的同步机制
条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量创建pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
/*cond:全局变量pthread_cond_t cond的地址
attr:除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。
*/
返回值:若成功返回0,否则返回错误编号
条件变量销毁pthread_cond_destory
int pthread_cond_destroy(pthread_cond_t *cond);
返回值:若成功返回0,否则返回错误编号
条件变量等待:pthread_cond_wait
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*cond:全局变量pthread_cond_t cond的地址。
restrict mutex:全局变量pthread_mutex_t mutex的地址。
*/
返回值:若成功返回0,否则返回错误编号
条件触发pthread_cond_signal:
可以用于通知线程条件已经满足
int pthread_cond_signal(pthread_cond_t *cond);
//cond:全局变量pthread_cond_t cond的地址。
返回值:若成功返回0,否则返回错误编号
tip:调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。
条件触发(广播)pthread_cond_broadcast:
pthread_cond_broadcast(pthread_cond_t *cond);
条件满足将唤醒等待该条件的所有进程。
4.其他常用API
线程ID获取及比较
获取:
pthread_t pthread_self(void);
返回值:线程的id(长整型)
比较:
返回:若相等则返回非0值,否则返回0
更多api查看:大佬文章
举个栗子:解决两个线程对一个资源的竞态问题
要求:将全局变量data逐渐加到6,线程t1先加到三然后进程t2加到6
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex1; //互斥锁1
pthread_cond_t cond1; //条件变量1
int data=0;
void *func1(void *arg)
{
int i=3;
static int retval=1000; //退出返回数据必须设置成全局变量,不然线程结束后main里读不出
printf("t1:id=%ld\n",(unsigned long)pthread_self());//打印t1的id
printf("t1:arg=%d\n",*(int *)arg); //打印线程的传参
sleep(5);
pthread_mutex_lock(&mutex1); //拿锁
printf("t1:-------------t1 lock--------------\n");
while(i--){
sleep(1);
printf("t1:data=%d\n",++data);
}
printf("t1:unlock\n");
pthread_mutex_unlock(&mutex1); //解锁
pthread_cond_signal(&cond1); //发送信号给条件变量cond1
pthread_exit((void *)&retval); //线程退出调用
}
void *func2(void *arg)
{
int i=3;
printf("t2:id=%ld\n",(unsigned long)pthread_self());
pthread_mutex_lock(&mutex1);
printf("t2:-------------t2 wait--------------\n");
pthread_cond_wait(&cond1,&mutex1); //阻塞,等待条件cond1
printf("t2:-------------t2 lock--------------\n");
while(i--){
sleep(1);
printf("t2:data=%d\n",++data);
}
printf("t2:uclock\n");
pthread_mutex_unlock(&mutex1);
pthread_exit(NULL);
}
int main()
{
pthread_t t1;
pthread_t t2;
int arg=100;
void *retval;
printf("main:id=%ld\n",(unsigned long)pthread_self());
pthread_create(&t1,NULL,&func1,(void *)&arg); //线程创建
pthread_create(&t2,NULL,&func2,NULL);
pthread_mutex_init(&mutex1,NULL); //锁创建
pthread_cond_init(&cond1,NULL); //条件变量创建
pthread_join(t1,&retval); //等待线程退出
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex1); //销毁
pthread_cond_destroy(&cond1);
printf("retval = %d\n",*(int *)retval); //打印t1退出数据
return 0;
}