多线程编程
2021-07-25 00:00:32 #网络编程 

线程使用

线程模型

详情见【OS】进程与线程 | xStack 线程模型相关内容


Linux 线程库

Linux 上默认是 NPTL(Native POSIX Thread Library),查询相关信息

1
2
3
4
5
6
~ » neofetch
OS: Arch Linux on Windows 10 x86_64
Kernel: Linux 5.10.43.3-microsoft-standard-WSL2

~ » getconf GNU_LIBPTHREAD_VERSION
NPTL 2.33

创建与结束

1
2
3
4
5
6
#include <pthread.h>
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*),
void* arg);

#include <bits/pthreadtypes.h>
typedef unsigned long int pthread_t;

thread:新线程的标识符

attr:设置线程属性,NULL表示默认属性

start_routine:线程将要执行的函数

art:线程执行函数的参数列表

成功时返回 0,失败时返回错误码

通过输出以下信息可以查询 Linux 中所有用户能创建的线程总数,当前机器最多创建 101244 个线程

1
2
3
4
5
6
~ » neofetch
OS: Arch Linux on Windows 10 x86_64
Kernel: Linux 5.10.43.3-microsoft-standard-WSL2

~ » cat /proc/sys/kernel/threads-max
101244

线程被创建好后,内核调度内核线程来执行start_routine函数指针所指向的工作函数。完成工作后调用退出函数

1
2
#include <pthread.h>
void pthread_exit(void* retval);

retval:通过此参数向线程回收者返回退出信息

1
2
#include <pthread.h>
int pthread_join(pthread_t thread, void** retval);

thread:线程标识符

retval:目标线程返回的退出信息

执行pthread_join()会一直阻塞,直到被回收的线程结束,成功时返回 0,失败返回错误码

1
2
#include <pthread.h>
int pthread_cancel(pthread_t thread);

thread:线程标识符

此函数异常终止一个线程,成功时返回 0,失败返回错误码。被终止的目标线程可以通过以下函数自主决定是否可以被取消以及取消方式

1
2
3
#include <pthread.h>
int pthread_setcancelstate(int state, int* oldstate);
int pthread_setcanceltype(int type, int* oldtype);

是否可以取消:state

PTHREAD_CANCEL_ENABLE:可以取消,创建线程时的默认状态

PTHREAD_CANCEL_DISABLE:不可取消

线程原来的取消状态:oldstate

取消方式:type

PTHREAD_CANCEL_ASYNCHRONOUS:线程可以随时取消,接收取消请求时立即行动

PTHREAD_CANCEL_DEFERRED:可以推迟取消,直到调用取消点函数


例:

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
33
34
35
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void*
thread(void* ptr)
{
for (int i = 0; i < 3; i++) {
sleep(1);
printf("This is a pthread.\n");
}

pthread_exit(0);

return 0;
}

int
main()
{
pthread_t id;
if (pthread_create(&id, NULL, thread, NULL)) {
printf("Create pthread error!\n");
return 1;
}

for (int i = 0; i < 3; i++) {
printf("This is the main process.\n");
sleep(1);
}

pthread_join(id, NULL);

return 0;
}

线程同步

POSIX 信号量

1
2
3
4
5
6
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
int sem_destroy(sem_t* sem);
int sem_wait(sem_t* sem);
int sem_trywait(sem_t* sem);
int sem_post(sem_t* sem);

sem:被操作的信号量

pshared:取值为 0 表示为当前进程的局部信号量,否则可以在多个进程间共享

value:指定信号量初始值,0 或者更多

sem_init()用于初始化一个未命名的信号量

sem_destroy()用于销毁信号量

sem_wait()用于将信号量-1(原子操作),即 P 操作;当信号量为 0 时,阻塞

sem_trywait()用于非阻塞的 P 操作;当信号量为 0 时返回-1 并设置 errno

sem_post()用于将信号量 +1(原子操作),即 V 操作;当信号量大于 0 时,将唤醒其他等待该信号量的阻塞进程

以上成功时返回 0,失败返回-1 并设置 errno


互斥锁

1
2
3
4
5
6
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

mutex:被操作的互斥锁

mutexattr:初始化时互斥锁的属性;设置为NULL时使用默认属性

pthread_mutex_init()用于初始化互斥锁

pthread_mutex_destroy()用于销毁互斥锁

pthread_mutex_lock()用于加锁(原子操作);当已上锁时阻塞

pthread_mutex_trylock()非阻塞的上锁操作;已上锁时返回错误码EBUSY

pthread_mutex_unlock()用于解锁(原子操作);当其他线程等待这个互斥锁时将唤醒某一个线程

以上成功时返回 0,失败返回错误码

互斥锁通过pthread_mutexattr_t结构体来定义其属性,并通过函数操作来设置属性

1
2
3
4
5
6
7
8
9
10
11
#include <pthread.h>
// 初始化互斥锁对象属性
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
// 销毁互斥锁对象属性
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
// 获取和设置互斥锁的pshared属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr, int* pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared);
// 获取和设置互斥锁的type属性
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);

是否允许跨进程共享互斥锁:pshared

PTHREAD_PROCESS_SHARED:互斥锁可以跨进程共享

PTHREAD_PROCESS_PRIVATE:现有一线程用来初始化互斥锁,与此线程隶属于同一个进程的其他线程可以共享互斥锁,即不可以跨进程共享

指定互斥锁的类型:type

PTHREAD_MUTEX_NORMAL:普通锁,默认类型;请求锁的线程形成顺序等待队列;对已经加锁的锁再次加锁,将死锁;对已经解锁的锁再次解锁,将导致不可预知的错误

PTHREAD_MUTEX_ERRORCHECK:检错锁;对已经加锁的锁再次加锁,返回错误码EDEADLK;对已经解锁的锁再次解锁,返回错误码EPERM

PTHREAD_MUTEX_RECURSIVE:嵌套锁;对一个锁可以多次加锁,相应的解锁时也需要多次解锁

PTHREAD_MUTEX_DEFAULT:默认锁


条件变量

1
2
3
4
5
6
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);

cond:被操作的条件变量

cond_attr:初始化时条件变量的属性;设置为NULL时使用默认属性

pthread_cond_init()用于初始化条件变量

pthread_cond_destroy()用于销毁条件变量

pthread_cond_broadcast()用于以广播方式唤醒所有等待条件变量的线程

pthread_cond_signal()用于唤醒一个等待条件变量的线程

pthread_cond_wait()用于等待条件变量;使用前需要以mutex加锁以保证原子性

以上成功时返回 0,失败返回错误码