多线程编程基础知识总结!
来源:嵌入式大杂烩发布时间:2023-05-041145浏览
询问 AI
进程是指正在运行的程序,它拥有独立的内存空间和系统资源,不同进程之间的数据不共享。
线程是进程内的执行单元,它与同一进程内的其他线程共享进程的内存空间和系统资源。
多线程是一种并发编程方式,它的优势包括:
- 提高程序的响应速度和运行效率(多核CPU下的多线程)
- 充分利用CPU资源,提高系统的利用率
- 支持多个任务并行执行,提高程序的可扩展性和可维护性
Linux下C语言多线程编程依赖于pthread多线程库。pthread库是Linux的多线程库,是POSIX标准线程API的实现,它提供了一种创建和操纵线程的方法,以及一些同步机制,如互斥锁、条件变量等。
头文件:
#include<pthread.h>
编译链接需要链接链接库 pthread。
/**
*@brief创建一个线程
*
*Detailedfunctiondescription
*
*@param[in] thread:一个指向线程标识符的指针,线程调用后,该值被设置为线程ID;pthread_t为unsigned long int
*@param[in]attr:用来设置线程属性
*@param[in]start_routine:线程函数体,线程创建成功后,thread指向的内存单元从该地址开始运行
*@param[in]arg:传递给线程函数体的参数
*
*@return线程创建成功,则返回0,失败则返回错误码,并且thread内容是未定义的
*/
intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
例子test.c:创建一个线程,每1s打印一次。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
void*thread_fun(void*arg)
{
s_thread_running=1;
while(s_thread_running)
{
printf("threadrun...\n");
sleep(1);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread_id,NULL);///<阻塞等待线程结束
if(ret!=0)
{
printf("pthread_joinerror!\n");
exit(EXIT_FAILURE);
}
printf("AfterThread\n");
exit(EXIT_SUCCESS);
}
编译、运行:
gcctest.c-otest-lpthread

/**
*@brief等待某个线程结束
*
*Detailedfunctiondescription:这是一个线程阻塞函数,调用该函数则等到线程结束才继续运行
*
*@param[in]thread:某个线程的ID
*@param[in]retval:用于获取线程start_routine的返回值
*
*@return线程创建成功,则返回0,失败则返回错误码,并且thread内容是未定义的
*/
intpthread_join(pthread_tthread,void**retval);
例子test.c:创建一个线程,进行一次加法运算就返回。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
void*thread_fun(void*arg)
{
staticintres=0;
inta=1,b=2;
res=a+b;
sleep(1);
printf("threadrun,a+b=%d,addr=%p\n",res,res);
pthread_exit(res);
}
intmain(void)
{
intret=0;
int*retval=NULL;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("pthread_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread_id,(void**)retval);///<阻塞等待线程结束
if(ret!=0)
{
printf("pthread_joinerror!\n");
exit(EXIT_FAILURE);
}
if(retval!=NULL)
{
printf("AfterThread,retval=%d,addr=%p\n",(int)*retval,retval);
}
exit(EXIT_SUCCESS);
}
编译、运行:

/**
*@brief退出线程
*
*Detailedfunctiondescription
*
*@param[in]retval:它指向的数据将作为线程退出时的返回值
*
*@returnvoid
*/
voidpthread_exit(void*retval);
- 线程将指定函数体中的代码执行完后自行结束;
- 线程执行过程中,被同一进程中的其它线程(包括主线程)强制终止;
- 线程执行过程中,遇到 pthread_exit() 函数结束执行。
例子test.c:创建一个线程,每个1s打印一次,打印超过5次时调用pthread_exit退出。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
conststaticchar*thread_exit_str="thread_exitok!";
void*thread_fun(void*arg)
{
staticintcnt=0;
s_thread_running=1;
while(s_thread_running)
{
cnt++;
if(cnt>5)
{
pthread_exit((void*)thread_exit_str);
}
printf("threadrun...\n");
sleep(1);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
void*thread_res=NULL;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread_id,(void**)thread_res);
if(ret!=0)
{
printf("thread_joinerror!\n");
exit(EXIT_FAILURE);
}
printf("AfterThread,thread_res=%s\n",(char*)thread_res);
exit(EXIT_SUCCESS);
}
编译、运行:

使用return退出线程与使用pthread_exit退出线程的区别?
return为通用的函数退出操作,pthread_exit专用与线程,既然pthread库有提供专门的函数,自然用pthread_exit会好些,虽然使用return也可以。
看看return退出线程与使用pthread_exit退出线程的具体区别:退出主线程。使用pthread_exit退出主线程只会终止当前线程,不会影响进程中其它线程的执行;使用return退出主线程,主线程退出执行很快,所有线程都会退出。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
conststaticchar*thread_exit_str="thread_exitok!";
void*thread_fun(void*arg)
{
sleep(1);
printf("thread_funrun...\n");
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
void*thread_res=NULL;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
printf("mainthreadexit\n");
pthread_exit(NULL);
}
编译、运行:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
conststaticchar*thread_exit_str="thread_exitok!";
void*thread_fun(void*arg)
{
sleep(1);
printf("thread_funrun...\n");
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
void*thread_res=NULL;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
printf("mainthreadexit\n");
return0;
}
编译、运行:

/**
*@brief用来获取当前线程ID
*
*Detailedfunctiondescription
*
*@param[in]void
*
*@return返回线程id
*/
pthread_tpthread_self(void);
例子:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
conststaticchar*thread_exit_str="thread_exitok!";
void*thread_fun(void*arg)
{
staticintcnt=0;
s_thread_running=1;
while(s_thread_running)
{
cnt++;
if(cnt>5)
{
pthread_exit((void*)thread_exit_str);
}
printf("threadrun(tid=%ld)...\n",pthread_self());
sleep(1);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
void*thread_res=NULL;
pid_tpid=getpid();
printf("pid=%d\n",pid);
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread_id,(void**)thread_res);
if(ret!=0)
{
printf("thread_joinerror!\n");
exit(EXIT_FAILURE);
}
printf("AfterThread,thread_res=%s\n",(char*)thread_res);
exit(EXIT_SUCCESS);
}
编译、运行:

/**
*@brief分离线程
*
*Detailedfunctiondescription:分离线程,线程结束是系统自动回收线程的资源
*
*@param[in]thread:某个线程的ID
*
*@return成功时返回0,失败返回其他值
*/
intpthread_detach(pthread_tthread);
pthread_create创建的线程有两种状态:joinable(可结合的)和unjoinable(不可结合的/分离的)。默认是joinable 状态。
一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的,所以以默认的属性创建线程时,创建的线程时可结合的,我们需要对线程退出时调用pthread_join对线程资源进行回收。只有当pthread_join函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
一个不可结合的线程,线程结束后会自动释放占用资源。
因为pthread_join是一个阻塞的操作,而大多数时候主线程并不希望因为调用pthread_join而阻塞,并且大多数情况下不会使用线程函数体的返回值,所以这时候可以把线程创建为不可结合的/分离的。
把线程创建为不可结合的/分离的有两种方式:
在创建线程之后,使用pthraad_detach分离线程。
在创建线程之前,使用pthread_attr_setdetachstate设置线程以不可结合的/分离的状态创建。
例子:在创建线程之后,使用pthraad_detach分离线程。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
void*thread_fun(void*arg)
{
s_thread_running=1;
while(s_thread_running)
{
printf("childthreadrun...\n");
sleep(1);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
printf("BeforeThread\n");
ret=pthread_create(s_thread_id,NULL,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_detach(s_thread_id);
if(ret!=0)
{
printf("pthread_detacherror!\n");
exit(EXIT_FAILURE);
}
printf("AfterThread\n");
while(1)
{
printf("mainthreadrun...\n");
sleep(1);
}
exit(EXIT_SUCCESS);
}
编译、运行:

- pthread_detach()即主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收。
- pthread_join()即是子线程合入主线程,主线程会一直阻塞,直到子线程执行结束,然后回收子线程资源,并继续执行。
/**
*@brief初始化一个线程对象的属性
*
*Detailedfunctiondescription
*
*@param[in]attr:指向一个线程属性的指针
*
*@return成功时返回0,失败返回其他值
*/
intpthread_attr_init(pthread_attr_t*attr);
如果不设置线程属性,线程则以默认属性进行创建,默认的属性值如:

例子:在创建线程之前,使用pthread_attr_setdetachstate设置线程以不可结合的/分离的状态创建。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread_id;
staticunsignedchars_thread_running=0;
void*thread_fun(void*arg)
{
s_thread_running=1;
while(s_thread_running)
{
printf("threadrun...\n");
sleep(1);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
printf("BeforeThread\n");
pthread_attr_tattr;
ret=pthread_attr_init(attr);
if(ret!=0)
{
printf("pthread_attr_initerror!\n");
exit(EXIT_FAILURE);
}
pthread_attr_setdetachstate(attr,PTHREAD_CREATE_DETACHED);///<线程以分离的状态创建
ret=pthread_create(s_thread_id,attr,thread_fun,NULL);
if(ret!=0)
{
printf("thread_createerror!\n");
exit(EXIT_FAILURE);
}
printf("AfterThread\n");
pthread_attr_destroy(attr);///<销毁线程属性结构
while(1)
{
sleep(1);
}
exit(EXIT_SUCCESS);
}
互斥锁用于保护一些公共资源。一些公共资源有可能会被多个线程共同使用,如果不做资源保护,可能会产生 意想不到的bug。
一个线程,如果需要访问公共资源,需要获得互斥锁并对其加锁,资源在在锁定过程中,如果其它线程对其进行访问,也需要获得互斥锁,如果获取不到,线程只能进行阻塞,直到获得该锁的线程解锁。
互斥锁API:
#include<pthread.h>
///<创建互斥对象,用指定的初始化属性初始化互斥对象
intpthread_mutex_init(pthread_mutex_t*mutex,
constpthread_mutex_attr_t*mutexattr);
///<加锁
intpthread_mutex_lock(pthread_mutex_t*mutex);
///<解锁
intpthread_mutex_unlock(pthread_mutex_t*mutex);
///<加锁,但是如果对象已经上锁则返回EBUSY错误代码而不阻塞
intpthread_mmutex_trylock(pthread_mutex_t*mutex);
///<析构并释放mutex相关资源
intpthread_mutex_destroy(pthread_mutex_t*mutex);
互斥锁有两种创建方式:
- 静态创建:
pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;
- 动态创建:
pthread_mutex_tmutex;
pthread_mutex_init(mutex,NULL);
pthread互斥锁属性包括:
- PTHREAD_MUTEX_TIMED_NP:这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将会形成一个等待队列,并在解锁后按优先级获得锁。这种策略可以确保资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁。允许同一个线程对同一个锁成功获得多次,并通过unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP:检错锁。如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同,这样就保证了当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,动作最简单的锁类型,仅等待一小段时间,如果不能获得锁就放弃等待
互斥锁使用形式:
pthread_mutex_tmutex;
pthread_mutex_init(mutex,NULL);///<初始化互斥锁
pthread_mutex_lock(mutex);///<加锁
///<操作公共资源
pthread_mutex_unlock(mutex);///<解锁
pthread_mutex_destroy(mutex);///<销毁互斥锁
例子:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread1_id;
staticpthread_ts_thread2_id;
staticunsignedchars_thread1_running=0;
staticunsignedchars_thread2_running=0;
staticpthread_mutex_ts_mutex;
staticints_cnt=0;
void*thread1_fun(void*arg)
{
printf("[%s]pthread_mutex_lock------s_cnt=%d\n",__FUNCTION__,s_cnt);
pthread_mutex_lock(s_mutex);///<加锁
for(size_ti=0;i<100;i++)
{
s_cnt++;
}
printf("[%s]pthread_mutex_unlock------s_cnt=%d\n",__FUNCTION__,s_cnt);
pthread_mutex_unlock(s_mutex);///<解锁
pthread_exit(NULL);
}
void*thread2_fun(void*arg)
{
printf("[%s]pthread_mutex_lock------s_cnt=%d\n",__FUNCTION__,s_cnt);
pthread_mutex_lock(s_mutex);///<加锁
for(size_ti=0;i<100;i++)
{
s_cnt++;
}
printf("[%s]pthread_mutex_unlock------s_cnt=%d\n",__FUNCTION__,s_cnt);
pthread_mutex_unlock(s_mutex);///<解锁
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
///<创建互斥量
ret=pthread_mutex_init(s_mutex,NULL);
if(ret!=0)
{
printf("pthread_mutex_initerror!\n");
exit(EXIT_FAILURE);
}
///<创建线程1
ret=pthread_create(s_thread1_id,NULL,thread1_fun,NULL);
if(ret!=0)
{
printf("thread1_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread1_id,NULL);///<阻塞等待线程结束
if(ret!=0)
{
printf("pthread1_joinerror!\n");
exit(EXIT_FAILURE);
}
///<创建线程2
ret=pthread_create(s_thread2_id,NULL,thread2_fun,NULL);
if(ret!=0)
{
printf("thread2_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_join(s_thread2_id,NULL);///<阻塞等待线程结束
if(ret!=0)
{
printf("pthread2_joinerror!\n");
exit(EXIT_FAILURE);
}
printf("mainthread,s_cnt=%d\n",s_cnt);
ret=pthread_mutex_destroy(s_mutex);
{
printf("pthread_mutex_destroyerror!\n");
exit(EXIT_FAILURE);
}
return0;
}
编译、运行:

条件变量是在线程中以睡眠的方式等待某一条件的发生,是利用线程间共享的全局变量进行同步的一种机制。
条件变量是线程可用的一种同步机制,条件变量给多个线程提供了一个会合的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件变量API:
#include<pthread.h>
///<条件变量初始化
intpthread_cond_init(pthread_cond_t*restrictcond,constpthread_condattr_t*restrictattr);
///<销毁条件变量
intpthread_cond_destroy(pthread_cond_t*cond);
///<等待条件变量
intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex);
///<带有超时功能的等待条件变量
intpthread_cond_timedwait(pthread_cond_t*restrictcond,
pthread_mutex_t*restrictmutex,
conststructtimespec*restricttsptr);
///<通知条件变量,唤醒至少1个等待该条件的线程
intpthread_cond_signal(pthread_cond_t*cond);
///<通知条件变量,广播唤醒等待该条件的所有线程
intpthread_cond_broadcast(pthread_cond_t*cond);
假如有两个线程,线程1依赖于某个变量才能执行相应的操作,而这个变量正好是由线程2来改变的。这种情况下有两种方案编写程序:
方案一:线程1轮询的方式检测这个变量是否变化,变化则执行相应的操作。
方案二:使用条件变量的方式。线程1等待线程2满足条件时进行唤醒。
其中,方案一比较浪费CPU资源。
条件变量的例子:创建两个线程,线程1对全局计数变量cnt从0开始进行自增操作。线程2打印5的倍数,线程1打印其它数。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
staticpthread_ts_thread1_id;
staticpthread_ts_thread2_id;
staticunsignedchars_thread1_running=0;
staticunsignedchars_thread2_running=0;
staticpthread_mutex_ts_mutex;
staticpthread_cond_ts_cond;
staticints_cnt=0;
void*thread1_fun(void*arg)
{
s_thread1_running=1;
while(s_thread1_running)
{
pthread_mutex_lock(s_mutex);///<加锁
s_cnt++;
pthread_mutex_unlock(s_mutex);///<解锁
if(s_cnt%5==0)
{
pthread_cond_signal(s_cond);///<唤醒其它等待该条件的线程
}
else
{
printf("[%s]s_cnt=%d\n",__FUNCTION__,s_cnt);
}
usleep(100*1000);
}
pthread_exit(NULL);
}
void*thread2_fun(void*arg)
{
s_thread2_running=1;
while(s_thread2_running)
{
pthread_mutex_lock(s_mutex);///<加锁
while(s_cnt%5!=0)
{
pthread_cond_wait(s_cond,s_mutex);///<等待条件变量
}
printf("[%s]s_cnt=%d\n",__FUNCTION__,s_cnt);
pthread_mutex_unlock(s_mutex);///<解锁
usleep(200*1000);
}
pthread_exit(NULL);
}
intmain(void)
{
intret=0;
///<创建互斥量
ret=pthread_mutex_init(s_mutex,NULL);
if(ret!=0)
{
printf("pthread_mutex_initerror!\n");
exit(EXIT_FAILURE);
}
///<创建条件变量
ret=pthread_cond_init(s_cond,NULL);
if(ret!=0)
{
printf("pthread_cond_initerror!\n");
exit(EXIT_FAILURE);
}
///<创建线程1
ret=pthread_create(s_thread1_id,NULL,thread1_fun,NULL);
if(ret!=0)
{
printf("thread1_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_detach(s_thread1_id);
if(ret!=0)
{
printf("s_thread1_iderror!\n");
exit(EXIT_FAILURE);
}
///<创建线程2
ret=pthread_create(s_thread2_id,NULL,thread2_fun,NULL);
if(ret!=0)
{
printf("thread2_createerror!\n");
exit(EXIT_FAILURE);
}
ret=pthread_detach(s_thread2_id);
if(ret!=0)
{
printf("s_thread2_iderror!\n");
exit(EXIT_FAILURE);
}
while(1)
{
sleep(1);
}
return0;
}
编译、运行:

以上就是本次的分享,如果觉得文章有用,欢迎收藏、转发!
相关资料:
《UNIX环境高级编程(第三版)》
《高质量嵌入式Linux C编程》
https://zhuge.blog.csdn.net/article/details/123783374
https://blog.csdn.net/jinking01/article/details/112185115
在公众号聊天界面回复1024,可获取嵌入式资源;回复m,可查看文章汇总
新闻来源:嵌入式大杂烩,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。


