Linux线程(二)

linux线程(二)

一、线程终止

1.pthread_exit函数:

只需终止某个线程而不需要终止整个进程的三个方法:

  • 从线程函数return,这种方法对主线程不适合,从main函数的return,相当于调用exit函数
  • 线程可以调用pthread_exit来终止自己
  • 一个线程可以调用pthread_cancel函数终止同一个进程中的另一个线程
 void pthread_exit(void *retval);
  • 作用:终止线程自己
  • 参数:void *retval:不要指向一个局部变量,子线程可以通过pthread_exit传递一个返回值,而主线程通过pthread_join获得该返回值,从而判断该子线程的退出是正常还是异常。
  • 返回值:无反回值,和进程一样,线程结束就无法返回到它的调用者(自己都被自己给干掉了,怎么返回给自己)
  • 注意⚠️:pthread_exit或者return返回的指针所指向的内存单元必须是全局变量或者是在堆上申请的空间,不能是线程函数内部栈上分配的,因为其他线程得到这个返回指针的时候线程函数已经退出了

2.pthread_cancel函数

int pthread_cancel(pthread_t thread)
  • 功能:取消一个执行中的线程
  • 参数:thread:线程ID
  • 返回值:成功返回0.失败返回错误码

二、线程等待

  1. 线程等待原因:
  • 已经退出的线程,其空间是没有被释放的,仍然在进程的地址空间中,即释放资源,防止内存泄漏
  • 创建新的线程不会复用刚才退出线程的地址空间

2.pthread_join函数

int pthread_join(pthread_t thread, void **retval);
  • 功能:等待线程的结束

  • 参数:pthread_t thread:线程ID
    void **retval:它指向一个指针指向线程的返回值,输出型参数,一般填NULL

  • 注意⚠️:调用该函数的线程将被挂起等待,直到id为指定的线程结束终止,thread线程以不同的方法终止,通过pthread_join得到的终
    止状态是不同的,总结如下:
    a.如果thread线程是通过return返回,retval所指向的单元里存放的是thread线程函数的返回值
    b.如果thread线程被别的线程调用pthread_cancel函数异常终止掉,retval所指向的单元里存放到是常数PTHREAD_
    CANCELED
    c.如果thread线程是调用pthread_exit函数终止的,retval所指向的单元里存放是传给pthread_exit函数的参数,(即这里的retval参数是和pthread_exit函数的参数是相关联的)
    d.如果对thread线程终止状态不感兴趣直接传NULL即可。

    Linux线程(二)_第1张图片
    通过代码验证:

#include 
#include 
#include 
#include 
#include 
void *thread1( void *arg )
{
printf("thread 1 returning ... \n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
} v
oid *thread2( void *arg )
{
printf("thread 2 exiting ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
} v
oid *thread3( void *arg )
{
while ( 1 ){ //
printf("thread 3 is running ...\n");
sleep(1);
} r
eturn NULL;
} i
nt main( void )
{
pthread_t tid;
void *ret;
// thread 1 return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
free(ret);
// thread 2 exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
free(ret);
// thread 3 cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if ( ret == PTHREAD_CANCELED )
printf("thread return, thread id %X, return code:PTHREAD_CANCELED\n", tid);
else
printf("thread return, thread id %X, return code:NULL\n", tid);
}

Linux线程(二)_第2张图片
通过结果充分验证上面的结论

三、线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放线程自己的空间上的资源,从而导致内存泄漏
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时自动释放线程的资源
int pthread_detach(pthread_t thread)
  • 功能:可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
  • 参数:thread:线程ID
  • 返回值:若成功则返回0,若出错则返回错误码
  • 注意⚠️:joinable和thread_detach是冲突的,一个线程不能即时joinable的又是分离的
#include 
#include 
#include 
#include 
#include 
void *thread_run( void * arg )
{
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
} i
nt main( void )
{
pthread_t tid;
if ( pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0 ) {
printf("create thread error\n");
return 1;
} i
nt ret = 0;
sleep(1);//很重要,要让线程先分离,再等待
if ( pthread_join(tid, NULL ) == 0 ) {
printf("pthread wait success\n");
ret = 0;
} else {
printf("pthread wait failed\n");
ret = 1;
} r
eturn ret;
}

Linux线程(二)_第3张图片

四、线程互斥

1.线程互斥相关概念

  • List item临界资源:多线程执行流中共享的资源就叫临界资源(如多个线程共同访问一个全局变量,那个被访问的全局变量就是临界资源)
  • 临界区:每个线程内部,用来访问临界资源的代码就是临界区(如上面的例子,访问全局变量的代码就是临界区)
  • 互斥:任何时候,互斥保证了有且仅有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。互斥机制也是解决线程不安全问题的方法
  • 互斥机制的使用:a.先加锁 b.执行临界区代码 c.释放锁
  • 注意,在互斥机制下,同一时刻只能有一个线程获取到锁,只有获取到锁的线程才能执行临界区代码,访问临界资源,其他线程只能等待获得锁的线程释放锁
  • 原子性:不会被任何调度机制打断的操作,该操作只有两个状态,要么完成,要么未完成(相当于是最小的帧操作,没有任何机制可以打断的操作)

2.互斥量:mutex

  • 大部分情况,线程使用的数据都是局部数据,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获取到这种变量
  • 有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互
  • 但是,多个线程并发的操作共享变量,会带来很多问题
#include 
#include 
#include 
#include 
#include 
int ticket = 100;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 ) {
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
} else {
break;
}
}
} i
nt main( void )
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}

Linux线程(二)_第4张图片
Linux线程(二)_第5张图片
根据上面的代码,发现没有正确的获取到我们想要的结果,为什么呢?

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • –ticket操作本身就不是一个原子操作
    Linux线程(二)_第6张图片
  • –操作并不是原子操作,而是对应三条汇编指令
  • load:将共享变量ticket从内存加载到寄存器中
  • update: 更新寄存器里面的值,执行-1操作
  • store:将新值,从寄存器写回共享变量ticket的内存地址

解决办法请看Linux线程(三)

你可能感兴趣的:(Linux)