提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Linux线程、线程创建-退出-等待、互斥锁、条件控制线程同步
提示:以下是本篇文章正文内容,下面案例可供参考
一、线程是什么?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流。
一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1.进程与线程
- 在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
- 进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
“进程——资源分配的最小单位,线程——程序执行的最小单位”
从函数调用上来说,进程创建使用fork()操作;线程创建使用clone()操作。
2.线程优势
- 线程共享进程代码段,节省空间。
- 线程通信机制方便。
二、线程API
头文件
#include<pthread.h>;
| 对象 | 操作 | Linux Pthread API |
|---|---|---|
| 线程 | 创建 | pthread_create() |
| 线程 | 退出 | pthread_exit() |
| 线程 | 等待 | pthread_join() |
| 线程 | 获取ID | pthread_self() |
常用命令:
- ps -aux|grep a.out 查看正在运行的线程
- kill xxxxx xxxxx代表上一步显示的线程序号
1.具体API参数
1. 线程创建
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
//tidp 指向新创建线程的线程ID;
//attr 定制线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
//start_rtn 使用start_rtn函数指针,指向所需要操作的函数。
//arg 利用arg参数给该线程传参。
2. 线程退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit。
#include <pthread.h>
int pthread_exit(void *rval_ptr);
//进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
3. 线程等待
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号
//rval_ptr是个二级指针
4. 线程脱离
pthread_detach函数把指定的线程转变为脱离状态。一般使用频率较少。
#include <pthread.h>
int pthread_detach(pthread_t thread);
// 返回:若成功返回0,否则返回错误编号
本函数通常由想让自己脱离的线程使用,就如以下语句:
pthread_detach(pthread_self());
5. 线程ID获取及比较
#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID
对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,
因为不同系统对线程ID的定义可能不一样。我们应该要用下边的函数:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 返回:若相等则返回非0值,否则返回0
6.线程例子
#include <stdio.h>
#include <pthread.h>
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
void *funcl1(void *arg) //pthread_create所需调用函数
{
static int ret =10;
printf("t1: %ld,thread is create\n ",(unsigned long)pthread_self());//进程ID获取
printf("t1: the param is %d\n",*((int *)arg)); //pthread_create传参
pthread_exit((void *)&ret); //进程退出
}
void *funcl2(void *arg) //与funcl1相似,只是将ret由整型转换为字符型*p
{
static char *p="t2 id run out";
printf("t2: %ld,thread is create\n ",(unsigned long)pthread_self());
printf("t2: the param is %d\n",*((int *)arg));
pthread_exit((void *)p);
}
int main()
{
int ret;
// pthread_t *t1= NULL;
int *pret=NULL;
pthread_t t1;
int arg=100;
ret=pthread_create(&t1,NULL,funcl1,(void *)&arg); //pthread_create线程创建
if(ret ==0 ){
printf("main: thread t1 create success\n");
}
printf("main: %ld,t1 thread is create\n ",(unsigned long)pthread_self());
pthread_join(t1,(void **)&pret);//进程等待
printf("main: t1 quit: %d\n",*pret);
//下面,重复上面代码实现字符型输出
int ret2;
char *pret2=NULL;
pthread_t t2;
int arg2=1000;
ret2=pthread_create(&t2,NULL,funcl2,(void *)&arg2);
if(ret2 ==0 ){
printf("main: thread t2 create success\n");
}
printf("main: %ld,t2 thread is create\n ",(unsigned long)pthread_self());
pthread_join(t1,(void **)&pret2);
printf("main: t2 quit: %s\n",pret2);
perror("why");
return 0;
}
ps:多个线程对同一个变量,是共享的状态。因此,需要特定线程获取变量时,必须控制其他变量,比如使用:pthread_exit();
2.互斥锁
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。其他线程需要等待,等待到当前线程释放该互斥锁,这样保证每次只有一个线程向前运行。
1. 创建及销毁互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
//创建锁
//销毁锁
// 返回:若成功返回0,否则返回错误编号
//要用默认的属性初始化互斥量,只需把attr设置为NULL。
2. 加锁及解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
//加锁
//尝试加锁
//解锁
// 返回:若成功返回0,否则返回错误编号
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
3.互斥锁例子(线程例子加以改动)
在线程的例子上做改动,结合线程的例子一起看。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_mutex_t mutex; //互斥锁
int g_data=0;
void *funcl1(void *arg)
{
static int ret =10;
printf("t1: %ld,thread is create\n ",(unsigned long)pthread_self());
printf("t1: the param is %d\n",*((int *)arg));
pthread_mutex_lock(&mutex); //加锁
while(1){
printf("t1:%d\n",g_data++);
sleep(1);
if(g_data==3){
pthread_mutex_unlock(&mutex); //解锁
// pthread_exit(NULL);
printf("t1:quit=======================\n");
exit(0);
}
}
}
void *funcl2(void *arg)
{
static char *p="t2 id run out";
printf("t2: %ld,thread is create\n ",(unsigned long)pthread_self());
printf("t2: the param is %d\n",*((int *)arg));
while(1){
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex); //加锁
g_data++;
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL); //创建锁
int ret;
pthread_t t1;
int arg=100;
ret=pthread_create(&t1,NULL,funcl1,(void *)&arg);
if(ret ==0 ){
printf("main: thread t1 create success\n");
}
printf("main: %ld,t1 thread is create\n ",(unsigned long)pthread_self());
pthread_t t2;
int arg2=1000;
ret=pthread_create(&t2,NULL,funcl2,(void *)&arg2);
if(ret ==0 ){
printf("main: thread t2 create success\n");
}
printf("main: %ld,t2 thread is create\n ",(unsigned long)pthread_self());
while(1){
printf("main:%d\n",g_data);
sleep(1);
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex); //销毁锁
perror("why");
return 0;
}
ps:死锁:前提是存在两个锁,线程A先拿到锁a,再拿锁b;线程B先拿到锁b,再拿锁a;就会产生死锁。
3.条件控制线程
- 条件变量是线程另一可用的同步机制。
- 条件变量给多个线程提供了一个会合的场所。
- 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
- 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
ps:需要给条件变量进行初始化:
分为静态初始化和动态初始化:
静态:
pthread_cond_t = PTHREAD_COND_INITIALIZER;
pthread_mutux_t = PTHREAD_MUTIX_INITIALIZER;
动态:
pthread_cond_init(&cond, NULL);
pthread_mutux_init(&mutux, NULL);
1. 创建及销毁条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号
除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。
2. 等待
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
// 返回:若成功返回0,否则返回错误编号
pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。
函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。
3. 触发
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号
这两个函数可以用于通知线程条件已经满足。
pthread_cond_signal函数将唤醒等待该条件的某个线程。
pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
ps:一定要在改变条件状态以后再给线程发信号。
4.条件控制例子(基于上述两个例子改动)
程序运行后,先堵塞t1,触发t2。然后t2线程运行三次,t1线程运行一次。实现条件控制线程。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_mutex_t mutex; //全局变量定义互斥锁mutux和条件变量cond
pthread_cond_t cond;
int g_data=0;
void *funcl1(void *arg)
{
static int ret =10;
int cnt=0;
printf("t1: %ld,thread is create\n ",(unsigned long)pthread_self());
printf("t1: the param is %d\n",*((int *)arg));
while(1){
pthread_cond_wait(&cond,&mutex); //等待条件
printf("t1:%d\n",g_data);
printf("t1:3---0=======================\n");
g_data=0;
sleep(1);
if(cnt++==5){
exit(1);
}
}
}
void *funcl2(void *arg)
{
static char *p="t2 id run out";
printf("t2: %ld,thread is create\n ",(unsigned long)pthread_self());
printf("t2: the param is %d\n",*((int *)arg));
while(1){
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex);
g_data++;
if(g_data==3){
pthread_cond_signal(&cond); //单一触发锁
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL); //动态初始化mutux和cond
pthread_cond_init(&cond,NULL);
int ret;
pthread_t t1;
int arg=100;
ret=pthread_create(&t1,NULL,funcl1,(void *)&arg);
if(ret ==0 ){
printf("main: thread t1 create success\n");
}
pthread_t t2;
ret=pthread_create(&t2,NULL,funcl2,(void *)&arg);
if(ret ==0 ){
printf("main: thread t2 create success\n");
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex); //销毁锁和条件
pthread_cond_destroy(&cond);
perror("why");
return 0;
}
PS:想要导出测试结果命令:./a.out >>test.ret.txt &
总结
参考文章:https://www.cnblogs.com/xiehongfeng100/p/4620852.html
https://www.cnblogs.com/keep–fighting/p/15489807.html
本文详细介绍了Linux线程的概念,包括进程与线程的区别、线程的优势。接着,讲解了线程API的使用,如线程创建、退出、等待、线程ID获取以及互斥锁和条件控制线程同步的实现,通过实例阐述了如何避免死锁。最后,总结了线程在Linux系统中的重要性和应用。

被折叠的 条评论
为什么被折叠?



