Linux线程相关笔记

Linux“线程”

进程与线程之间是有区别的,不过linux内核只提供了轻量进程的支持,未实现线程模型。Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。Linux中所谓的“线程”只是在被创建时clone了父进程的资源,因此clone出来的进程表现为“线程”。

1.创建线程

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create:

pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * 
(new_pthread)(void*), void *arg);

参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的地址。
最后一个参数是运行函数的参数。

每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用pthread_self()调用获取自己的线程ID:

pthread_self(void) ;

2.线程退出

线程的退出方式有三种:
(1)执行完成后隐式退出;
(2)由线程本身显示调用pthread_exit 函数退出;

pthread_exit(void * retval) ;

(3)被其他线程用pthread_cance函数终止:

pthread_cance(pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。

等待另一个线程结束

pthread_join使当前一个线程阻塞地等待另一个线程结束。代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行,一般pthread_join放在main()函数中。

pthread_join(pthread_t thread, void** threadreturn);
/*
args:
    pthread_t thread: 被连接线程的线程号
    void **retval   : 指向一个指向被连接线程的返回码的指针的指针
return:
    线程连接的状态,0是成功,非0是失败*/

当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。

3.线程通信

线程互斥

互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux下可以通过pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞(休眠),直到拥有该互斥体的线程完成互斥部分的操作为止。
下面的代码实现了对共享全局变量x 用互斥体mutex 进行保护的目的:

int x; // 进程中的全局变量 
pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex 
pthread_mutex_lock(&mutex); // 给互斥体变量加锁 //对变量x 的操作 
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁
线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任务必须在一定的条件下同步。
Linux下的C语言编程有多种线程同步机制,最典型的是条件变量。
pthread_cond_init用来创建一个条件变量,其函数原型为:

pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

使用pthread_cond_wait方式如下:

pthread_mutex_lock(&mutex)
while(线程执行的条件不成立)
{
	pthread_cond_wait(&cond, &mutex);
}
//线程执行相应操作
pthread_mutex_unlock(&mutex);

pthread_cond_wait() 必须与pthread_mutex 配套使用。
条件变量的使用:无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求条件变量而导致竞争,所以要让每个线程互斥的访问公有资源,即上述的条件变量。
pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。

问题1:pthread_cond_wait()会阻塞当前线程,但在前面持有的锁怎么办?
回答:pthread_cond_wait()函数一进入wait状态,其函数内部就会自动释放互斥锁。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。
因此pthread_cond_wait()可分为三步:block–>unlock–>wait() return–>lock
(1)线程放在等待队列上,释放前面加的互斥锁,阻塞自己(休眠);
(2)等待 pthread_cond_signal或者pthread_cond_broadcast信号之后去竞争锁,解除阻塞;
(3)若竞争到互斥锁则加锁(与后一个解锁对应) 。

问题2:为什么使用while循环?
回答:防止线程被意外唤醒或者虚假唤醒,使用while循环同时也可以解决多个线程等待此资源的问题。

pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_signal则用于解除某一个等待线程的阻塞状态:

pthread_cond_signal(pthread_cond_t *cond) ;

条件变量的销毁

pthread_cond_destroy(pthread_cond_t *cv); //返回0表示成功,返回其他值都表示失败。

生产者、消费者问题

假设有两个线程同时访问一个全局变量 n,这个全局变量的初始值等于0。
消费者线程 A 进入临界区,访问 n,A 必须等到 n 大于 0 才能接着往下执行,如果 n== 0,那么 A 将一直等待。
还有一个生产者线程 B,B 进入临界区,修改 n 的值,使得 n >0,当 n > 0 时,B 通知等待 n > 0 的消费者线程A。A 被 B 通知之后就可以接着往下执行了。
生产者和消费者模型中互斥锁和条件变量的使用流程图如下,其中蓝色代表消费者的执行流,红色是生产者的执行流。
在这里插入图片描述
消费者等待条件的伪代码:

pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区
while( 条件为假)
	pthread_cond_wait(cond, mutex); // 令进程等待在条件变量上
修改条件
pthread_mutex_unlock(&mutex); // 释放互斥锁

生产者通知消费者的伪代码:

pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区
设置条件为真
pthread_cond_signal(cond); // 通知等待在条件变量上的消费者
pthread_mutex_unlock(&mutex); // 释放互斥锁

代码

#include <unistd.h>
#include <pthread.h>


#define CONSUMERS_COUNT 2	
#define PRODUCERS_COUNT 1

pthread_mutex_t g_mutex ;
pthread_cond_t g_cond ;

pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT] ;
int share_variable = 0 ;// 生产者和消费者要访问的共享变量
 
void* consumer( void* arg )
{
   int num = (int)arg ;
   while ( 1 ) 
   {
      //临界区开始
      pthread_mutexlock( &g_mutex ) ;

      // 若share_variable为0,消费者需等待
      while ( share_variable == 0 )
      {
         printf( "consumer %d begin wait a condition...\n", num ) ;
         // 等待条件变量的通知,并释放锁
         pthread_cond_wait( &g_cond, &g_mutex ) ;
      }
      // share_variable不为0,消费者执行减1操作
      printf( "consumer %d end wait a condition...\n", num ) ;
      printf( "consumer %d begin consume product\n", num ) ;
      share_variable--;
      pthread_mutex_unlock( &g_mutex ) ;
      // 临界区结束
      sleep( 1 ) ;
   }
   
   return NULL ;
}
 
void* producer( void* arg )
{
   int num = (int)arg ;
   while ( 1 )
   {
      // 临界区开始
      pthread_mutex_lock( &g_mutex ) ;
 
      // share_variable加1
      printf( "producer %d begin produce product...\n", num ) ;
      share_variable++;
      printf( "producer %d end produce product...\n", num ) ;
      // 通知消费者线程条件成立,不再阻塞
      pthread_cond_signal( &g_cond ) ;
      printf( "producer %d notified consumer by condition variable...\n", num ) ;
      pthread_mutex_unlock( &g_mutex ) ;
 
      // 临界区结束
      sleep( 5 ) ;
   }
   
   return 1 ;
}

int main( void )
{
   //初始化互斥体和条件变量
   pthread_mutex_init( &g_mutex, NULL ) ;
   pthread_cond_init( &g_cond, NULL ) ;
 
   // 创建两个消费者线程
   for ( int i = 0; i < CONSUMERS_COUNT; ++ i )
   {
      pthread_create( &g_thread[i], NULL, consumer, (void*)i ) ;
   }
   sleep( 1 ) ;
   // 创建一个生产者线程
   for ( int i = 0; i < PRODUCERS_COUNT; ++ i )
   {
      pthread_create( &g_thread[i], NULL, producer, (void*)i ) ;
   }
   for ( int i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; ++ i ) 
   {
      pthread_join( g_thread[i], NULL ) ;
   }
 
   pthread_mutex_destroy( &g_mutex ) ;
   pthread_cond_destroy( &g_cond ) ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值