Pthread学习文档

什么是线程

线程是 CPU 调度的最小执行单位,你可以创建一个线程用于ListenMusic,再创建一个线程去PlayGame,这样操作系统就是同时处理这两个任务的(并发)。

最直观的感受就是,你可以边打游戏边听歌。

Pthread库

使用说明

POSIX 标准定义了一套线程操作相关的函数,用于让程序员更加方便地操作管理线程,函数名都是以前缀 pthread_开始,使用前包含头文件

1
#include<pthread.h>

在链接的时候要手动链接 pthread 这个库,如:gcc main.c -lpthread -o main

常用函数

pthread_create():创建一个线程

pthread_self():获取线程id

int pthread_equal(pthread_t t1, pthread_t t2):比较两个线程是否完全相同

void pthread_exit(void *retval):退出指定进程

int pthread_join(pthread_t thread,void **retval):阻塞等待线程退出,获取线程退出状态,相当于进程中的 waitpid 函数,如果线程退出,pthread_join 立刻返回。

int pthread_detach(pthread_t thread):将线程 ID 为 thread 的线程分离出去,所谓分离出去就是指主线程不需要再通过 pthread_join 等方式等待该线程的结束并回收其线程控制块(TCB)的资源,被分离的线程结束后由操作系统负责其资源的回收。

int pthread_cancel(pthread_t thread):杀死线程

入门:Pthread实现多线程HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<pthread.h>
#include<iostream>
#include<stdlib.h>
using namespace std;

int thread_count;

void* Hello(void* rank);

int main(int argc,char* argv[]){
long thread;
pthread_t* thread_handles;
// thread_count=strtol(argv[1],NULL,10);//从终端进行输入
cin>>thread_count;
thread_handles=(pthread_t*)malloc(thread_count*sizeof(pthread_t));
for(thread=0;thread<thread_count;thread++){
pthread_create(&thread_handles[thread],NULL,Hello,(void*)thread);
}

cout<<"Hello from the main thread"<<endl;
//退出线程
for(thread=0;thread<thread_count;thread++){
pthread_join(thread_handles[thread],NULL);
}
free(thread_handles);
return 0;
}

void* Hello(void* rank){
long my_rank=(long)rank;
printf("Hello from thread %ld of %d\n",my_rank,thread_count);
}
1
2
3
4
5
6
7
输入:4
输出:
Hello from thread 0 of 4
Hello from thread 1 of 4
Hello from thread 2 of 4
Hello from thread 3 of 4
Hello from the main thread

临界区

共享内存有一个基本问题,即当多个线程同时更新一个共享资源时,结果是无法预测的。比如一个加法进程与一个乘法进程同时访问一个共享资源,得到的结果可以完全不同:

应该保证临界区资源一次只允许一个线程执行代码片段

互斥量

互斥量类型:pthread_mutex_t

销毁:int pthread_mutex_destroy(pthread_mutex_t* mutex_p)

获取:int pthread_mutex_lock(pthread_mutex_t* mutex_p)

释放:int pthread_mutex_unlock(pthread_mutex_t* mutex_p)

用互斥量计算全局和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void* Thread_sum(void* rank){
long my_rank=(long)rank;
double factor,my_sum=0.0;
long long i;
long long my_n=n/thread_count;
long long my_first_i=my_n*my_rank;
long long my_last_i=my)first_i+my_n;
if(my_first_i%2==0){
factor=1.0;
}else{
factor=-1.0;
}
for(i=my_first_i;i<my_last_i;i++,factor=-factor)
my_sum+=factor/(2*i+1)

//实现互斥区访问
pthread_mutex_lock(&mutex);
sum+=my_sum;
pthread_mutex_unlock(&mutex);
}

生产者-消费者同步与信号量

生产者消费者同步模型

例:每个线程向其他线程发送消息

1-2,2-3,3-4,……(n-1)-n,n-0,类似于这样

使用忙等待
1
2
3
4
5
6
7
8
9
10
11
void* Send_msg(void* rank){
long my_rank=(long)rank;
long desk=(my_rank+thread_count-1)%thread_count;
long source=(my_rank+thread_count-1)%thread_count;
char* my_msg=malloc(MSG*sizeof(char));
messages[dest]=my_msg;
//忙等待
while(message[my_rank]==NULL){
cout<<"Thread"<<my_rank<<message[my_rank];
}
}

这里的忙等待与条件判断是大致一致的。

1
2
3
4
5
if(message[my_rank]!=NULL){
cout<<"Thread"<<my_rank<<message[my_rank];
}else{
cout<<"Thread"<<my_rank<<"No message from"<<source;
}
信号量机制

可以类比一下操作系统中的P,V操作,将上述忙等待的内容更改成如下即可:

1
2
sem_post(&semphores[dest]);
sem_wait(&semphores[my_rank]);

路障与条件变量

路障

路障指,使所有线程到达程序中同一个位置后,再继续执行。

实现方式1:忙等待
1
2
3
4
5
6
7
8
9
10
11
12
13
//路障需要一个不能重复使用的共享变量
int counter=0;//初始化为0
int thread_count;
pthread_mutex_t barrier_mutex;

void* Thread_work(){
...
pthread_mutex_lock(&barrier_mutex);//加锁
counter++;
pthread_mutex_unlock(&barrier_mutex);//解锁
//到达同一个地点,忙等待排队
while(counter<thread_count){...}
}
实现方式2:信号量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int counter=0;//无法重复使用的共享变量\
sem_t count_sem=1;
sem_t barrier_sem=0;

void* Thread_work(){
...
sem_wait(&count_sem);
if(counter==thread_count-1){
counter=0;
sem_post(&count_sem);
for(j=0;j<thread_count-1;j++){
sem_post(&barrier_sem);
}
}else{
counter++;
sem_post(&count_sem);
sem_wait(&barrier_sem);
}
}

当路障重复使用时,barrier_sem产生竞争。