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 ) ;
}