自旋锁
除了前面提到过的互斥锁和读写锁外,还有另外一种锁:自旋锁(spinlock)。自旋锁的原理比较简单,就是当某个资源不可用时不断查询,直到资源可用:
void tese_and_set(bool* condition)
{
bool tmp = *condition;
*condition = true;
return tmp;
}
void func()
{
while (test_and_set(&condition));
/* visit */
condition = false;
}
其中的操作 test_and_set() 可以看成一个原子操作,通常由硬件来实现。如果 condition 为 false,while 循环会不断地查询 condition 的状态,直到其可用为止。因为在条件不满足时程序也一直在忙,这样会消耗 cpu 资源,所以自旋锁适合用在执行时间不长的临界区中,例如修改一个数值等。
由于自旋锁比较简单,pthread 提供的接口也比较简单。下面是使用自旋锁实现的生产者-消费者问题(其实就是将 mutex 替换为 spinlock):
#include <stdio.h>
#include <pthread.h>
struct pool {
pthread_spinlock_t lock;
#define MAX_NUM 5
int num;
};
static inline int is_full(struct pool* pool)
{
return (pool->num == MAX_NUM);
}
static inline int is_empty(struct pool* pool)
{
return (pool->num == 0);
}
static inline void put(struct pool* pool)
{
++pool->num;
}
static inline void get(struct pool* pool)
{
--pool->num;
}
void init(struct pool* pool)
{
pool->num = 0;
pthread_spin_init(&pool->lock, 0);
}
static void* consumer(void* arg)
{
struct pool* pool = (struct pool*)arg;
while (1) {
pthread_spin_lock(&pool->lock);
if (is_empty(pool))
printf("get fail.\n");
else {
get(pool);
printf("get successfully. available: %d\n", pool->num);
}
pthread_spin_unlock(&pool->lock);
}
return arg;
}
static void* producer(void* arg)
{
struct pool* pool = (struct pool*)arg;
while (1) {
pthread_spin_lock(&pool->lock);
if (is_full(pool))
printf("put fail.\n");
else {
put(pool);
printf("put successfully. available: %d\n", pool->num);
}
pthread_spin_unlock(&pool->lock);
}
return arg;
}
int main(void)
{
struct pool pool;
pthread_t producer_id, consumer_id;
init(&pool);
pthread_create(&producer_id, NULL, producer, &pool);
pthread_create(&consumer_id, NULL, consumer, &pool);
pthread_join(producer_id, NULL);
pthread_join(consumer_id, NULL);
return 0;
}
初始化函数 init() 中使用了
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
第二个参数 pshared 的取值可以为 PTHREAD_PROCESS_SHARED 或 PTHREAD_PROCESS_PRIVATE,即是否允许在多个线程中共享该自旋锁(详细解释请 man pthread_spin_init)。访问临界区前使用函数
int pthread_spin_lock(pthread_spinlock_t *lock);
对自旋锁进行加锁。与互斥锁和读写锁相似的是,这个函数是阻塞版本,也就是一直到加锁成功才返回。另外的立即返回的函数为
int pthread_spin_trylock(pthread_spinlock_t *lock);
访问完临界区后使用函数
int pthread_spin_unlock(pthread_spinlock_t *lock);
释放占有的锁。最后使用函数
int pthread_spin_destroy(pthread_spinlock_t *lock);
释放不需要的资源。
终止线程
很多时候线程里都是一个 while(1) 循环,想退出的话要么加个开关变量,每次执行前检查一下变量的值,但是这样有点罗嗦;要么用暴力点的方法:直接杀掉。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static void* thread_func(void* nil)
{
while (1) {
printf("sleep 1 s...\n");
sleep(1);
}
printf("will never reach here.\n");
return NULL;
}
int main(void)
{
pthread_t pid;
if (pthread_create(&pid, NULL, thread_func, NULL) != 0)
fprintf(stderr, "pthread_create error\n");
sleep(3);
pthread_cancel(pid);
pthread_join(pid, NULL);
printf("thread is killed.\n");
return 0;
}
第 25 行使用函数
int pthread_cancel(pthread_t thread);
向线程发了一个 cancel 的信号,但是线程并不会马上结束。线程能否被杀死取决于两个属性:线程的 cancel 状态(state)和类型(type)。使用函数
int pthread_setcancelstate(int state, int *oldstate);
可以设置线程是否可被 pthread_cancel() 杀死,state 的值只能是 PTHREAD_CANCEL_ENABLE 或 PTHREAD_CANCEL_DISABLE。如果 oldstate 不为空,线程原来的状态会保存在 oldstate 中,方便以后恢复。线程默认的状态是 enable,即可被 pthread_cancel() 杀死。线程的类型(type)可使用函数
int pthread_setcanceltype(int type, int *oldtype);
来设置,type 的值只能是 PTHREAD_CANCEL_DEFERRED 或 PTHREAD_CANCEL_ASYNCHRONOUS。如果值为 PTHREAD_CANCEL_DEFERRED,线程并不会立即被杀死,而是当线程运行到某个可处理 cancel 信号的函数时才会被杀死(man 7 pthread搜关键字“cancellation point”)。如果值为 PTHREAD_CANCEL_ASYNCHRONOUS,理论上线程会被立即杀掉,但是由于调度或中断等其它原因可能会有延迟。
如果线程中没有用到 cancellation point 中提到的函数,可以使用
void pthread_testcancel(void);
手动创建一个 cancellation point,这样当线程运行到这个函数的时候,如果有 cancel 信号就会被杀死。
信号传递
pthread 提供了一个给线程传递信号的函数:
int pthread_kill(pthread_t thread, int sig);
千万不要被函数名字骗了,以为随便发个信号就能杀掉进程(我刚开始就是这么以为的……)。函数给指定的线程发一个信号,如果进程没有实现相应的信号处理函数,该信号默认的行为将会作用于线程所在的整个进程,例如向某个线程发送一个 SIGINT 信号,如果没有实现对应的信号处理函数,则整个进程都会退出。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
static void sigint_action(int sig, struct siginfo* s, void* nil)
{
pthread_exit(NULL);
}
static inline void install_sighandler(void)
{
struct sigaction handler;
sigemptyset(&handler.sa_mask);
sigaddset(&handler.sa_mask, SIGINT);
handler.sa_flags = SA_SIGINFO; /* use sa_sigaction instead of sa_handler */
handler.sa_sigaction = sigint_action;
if (sigaction(SIGINT, &handler, NULL) != 0)
perror("sigaction");
}
static void* thread_func(void* tid)
{
while (1) {
printf("thread %d is running ...\n", *(int*)tid);
sleep(1);
}
return tid;
}
#define THREAD_NR 3
int main(void)
{
int i, tid[THREAD_NR];
pthread_t pid[THREAD_NR];
for (i = 0; i < THREAD_NR; ++i) {
tid[i] = i;
if (pthread_create(&pid[i], NULL, thread_func, &tid[i]) != 0) {
fprintf(stderr, "cannot create thread %d.\n", i);
return -1;
}
}
install_sighandler();
for (i = THREAD_NR - 1; i >= 0; --i) {
sleep(3);
if (pthread_kill(pid[i], SIGINT) != 0)
perror("pthread_kill");
pthread_join(pid[i], NULL);
printf("-----> thread %d is killed.\n", i);
}
return 0;
}
main() 中创建了 3 个线程,并使用 sigaction() 为信号 SIGINT 指定了一个处理函数 sigint_action()。sigint_action() 调用了 pthread_exit() 终止收到 SIGINT 信号的线程。如果没有这个函数(把第 20-21 行注释掉),SIGINT 默认作用于整个进程,即整个进程退出而不仅仅是收到这个信号的线程。